一期功能

This commit is contained in:
zy 2025-09-17 18:13:57 +08:00
parent 76d5d0941b
commit 5940a2916b
115 changed files with 6029 additions and 336 deletions

View File

@ -1,8 +1,20 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.AD_ID" />
<uses-permission android:name="com.google.android.gms.permission.AD_ID" />
<uses-permission android:name="com.android.vending.BILLING" />
<application
android:label="Kinetra"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
android:hardwareAccelerated="true"
android:icon="@mipmap/launcher_icon">
<activity
android:name=".MainActivity"
android:exported="true"

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
assets/collect_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

BIN
assets/dialog_bg1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

BIN
assets/dialog_bg2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

BIN
assets/dialog_logout.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
assets/dialog_star.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
assets/dialog_update.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
assets/ep_sel_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

BIN
assets/explore_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/home_item_sel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
assets/ic_arrow_top.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 B

BIN
assets/ic_back.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 579 B

BIN
assets/ic_back_white.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
assets/ic_dialog_delete.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 833 B

BIN
assets/ic_drop_dot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 942 B

BIN
assets/ic_ep.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

BIN
assets/ic_explore_ip.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
assets/ic_explore_tv.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
assets/ic_favorite.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/ic_fire.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
assets/ic_image_ver.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
assets/ic_load_failed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

BIN
assets/ic_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

BIN
assets/ic_mine_ip.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
assets/ic_mine_top_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
assets/ic_no_network.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
assets/ic_not_found.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
assets/ic_nothing.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
assets/ic_play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
assets/ic_right_arrow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 B

BIN
assets/ic_right_white.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

BIN
assets/ic_rise_bottom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
assets/ic_rise_play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
assets/ic_rise_right.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
assets/ic_top_play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

BIN
assets/setting_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

BIN
assets/text_bg_discover.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
assets/top_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
assets/top_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
assets/top_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
assets/top_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

BIN
assets/top_bottom_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
assets/top_other.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -563,7 +563,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@ -620,7 +620,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";

View File

@ -1,122 +1 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -1,19 +1,20 @@
class KtApis {
static const String baseUrl = "https://test1-api.guyantv.com";
static const String baseUrl = "https://api-csjiuxi.csjiuxi.com/kinet";
// static const String baseUrl = "https://test1-api.guyantv.com";
// static const String baseUrl = "https://api-pdaroll.pdaroll.com/panda";
static const String homeAllModules = "/home/all-modules";
static const String newShortPlay = "/newShortPlay";
static const String homeVideoList = "/videoList";
static const String getCategories = "/getCategories";
static const String getMostSearch = "/search/hots";
static const String searchHot = "/search/hots";
static const String search = "/search";
static const String getVideoDetails = "/getVideoDetails";
static const String getRecommands = "/getRecommands";
static const String getMyHistoryList = "/myHistorys";
static const String getMyCollections = "/myCollections";
static const String collectVideo = "/collect";
static const String cancelCollectVideo = "/cancelCollect";
static const String deleteFavoriteVideo = "/cancelCollect";
static const String getDetailsRecommand = "/getDetailsRecommand";
static const String getRevolutions = "/getRevolutions";
@ -79,7 +80,7 @@ class KtApis {
///
static String WEB_SITE_CIVIZ = "${WEB_SITE_INDEX}civizatio_convention";
static String WEB_SITE_HOST = "https://campaign.pdaroll.com/";
static String WEB_SITE_HOST = "https://campaign.csjiuxi.com/";
static String WEB_SITE_ACTIVITY = "${WEB_SITE_HOST}pages/reward/theme3";
///
@ -94,6 +95,9 @@ class KtApis {
///
static String WEB_SITE_WALLET = "${WEB_SITE_HOST}pages/leave/detail";
///
static String WEB_SITE_SEARCH = "${WEB_SITE_HOST}pages/search/kinetra";
/// w2a
static String WEB_2_APP_INDEX = "https://w2a.pdaroll.com/";
static String WEB_2_APP_INDEX = "https://w2a.csjiuxi.com/";
}

View File

@ -60,13 +60,13 @@ class ApiException implements Exception {
}
/// Dio请求封装类
class HttpClient {
static final HttpClient _instance = HttpClient._internal();
class KtHttpClient {
static final KtHttpClient _instance = KtHttpClient._internal();
factory HttpClient() => _instance;
factory KtHttpClient() => _instance;
late Dio _dio;
HttpClient._internal() {
KtHttpClient._internal() {
_initDio();
}

View File

@ -0,0 +1,134 @@
import 'dart:convert';
/// id : 76
/// short_id : 75
/// name : "Mrs. Gu, your disguise has been exposed"
/// short_play_video_id : 3572
/// description : "Since Xu Mumu married Gu Beichen, the family of three has been living their own small and happy life in a low-key manner. But Xu Mumu is not willing to be a full-time housewife from then on and is determined to create her own"
/// short_play_id : 76
/// image_url : "https://static.wanmwl.com/image/4e123202511fcb209b0d.webp"
/// episode_total : 18
/// current_episode : 1
/// updated_at : "2025-07-04 06:19:21"
/// is_collect : 1
/// category : ["Dominant CEO Romance","Sweet Romance "]
KtHistoryVideoBean ktHistoryVideoBeanFromJson(String str) =>
KtHistoryVideoBean.fromJson(json.decode(str));
String ktHistoryVideoBeanToJson(KtHistoryVideoBean data) =>
json.encode(data.toJson());
class KtHistoryVideoBean {
KtHistoryVideoBean({
num? id,
num? shortId,
String? name,
num? shortPlayVideoId,
String? description,
num? shortPlayId,
String? imageUrl,
num? episodeTotal,
num? currentEpisode,
String? updatedAt,
num? isCollect,
List<String>? category,
}) {
_id = id;
_shortId = shortId;
_name = name;
_shortPlayVideoId = shortPlayVideoId;
_description = description;
_shortPlayId = shortPlayId;
_imageUrl = imageUrl;
_episodeTotal = episodeTotal;
_currentEpisode = currentEpisode;
_updatedAt = updatedAt;
_isCollect = isCollect;
_category = category;
}
KtHistoryVideoBean.fromJson(dynamic json) {
_id = json['id'];
_shortId = json['short_id'];
_name = json['name'];
_shortPlayVideoId = json['short_play_video_id'];
_description = json['description'];
_shortPlayId = json['short_play_id'];
_imageUrl = json['image_url'];
_episodeTotal = json['episode_total'];
_currentEpisode = json['current_episode'];
_updatedAt = json['updated_at'];
_isCollect = json['is_collect'];
_category = json['category'] != null ? json['category'].cast<String>() : [];
}
num? _id;
num? _shortId;
String? _name;
num? _shortPlayVideoId;
String? _description;
num? _shortPlayId;
String? _imageUrl;
num? _episodeTotal;
num? _currentEpisode;
String? _updatedAt;
num? _isCollect;
List<String>? _category;
KtHistoryVideoBean copyWith({
num? id,
num? shortId,
String? name,
num? shortPlayVideoId,
String? description,
num? shortPlayId,
String? imageUrl,
num? episodeTotal,
num? currentEpisode,
String? updatedAt,
num? isCollect,
List<String>? category,
}) => KtHistoryVideoBean(
id: id ?? _id,
shortId: shortId ?? _shortId,
name: name ?? _name,
shortPlayVideoId: shortPlayVideoId ?? _shortPlayVideoId,
description: description ?? _description,
shortPlayId: shortPlayId ?? _shortPlayId,
imageUrl: imageUrl ?? _imageUrl,
episodeTotal: episodeTotal ?? _episodeTotal,
currentEpisode: currentEpisode ?? _currentEpisode,
updatedAt: updatedAt ?? _updatedAt,
isCollect: isCollect ?? _isCollect,
category: category ?? _category,
);
num? get id => _id;
num? get shortId => _shortId;
String? get name => _name;
num? get shortPlayVideoId => _shortPlayVideoId;
String? get description => _description;
num? get shortPlayId => _shortPlayId;
String? get imageUrl => _imageUrl;
num? get episodeTotal => _episodeTotal;
num? get currentEpisode => _currentEpisode;
String? get updatedAt => _updatedAt;
num? get isCollect => _isCollect;
List<String>? get category => _category;
set isCollect(num? value) => _isCollect = value;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = _id;
map['short_id'] = _shortId;
map['name'] = _name;
map['short_play_video_id'] = _shortPlayVideoId;
map['description'] = _description;
map['short_play_id'] = _shortPlayId;
map['image_url'] = _imageUrl;
map['episode_total'] = _episodeTotal;
map['current_episode'] = _currentEpisode;
map['updated_at'] = _updatedAt;
map['is_collect'] = _isCollect;
map['category'] = _category;
return map;
}
}

View File

@ -0,0 +1,37 @@
import 'dart:convert';
/// category_name : "123"
/// category_id : 12
KtHomeCategoryBean ktHomeCategoryBeanFromJson(String str) =>
KtHomeCategoryBean.fromJson(json.decode(str));
String ktHomeCategoryBeanToJson(KtHomeCategoryBean data) =>
json.encode(data.toJson());
class KtHomeCategoryBean {
KtHomeCategoryBean({String? categoryName, int? categoryId}) {
_categoryName = categoryName;
_categoryId = categoryId;
}
KtHomeCategoryBean.fromJson(dynamic json) {
_categoryName = json['category_name'];
_categoryId = json['category_id'];
}
String? _categoryName;
int? _categoryId;
KtHomeCategoryBean copyWith({String? categoryName, int? categoryId}) =>
KtHomeCategoryBean(
categoryName: categoryName ?? _categoryName,
categoryId: categoryId ?? _categoryId,
);
String? get categoryName => _categoryName;
int? get categoryId => _categoryId;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['category_name'] = _categoryName;
map['category_id'] = _categoryId;
return map;
}
}

View File

@ -0,0 +1,779 @@
import 'dart:convert';
/// business_model : "iap"
/// video_info : {"id":7132,"short_play_video_id":7132,"short_play_id":162,"short_id":11,"promise_view_ad":0,"video_url":"https://static.wanmwl.com/videom3u8/3f9afda335fd2616d901/3f9afda335fd2616d901.m3u8","coins":0,"vip_coins":0,"episode":1,"is_vip":2,"play_seconds":"0"}
/// shortPlayInfo : {"id":162,"short_id":11,"short_play_id":162,"name":"Madam President, Holding Multiple Positions ","description":"It is rumored outside that the prestigious president of Xu's Group is about to marry the daughter of the Xu family. Just because the name of the Xu family's daughter is Pearl, which is the same as his childhood sweetheart in ","process":2,"image_url":"https://static.wanmwl.com/eyJrZXkiOiJpbWFnZS80ZmRhZDg4OWIxNmM3MmQ2MjkwZC5qcGciLCJlZGl0cyI6eyJyZXNpemUiOnsiZml0IjoiY292ZXIifX19?sign=$23f3feca08db05081465b3677a6f671819b85d9f18ad94cc7236f3c89aada01ebe2de98adbca00c6f0c8c5b8621c5e4f6064afb617","horizontally_img":"https://static.wanmwl.com/image/ac2f87aad3384032215c.jpg","buy_type":1,"tag_type":"","all_coins":0,"collect_total":4,"watch_total":307,"episode_total":18,"search_click_total":0,"is_collect":true,"can_share_get_coin":true,"category":["Sweet Romance "]}
/// episodeList : [{"id":7132,"short_play_video_id":7132,"short_play_id":162,"short_id":11,"promise_view_ad":0,"video_url":"https://static.wanmwl.com/videom3u8/3f9afda335fd2616d901/3f9afda335fd2616d901.m3u8","coins":0,"vip_coins":0,"episode":1,"is_vip":2,"is_lock":false,"play_seconds":"0"},{"id":7140,"short_play_video_id":7140,"short_play_id":162,"short_id":11,"promise_view_ad":0,"video_url":"https://static.wanmwl.com/videom3u8/5c5860a67aab8fe3d89d/5c5860a67aab8fe3d89d.m3u8","coins":0,"vip_coins":0,"episode":2,"is_vip":2,"is_lock":false,"play_seconds":"0"},{"id":7136,"short_play_video_id":7136,"short_play_id":162,"short_id":11,"promise_view_ad":0,"video_url":"https://static.wanmwl.com/videom3u8/9f7c98f9c61fdf1bf5fc/9f7c98f9c61fdf1bf5fc.m3u8","coins":0,"vip_coins":0,"episode":3,"is_vip":2,"is_lock":false,"play_seconds":"0"},{"id":7137,"short_play_video_id":7137,"short_play_id":162,"short_id":11,"promise_view_ad":0,"video_url":"https://static.wanmwl.com/videom3u8/72c3555f90fcceb9aab4/72c3555f90fcceb9aab4.m3u8","coins":0,"vip_coins":0,"episode":4,"is_vip":2,"is_lock":false,"play_seconds":"0"},{"id":7133,"short_play_video_id":7133,"short_play_id":162,"short_id":11,"promise_view_ad":0,"video_url":"https://static.wanmwl.com/videom3u8/171bf35b0196bdf1e839/171bf35b0196bdf1e839.m3u8","coins":0,"vip_coins":0,"episode":5,"is_vip":2,"is_lock":false,"play_seconds":"0"},{"id":7135,"short_play_video_id":7135,"short_play_id":162,"short_id":11,"promise_view_ad":0,"video_url":"https://static.wanmwl.com/videom3u8/fff10f96153f53ddc7f6/fff10f96153f53ddc7f6.m3u8","coins":0,"vip_coins":0,"episode":6,"is_vip":2,"is_lock":false,"play_seconds":"0"},{"id":7134,"short_play_video_id":7134,"short_play_id":162,"short_id":11,"promise_view_ad":0,"video_url":"https://static.wanmwl.com/videom3u8/9a3ac7c5924b144b6222/9a3ac7c5924b144b6222.m3u8","coins":0,"vip_coins":0,"episode":7,"is_vip":2,"is_lock":false,"play_seconds":"0"},{"id":7139,"short_play_video_id":7139,"short_play_id":162,"short_id":11,"promise_view_ad":0,"video_url":"https://static.wanmwl.com/videom3u8/17927e0dd452d5a936d0/17927e0dd452d5a936d0.m3u8","coins":0,"vip_coins":0,"episode":8,"is_vip":2,"is_lock":false,"play_seconds":"0"},{"id":7138,"short_play_video_id":7138,"short_play_id":162,"short_id":11,"promise_view_ad":0,"video_url":"https://static.wanmwl.com/videom3u8/1d61d46d30292d0a7269/1d61d46d30292d0a7269.m3u8","coins":0,"vip_coins":0,"episode":9,"is_vip":2,"is_lock":false,"play_seconds":"0"},{"id":7141,"short_play_video_id":7141,"short_play_id":162,"short_id":11,"promise_view_ad":0,"video_url":"https://static.wanmwl.com/videom3u8/70ee4deb9bb176846534/70ee4deb9bb176846534.m3u8","coins":0,"vip_coins":0,"episode":10,"is_vip":2,"is_lock":false,"play_seconds":"0"},{"id":7146,"short_play_video_id":7146,"short_play_id":162,"short_id":11,"promise_view_ad":0,"video_url":"https://static.wanmwl.com/videom3u8/946ec0930ff6d1f9b36e/946ec0930ff6d1f9b36e.m3u8","coins":69,"vip_coins":0,"episode":11,"is_vip":2,"is_lock":true,"play_seconds":"0"},{"id":7144,"short_play_video_id":7144,"short_play_id":162,"short_id":11,"promise_view_ad":0,"video_url":"https://static.wanmwl.com/videom3u8/b92bed7933bbd5916f07/b92bed7933bbd5916f07.m3u8","coins":119,"vip_coins":0,"episode":12,"is_vip":2,"is_lock":true,"play_seconds":"0"},{"id":7142,"short_play_video_id":7142,"short_play_id":162,"short_id":11,"promise_view_ad":0,"video_url":"https://static.wanmwl.com/videom3u8/9679216680a679f7e6c2/9679216680a679f7e6c2.m3u8","coins":149,"vip_coins":0,"episode":13,"is_vip":2,"is_lock":true,"play_seconds":"0"},{"id":7143,"short_play_video_id":7143,"short_play_id":162,"short_id":11,"promise_view_ad":0,"video_url":"https://static.wanmwl.com/videom3u8/a73214d6aa17af4d639d/a73214d6aa17af4d639d.m3u8","coins":249,"vip_coins":0,"episode":14,"is_vip":2,"is_lock":true,"play_seconds":"0"},{"id":7147,"short_play_video_id":7147,"short_play_id":162,"short_id":11,"promise_view_ad":0,"video_url":"https://static.wanmwl.com/videom3u8/3a82aab4550ddc50cfe7/3a82aab4550ddc50cfe7.m3u8","coins":199,"vip_coins":0,"episode":15,"is_vip":2,"is_lock":true,"play_seconds":"0"},{"id":7149,"short_play_video_id":7149,"short_play_id":162,"short_id":11,"promise_view_ad":0,"video_url":"https://static.wanmwl.com/videom3u8/0b0781eefb331cc25fee/0b0781eefb331cc25fee.m3u8","coins":159,"vip_coins":0,"episode":16,"is_vip":2,"is_lock":true,"play_seconds":"0"},{"id":7145,"short_play_video_id":7145,"short_play_id":162,"short_id":11,"promise_view_ad":0,"video_url":"https://static.wanmwl.com/videom3u8/6c2d042f669e7bdaff3d/6c2d042f669e7bdaff3d.m3u8","coins":69,"vip_coins":0,"episode":17,"is_vip":2,"is_lock":true,"play_seconds":"0"},{"id":7148,"short_play_video_id":7148,"short_play_id":162,"short_id":11,"promise_view_ad":0,"video_url":"https://static.wanmwl.com/videom3u8/d6fd3ab315f161b9b980/d6fd3ab315f161b9b980.m3u8","coins":69,"vip_coins":0,"episode":18,"is_vip":2,"is_lock":true,"play_seconds":"0"}]
/// is_collect : true
/// show_share_coin : true
/// share_coin : 100
/// install_coins : 1000
/// revolution : 720
/// user_level : "normal"
/// unlock_video_ad_count : 1
/// discount : 80
VideoDetailBean videoDetailBeanBeanFromJson(String str) =>
VideoDetailBean.fromJson(json.decode(str));
String videoDetailBeanBeanToJson(VideoDetailBean data) =>
json.encode(data.toJson());
class VideoDetailBean {
VideoDetailBean({
int? payMode,
String? businessModel,
VideoInfo? videoInfo,
ShortPlayInfo? shortPlayInfo,
List<Episode>? episodeList,
List<int>? checkPoint,
bool? isCollect,
bool? showShareCoin,
num? shareCoin,
num? installCoins,
dynamic revolution,
String? userLevel,
num? unlockVideoAdCount,
num? discount,
}) {
_payMode = payMode;
_businessModel = businessModel;
_videoInfo = videoInfo;
_shortPlayInfo = shortPlayInfo;
_episodeList = episodeList;
_checkPoint = checkPoint;
_isCollect = isCollect;
_showShareCoin = showShareCoin;
_shareCoin = shareCoin;
_installCoins = installCoins;
_revolution = revolution;
_userLevel = userLevel;
_unlockVideoAdCount = unlockVideoAdCount;
_discount = discount;
}
VideoDetailBean.fromJson(dynamic json) {
_businessModel = json['business_model'];
_payMode = json['pay_mode'];
_videoInfo = json['video_info'] != null
? VideoInfo.fromJson(json['video_info'])
: null;
_shortPlayInfo = json['shortPlayInfo'] != null
? ShortPlayInfo.fromJson(json['shortPlayInfo'])
: null;
if (json['episodeList'] != null) {
_episodeList = [];
json['episodeList'].forEach((v) {
_episodeList?.add(Episode.fromJson(v));
});
}
_checkPoint = json['check_point'] != null
? json['check_point'].cast<int>()
: [];
_isCollect = json['is_collect'];
_showShareCoin = json['show_share_coin'];
_shareCoin = json['share_coin'];
_installCoins = json['install_coins'];
_revolution = json['revolution'];
_userLevel = json['user_level'];
_unlockVideoAdCount = json['unlock_video_ad_count'];
_discount = json['discount'];
}
int? _payMode;
String? _businessModel;
VideoInfo? _videoInfo;
ShortPlayInfo? _shortPlayInfo;
List<Episode>? _episodeList;
List<int>? _checkPoint;
bool? _isCollect;
bool? _showShareCoin;
num? _shareCoin;
num? _installCoins;
dynamic _revolution;
String? _userLevel;
num? _unlockVideoAdCount;
num? _discount;
VideoDetailBean copyWith({
int? payMode,
String? businessModel,
VideoInfo? videoInfo,
ShortPlayInfo? shortPlayInfo,
List<Episode>? episodeList,
List<int>? checkPoint,
bool? isCollect,
bool? showShareCoin,
num? shareCoin,
num? installCoins,
dynamic revolution,
String? userLevel,
num? unlockVideoAdCount,
num? discount,
}) => VideoDetailBean(
payMode: payMode ?? _payMode,
businessModel: businessModel ?? _businessModel,
videoInfo: videoInfo ?? _videoInfo,
shortPlayInfo: shortPlayInfo ?? _shortPlayInfo,
episodeList: episodeList ?? _episodeList,
checkPoint: checkPoint ?? _checkPoint,
isCollect: isCollect ?? _isCollect,
showShareCoin: showShareCoin ?? _showShareCoin,
shareCoin: shareCoin ?? _shareCoin,
installCoins: installCoins ?? _installCoins,
revolution: revolution ?? _revolution,
userLevel: userLevel ?? _userLevel,
unlockVideoAdCount: unlockVideoAdCount ?? _unlockVideoAdCount,
discount: discount ?? _discount,
);
int? get payMode => _payMode;
String? get businessModel => _businessModel;
VideoInfo? get videoInfo => _videoInfo;
ShortPlayInfo? get shortPlayInfo => _shortPlayInfo;
List<Episode>? get episodeList => _episodeList;
List<int>? get checkPoint => _checkPoint;
bool? get isCollect => _isCollect;
bool? get showShareCoin => _showShareCoin;
num? get shareCoin => _shareCoin;
num? get installCoins => _installCoins;
dynamic get revolution => _revolution;
String? get userLevel => _userLevel;
num? get unlockVideoAdCount => _unlockVideoAdCount;
num? get discount => _discount;
// VideoDetailBean setters
set payMode(int? value) => _payMode = value;
set businessModel(String? value) => _businessModel = value;
set videoInfo(VideoInfo? value) => _videoInfo = value;
set shortPlayInfo(ShortPlayInfo? value) => _shortPlayInfo = value;
set episodeList(List<Episode>? value) => _episodeList = value;
set checkPoint(List<int>? value) => _checkPoint = value;
set isCollect(bool? value) => _isCollect = value;
set showShareCoin(bool? value) => _showShareCoin = value;
set shareCoin(num? value) => _shareCoin = value;
set installCoins(num? value) => _installCoins = value;
set revolution(dynamic value) => _revolution = value;
set userLevel(String? value) => _userLevel = value;
set unlockVideoAdCount(num? value) => _unlockVideoAdCount = value;
set discount(num? value) => _discount = value;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['pay_mode'] = _payMode;
map['business_model'] = _businessModel;
if (_videoInfo != null) {
map['video_info'] = _videoInfo?.toJson();
}
if (_shortPlayInfo != null) {
map['shortPlayInfo'] = _shortPlayInfo?.toJson();
}
if (_episodeList != null) {
map['episodeList'] = _episodeList?.map((v) => v.toJson()).toList();
}
map['check_point'] = _checkPoint;
map['is_collect'] = _isCollect;
map['show_share_coin'] = _showShareCoin;
map['share_coin'] = _shareCoin;
map['install_coins'] = _installCoins;
map['revolution'] = _revolution;
map['user_level'] = _userLevel;
map['unlock_video_ad_count'] = _unlockVideoAdCount;
map['discount'] = _discount;
return map;
}
}
/// id : 7132
/// short_play_video_id : 7132
/// short_play_id : 162
/// short_id : 11
/// promise_view_ad : 0
/// video_url : "https://static.wanmwl.com/videom3u8/3f9afda335fd2616d901/3f9afda335fd2616d901.m3u8"
/// coins : 0
/// vip_coins : 0
/// episode : 1
/// is_vip : 2
/// is_lock : false
/// play_seconds : "0"
Episode episodeListFromJson(String str) => Episode.fromJson(json.decode(str));
String episodeListToJson(Episode data) => json.encode(data.toJson());
class Episode {
Episode({
num? id,
num? shortPlayVideoId,
num? shortPlayId,
num? shortId,
num? promiseViewAd,
String? videoUrl,
num? coins,
num? vipCoins,
int? episode,
num? isVip,
bool? isLock,
String? playSeconds,
}) {
_id = id;
_shortPlayVideoId = shortPlayVideoId;
_shortPlayId = shortPlayId;
_shortId = shortId;
_promiseViewAd = promiseViewAd;
_videoUrl = videoUrl;
_coins = coins;
_vipCoins = vipCoins;
_episode = episode;
_isVip = isVip;
_isLock = isLock;
_playSeconds = playSeconds;
}
Episode.fromJson(dynamic json) {
_id = json['id'];
_shortPlayVideoId = json['short_play_video_id'];
_shortPlayId = json['short_play_id'];
_shortId = json['short_id'];
_promiseViewAd = json['promise_view_ad'];
_videoUrl = json['video_url'];
_coins = json['coins'];
_vipCoins = json['vip_coins'];
_episode = json['episode'];
_isVip = json['is_vip'];
_isLock = json['is_lock'];
_playSeconds = json['play_seconds'];
}
num? _id;
num? _shortPlayVideoId;
num? _shortPlayId;
num? _shortId;
num? _promiseViewAd;
String? _videoUrl;
num? _coins;
num? _vipCoins;
int? _episode;
num? _isVip;
bool? _isLock;
String? _playSeconds;
Episode copyWith({
num? id,
num? shortPlayVideoId,
num? shortPlayId,
num? shortId,
num? promiseViewAd,
String? videoUrl,
num? coins,
num? vipCoins,
int? episode,
num? isVip,
bool? isLock,
String? playSeconds,
}) => Episode(
id: id ?? _id,
shortPlayVideoId: shortPlayVideoId ?? _shortPlayVideoId,
shortPlayId: shortPlayId ?? _shortPlayId,
shortId: shortId ?? _shortId,
promiseViewAd: promiseViewAd ?? _promiseViewAd,
videoUrl: videoUrl ?? _videoUrl,
coins: coins ?? _coins,
vipCoins: vipCoins ?? _vipCoins,
episode: episode ?? _episode,
isVip: isVip ?? _isVip,
isLock: isLock ?? _isLock,
playSeconds: playSeconds ?? _playSeconds,
);
num? get id => _id;
num? get shortPlayVideoId => _shortPlayVideoId;
num? get shortPlayId => _shortPlayId;
num? get shortId => _shortId;
num? get promiseViewAd => _promiseViewAd;
String? get videoUrl => _videoUrl;
num? get coins => _coins;
num? get vipCoins => _vipCoins;
int? get episode => _episode;
num? get isVip => _isVip;
bool? get isLock => _isLock;
String? get playSeconds => _playSeconds;
// Episode setters
set id(num? value) => _id = value;
set shortPlayVideoId(num? value) => _shortPlayVideoId = value;
set shortPlayId(num? value) => _shortPlayId = value;
set shortId(num? value) => _shortId = value;
set promiseViewAd(num? value) => _promiseViewAd = value;
set videoUrl(String? value) => _videoUrl = value;
set coins(num? value) => _coins = value;
set vipCoins(num? value) => _vipCoins = value;
set episode(int? value) => _episode = value;
set isVip(num? value) => _isVip = value;
set isLock(bool? value) => _isLock = value;
set playSeconds(String? value) => _playSeconds = value;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = _id;
map['short_play_video_id'] = _shortPlayVideoId;
map['short_play_id'] = _shortPlayId;
map['short_id'] = _shortId;
map['promise_view_ad'] = _promiseViewAd;
map['video_url'] = _videoUrl;
map['coins'] = _coins;
map['vip_coins'] = _vipCoins;
map['episode'] = _episode;
map['is_vip'] = _isVip;
map['is_lock'] = _isLock;
map['play_seconds'] = _playSeconds;
return map;
}
}
/// id : 162
/// short_id : 11
/// short_play_id : 162
/// name : "Madam President, Holding Multiple Positions "
/// description : "It is rumored outside that the prestigious president of Xu's Group is about to marry the daughter of the Xu family. Just because the name of the Xu family's daughter is Pearl, which is the same as his childhood sweetheart in "
/// process : 2
/// image_url : "https://static.wanmwl.com/eyJrZXkiOiJpbWFnZS80ZmRhZDg4OWIxNmM3MmQ2MjkwZC5qcGciLCJlZGl0cyI6eyJyZXNpemUiOnsiZml0IjoiY292ZXIifX19?sign=$23f3feca08db05081465b3677a6f671819b85d9f18ad94cc7236f3c89aada01ebe2de98adbca00c6f0c8c5b8621c5e4f6064afb617"
/// horizontally_img : "https://static.wanmwl.com/image/ac2f87aad3384032215c.jpg"
/// buy_type : 1
/// tag_type : ""
/// all_coins : 0
/// collect_total : 4
/// watch_total : 307
/// episode_total : 18
/// search_click_total : 0
/// is_collect : true
/// can_share_get_coin : true
/// category : ["Sweet Romance "]
ShortPlayInfo shortPlayInfoFromJson(String str) =>
ShortPlayInfo.fromJson(json.decode(str));
String shortPlayInfoToJson(ShortPlayInfo data) => json.encode(data.toJson());
class ShortPlayInfo {
ShortPlayInfo({
num? id,
num? shortId,
num? shortPlayId,
String? name,
String? description,
num? process,
String? imageUrl,
String? horizontallyImg,
num? buyType,
String? tagType,
num? allCoins,
num? collectTotal,
num? watchTotal,
num? episodeTotal,
num? searchClickTotal,
bool? isCollect,
bool? canShareGetCoin,
List<String>? category,
}) {
_id = id;
_shortId = shortId;
_shortPlayId = shortPlayId;
_name = name;
_description = description;
_process = process;
_imageUrl = imageUrl;
_horizontallyImg = horizontallyImg;
_buyType = buyType;
_tagType = tagType;
_allCoins = allCoins;
_collectTotal = collectTotal;
_watchTotal = watchTotal;
_episodeTotal = episodeTotal;
_searchClickTotal = searchClickTotal;
_isCollect = isCollect;
_canShareGetCoin = canShareGetCoin;
_category = category;
}
ShortPlayInfo.fromJson(dynamic json) {
_id = json['id'];
_shortId = json['short_id'];
_shortPlayId = json['short_play_id'];
_name = json['name'];
_description = json['description'];
_process = json['process'];
_imageUrl = json['image_url'];
_horizontallyImg = json['horizontally_img'];
_buyType = json['buy_type'];
_tagType = json['tag_type'];
_allCoins = json['all_coins'];
_collectTotal = json['collect_total'];
_watchTotal = json['watch_total'];
_episodeTotal = json['episode_total'];
_searchClickTotal = json['search_click_total'];
_isCollect = json['is_collect'];
_canShareGetCoin = json['can_share_get_coin'];
_category = json['category'] != null ? json['category'].cast<String>() : [];
}
num? _id;
num? _shortId;
num? _shortPlayId;
String? _name;
String? _description;
num? _process;
String? _imageUrl;
String? _horizontallyImg;
num? _buyType;
String? _tagType;
num? _allCoins;
num? _collectTotal;
num? _watchTotal;
num? _episodeTotal;
num? _searchClickTotal;
bool? _isCollect;
bool? _canShareGetCoin;
List<String>? _category;
ShortPlayInfo copyWith({
num? id,
num? shortId,
num? shortPlayId,
String? name,
String? description,
num? process,
String? imageUrl,
String? horizontallyImg,
num? buyType,
String? tagType,
num? allCoins,
num? collectTotal,
num? watchTotal,
num? episodeTotal,
num? searchClickTotal,
bool? isCollect,
bool? canShareGetCoin,
List<String>? category,
}) => ShortPlayInfo(
id: id ?? _id,
shortId: shortId ?? _shortId,
shortPlayId: shortPlayId ?? _shortPlayId,
name: name ?? _name,
description: description ?? _description,
process: process ?? _process,
imageUrl: imageUrl ?? _imageUrl,
horizontallyImg: horizontallyImg ?? _horizontallyImg,
buyType: buyType ?? _buyType,
tagType: tagType ?? _tagType,
allCoins: allCoins ?? _allCoins,
collectTotal: collectTotal ?? _collectTotal,
watchTotal: watchTotal ?? _watchTotal,
episodeTotal: episodeTotal ?? _episodeTotal,
searchClickTotal: searchClickTotal ?? _searchClickTotal,
isCollect: isCollect ?? _isCollect,
canShareGetCoin: canShareGetCoin ?? _canShareGetCoin,
category: category ?? _category,
);
num? get id => _id;
num? get shortId => _shortId;
num? get shortPlayId => _shortPlayId;
String? get name => _name;
String? get description => _description;
num? get process => _process;
String? get imageUrl => _imageUrl;
String? get horizontallyImg => _horizontallyImg;
num? get buyType => _buyType;
String? get tagType => _tagType;
num? get allCoins => _allCoins;
num? get collectTotal => _collectTotal;
num? get watchTotal => _watchTotal;
num? get episodeTotal => _episodeTotal;
num? get searchClickTotal => _searchClickTotal;
bool? get isCollect => _isCollect;
bool? get canShareGetCoin => _canShareGetCoin;
List<String>? get category => _category;
// ShortPlayInfo setters
set id(num? value) => _id = value;
set shortId(num? value) => _shortId = value;
set shortPlayId(num? value) => _shortPlayId = value;
set name(String? value) => _name = value;
set description(String? value) => _description = value;
set process(num? value) => _process = value;
set imageUrl(String? value) => _imageUrl = value;
set horizontallyImg(String? value) => _horizontallyImg = value;
set buyType(num? value) => _buyType = value;
set tagType(String? value) => _tagType = value;
set allCoins(num? value) => _allCoins = value;
set collectTotal(num? value) => _collectTotal = value;
set watchTotal(num? value) => _watchTotal = value;
set episodeTotal(num? value) => _episodeTotal = value;
set searchClickTotal(num? value) => _searchClickTotal = value;
set isCollect(bool? value) => _isCollect = value;
set canShareGetCoin(bool? value) => _canShareGetCoin = value;
set category(List<String>? value) => _category = value;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = _id;
map['short_id'] = _shortId;
map['short_play_id'] = _shortPlayId;
map['name'] = _name;
map['description'] = _description;
map['process'] = _process;
map['image_url'] = _imageUrl;
map['horizontally_img'] = _horizontallyImg;
map['buy_type'] = _buyType;
map['tag_type'] = _tagType;
map['all_coins'] = _allCoins;
map['collect_total'] = _collectTotal;
map['watch_total'] = _watchTotal;
map['episode_total'] = _episodeTotal;
map['search_click_total'] = _searchClickTotal;
map['is_collect'] = _isCollect;
map['can_share_get_coin'] = _canShareGetCoin;
map['category'] = _category;
return map;
}
}
/// id : 7132
/// short_play_video_id : 7132
/// short_play_id : 162
/// short_id : 11
/// promise_view_ad : 0
/// video_url : "https://static.wanmwl.com/videom3u8/3f9afda335fd2616d901/3f9afda335fd2616d901.m3u8"
/// coins : 0
/// vip_coins : 0
/// episode : 1
/// is_vip : 2
/// play_seconds : "0"
VideoInfo videoInfoFromJson(String str) => VideoInfo.fromJson(json.decode(str));
String videoInfoToJson(VideoInfo data) => json.encode(data.toJson());
class VideoInfo {
VideoInfo({
num? id,
num? shortPlayVideoId,
num? shortPlayId,
num? shortId,
num? promiseViewAd,
String? videoUrl,
num? coins,
num? vipCoins,
int? episode,
num? isVip,
String? playSeconds,
}) {
_id = id;
_shortPlayVideoId = shortPlayVideoId;
_shortPlayId = shortPlayId;
_shortId = shortId;
_promiseViewAd = promiseViewAd;
_videoUrl = videoUrl;
_coins = coins;
_vipCoins = vipCoins;
_episode = episode;
_isVip = isVip;
_playSeconds = playSeconds;
}
VideoInfo.fromJson(dynamic json) {
_id = json['id'];
_shortPlayVideoId = json['short_play_video_id'];
_shortPlayId = json['short_play_id'];
_shortId = json['short_id'];
_promiseViewAd = json['promise_view_ad'];
_videoUrl = json['video_url'];
_coins = json['coins'];
_vipCoins = json['vip_coins'];
_episode = json['episode'];
_isVip = json['is_vip'];
_playSeconds = json['play_seconds'];
}
num? _id;
num? _shortPlayVideoId;
num? _shortPlayId;
num? _shortId;
num? _promiseViewAd;
String? _videoUrl;
num? _coins;
num? _vipCoins;
int? _episode;
num? _isVip;
String? _playSeconds;
VideoInfo copyWith({
num? id,
num? shortPlayVideoId,
num? shortPlayId,
num? shortId,
num? promiseViewAd,
String? videoUrl,
num? coins,
num? vipCoins,
int? episode,
num? isVip,
String? playSeconds,
}) => VideoInfo(
id: id ?? _id,
shortPlayVideoId: shortPlayVideoId ?? _shortPlayVideoId,
shortPlayId: shortPlayId ?? _shortPlayId,
shortId: shortId ?? _shortId,
promiseViewAd: promiseViewAd ?? _promiseViewAd,
videoUrl: videoUrl ?? _videoUrl,
coins: coins ?? _coins,
vipCoins: vipCoins ?? _vipCoins,
episode: episode ?? _episode,
isVip: isVip ?? _isVip,
playSeconds: playSeconds ?? _playSeconds,
);
num? get id => _id;
num? get shortPlayVideoId => _shortPlayVideoId;
num? get shortPlayId => _shortPlayId;
num? get shortId => _shortId;
num? get promiseViewAd => _promiseViewAd;
String? get videoUrl => _videoUrl;
num? get coins => _coins;
num? get vipCoins => _vipCoins;
int? get episode => _episode;
num? get isVip => _isVip;
String? get playSeconds => _playSeconds;
// VideoInfo setters
set id(num? value) => _id = value;
set shortPlayVideoId(num? value) => _shortPlayVideoId = value;
set shortPlayId(num? value) => _shortPlayId = value;
set shortId(num? value) => _shortId = value;
set promiseViewAd(num? value) => _promiseViewAd = value;
set videoUrl(String? value) => _videoUrl = value;
set coins(num? value) => _coins = value;
set vipCoins(num? value) => _vipCoins = value;
set episode(int? value) => _episode = value;
set isVip(num? value) => _isVip = value;
set playSeconds(String? value) => _playSeconds = value;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = _id;
map['short_play_video_id'] = _shortPlayVideoId;
map['short_play_id'] = _shortPlayId;
map['short_id'] = _shortId;
map['promise_view_ad'] = _promiseViewAd;
map['video_url'] = _videoUrl;
map['coins'] = _coins;
map['vip_coins'] = _vipCoins;
map['episode'] = _episode;
map['is_vip'] = _isVip;
map['play_seconds'] = _playSeconds;
return map;
}
}

View File

@ -0,0 +1,240 @@
import 'package:flutter_kinetra/kt_pages/kt_explore/state.dart';
import 'package:get/get.dart';
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
import '../../dio_cilent/kt_apis.dart';
import '../../dio_cilent/kt_request.dart';
import '../../kt_model/kt_short_video_bean.dart';
import '../../kt_widgets/kt_status_widget.dart';
class KtExploreLogic extends GetxController {
final state = KtExploreState();
final EasyRefreshController refreshCtrl = EasyRefreshController(
controlFinishLoad: true,
);
final PageController pageController = PageController(viewportFraction: .95);
final int preloadRange = 1;
bool isRefresh = false;
@override
void onReady() {
super.onReady();
fetchDiscoverList();
}
@override
void onClose() {
super.onClose();
clearController();
refreshCtrl.dispose();
pageController.dispose();
}
clearController() {
for (var item in state.controllers) {
item?.pause();
item?.dispose();
}
state.controllers.clear();
}
fetchDiscoverList({bool refresh = false}) async {
state.loadStatus = KtLoadStatusType.loading;
if (refresh) {
isRefresh = true;
state.curIndex = 1;
state.videoList.clear();
clearController();
}
if (refresh) refreshCtrl.finishLoad();
try {
ApiResponse res = await KtHttpClient().request(
KtApis.getRecommands,
queryParameters: {'current_page': state.curIndex, 'page_size': 20},
method: HttpMethod.get,
);
if (refresh) {
isRefresh = false;
update();
}
if (res.success) {
state.loadStatus = KtLoadStatusType.loadSuccess;
List<KtShortVideoBean> list = [
...res.data['list'].map((item) => KtShortVideoBean.fromJson(item)),
];
state.videoList.addAll(list);
// if (videoId == -1) {
// video = state.videoList.first;
// videoId = state.videoList.first.id ?? -1;
// }
state.controllers = List<VideoPlayerController?>.filled(
state.videoList.length,
null,
growable: true,
);
if (state.videoList.isNotEmpty && state.curIndex == 1) {
state.currentPage = 0;
_initializeController(0);
}
update();
} else {
if (refresh) refreshCtrl.finishRefresh(IndicatorResult.fail);
state.loadStatus = KtLoadStatusType.loadFailed;
}
} catch (e) {
if (refresh) {
isRefresh = false;
update();
}
state.loadStatus = KtLoadStatusType.loadFailed;
}
update();
}
//
Future<void> onPageChanged(int index, {bool isToggle = false}) async {
if (index < 0 || index >= state.videoList.length) return;
//
if (state.controllers[state.currentPage]?.value.isPlaying ?? false) {
await state.controllers[state.currentPage]?.pause();
}
state.currentPage = index;
if (isToggle) {
// loadStatusType = LoadStatusType.loading;
// update();
await _initializeController(index);
// loadStatusType = LoadStatusType.loadSuccess;
update();
WidgetsBinding.instance.addPostFrameCallback((_) {
pageController.jumpToPage(index);
});
}
if (state.controllers[index] != null) {
state.controllers[index]?.play();
} else {
if (!isToggle) await _initializeController(index);
state.controllers[index]?.play();
}
//
_preloadAdjacentVideos();
update();
// });
}
//
void _releaseUnusedControllers() {
for (int i = 0; i < state.controllers.length; i++) {
if (i < state.currentPage - 1 || i > state.currentPage + 1) {
state.controllers[i]?.dispose();
state.controllers[i] = null;
}
}
}
//
Future<void> _initializeController(int index) async {
if (index < 0 || index >= state.videoList.length) return;
if (state.controllers[index] != null) return;
final episode = state.videoList[index];
final controller = VideoPlayerController.networkUrl(
Uri.parse(episode.videoInfo!.videoUrl!),
formatHint: VideoFormat.hls,
);
state.controllers[index] = controller;
try {
await controller.initialize();
if (index == state.currentPage) {
controller.play();
update();
}
controller.addListener(() {
if (state.currentPage == index) update();
if (controller.value.isCompleted && !controller.value.isBuffering) {
onPageChanged(index + 1, isToggle: true);
}
});
} catch (e) {
//
// UserUtil().reportErrorEvent(
// 'video initialize failed',
// UserUtil.videoError,
// errMsg: e.toString(),
// payData: episode.toJson(),
// shortPlayId: episode.shortPlayId ?? 0,
// shortPlayVideoId: episode.shortPlayVideoId ?? 0,
// );
debugPrint('---err:$e');
}
}
//
void _preloadAdjacentVideos() {
if (state.currentPage > 0) _initializeController(state.currentPage - 1);
if (state.currentPage < state.videoList.length - 1) {
_initializeController(state.currentPage + 1);
}
_releaseUnusedControllers();
}
likeVideo(KtShortVideoBean video) {
if (video.isCollect ?? false) {
video.collectTotal = video.collectTotal! - 1;
if (video.collectTotal! < 0) {
video.collectTotal = 0;
}
KtHttpClient().request(
KtApis.deleteFavoriteVideo,
data: {
"short_play_id": video.shortPlayId,
"video_id": video.videoInfo!.id,
},
);
} else {
video.collectTotal = video.collectTotal! + 1;
KtHttpClient().request(
KtApis.collectVideo,
data: {
"short_play_id": video.shortPlayId,
"video_id": video.videoInfo!.id,
},
);
}
video.isCollect = !(video.isCollect ?? false);
update();
}
KtShortVideoBean get curVideo => state.videoList[state.currentPage];
setCollectVideo(int shortPlayId, {bool isCollect = true}) {
KtShortVideoBean? video = state.videoList.firstWhereOrNull(
(item) => item.shortPlayId == shortPlayId,
);
if (video == null) return;
video.isCollect = isCollect;
video.collectTotal = video.collectTotal! + (isCollect ? 1 : -1);
update();
}
//
// Future<void> onRefresh() async {
// state.currentPage = 0;
// state.curIndex = 1;
// // clearController();
// // update();
// WidgetsBinding.instance.addPostFrameCallback((_) async {
// await fetchDiscoverList(refresh: true);
// });
// }
}

View File

@ -0,0 +1,13 @@
import 'package:flutter_kinetra/kt_model/kt_short_video_bean.dart';
import 'package:video_player/video_player.dart';
import '../../kt_widgets/kt_status_widget.dart';
class KtExploreState {
int curIndex = 1;
List<KtShortVideoBean> videoList = [];
List<VideoPlayerController?> controllers = [];
int currentPage = 0;
KtLoadStatusType loadStatus = KtLoadStatusType.loading;
}

View File

@ -1,4 +1,16 @@
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:flutter_kinetra/kt_utils/kt_string_extend.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:video_player/video_player.dart';
import 'package:visibility_detector/visibility_detector.dart';
import '../../kt_model/kt_short_video_bean.dart';
import '../../kt_widgets/kt_network_image.dart';
import '../../kt_widgets/kt_status_widget.dart';
import '../kt_routes.dart';
import 'logic.dart';
class KtExplorePage extends StatefulWidget {
const KtExplorePage({super.key});
@ -7,9 +19,256 @@ class KtExplorePage extends StatefulWidget {
State<KtExplorePage> createState() => _KtExplorePageState();
}
class _KtExplorePageState extends State<KtExplorePage> {
class _KtExplorePageState extends State<KtExplorePage>
with AutomaticKeepAliveClientMixin {
final logic = Get.put(KtExploreLogic());
final state = Get.find<KtExploreLogic>().state;
@override
Widget build(BuildContext context) {
return const Scaffold();
super.build(context);
return GetBuilder<KtExploreLogic>(
builder: (ctrl) {
if (state.loadStatus == KtLoadStatusType.loadNoData ||
state.loadStatus == KtLoadStatusType.loadFailed) {
return KtStatusWidget(
type: KtErrorStatusType.nothingYet,
onPressed: logic.fetchDiscoverList,
);
}
return Container(
width: ScreenUtil().screenWidth,
height: ScreenUtil().screenHeight,
padding: EdgeInsets.only(
top: ScreenUtil().statusBarHeight,
left: 18.w,
right: 18.w,
),
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('explore_bg.png'.ktIcon),
fit: BoxFit.fill,
),
),
child: Stack(
clipBehavior: Clip.none,
children: [
Column(
children: [
SizedBox(height: 40.w),
Expanded(
child: EasyRefresh(
controller: logic.refreshCtrl,
header: MaterialHeader(),
footer: MaterialFooter(),
onRefresh: () => logic.fetchDiscoverList(refresh: true),
child: PageView.builder(
scrollDirection: Axis.vertical,
itemCount: state.videoList.length,
controller: logic.pageController,
onPageChanged: (index) => logic.onPageChanged(index),
itemBuilder: (_, index) =>
videoPlayerItem(index: index),
),
),
),
],
),
Row(
children: [
Stack(
clipBehavior: Clip.none,
children: [
Image.asset('ic_explore_ip.png'.ktIcon, width: 56.8.w),
Positioned(
top: 0.w,
right: -2.w,
child: Image.asset(
'ic_explore_tv.png'.ktIcon,
width: 19.w,
),
),
],
),
SizedBox(width: 12.w),
Text(
"Unlock Your Next Binge\nObsession",
style: TextStyle(
fontSize: 18.sp,
color: Colors.black,
fontStyle: FontStyle.italic,
fontWeight: FontWeight.w800,
),
),
],
),
],
),
);
},
);
}
Widget videoPlayerItem({int index = 0}) {
return GetBuilder<KtExploreLogic>(
builder: (ctrl) {
if (state.controllers.isEmpty) return Container();
final VideoPlayerController? videoPlayerController =
state.controllers[index];
if (videoPlayerController == null ||
!videoPlayerController.value.isInitialized) {
return Stack(
children: [
KtNetworkImage(
imageUrl: state.videoList[index].imageUrl!,
width: ScreenUtil().screenWidth - 36.w,
height: 540.w,
fit: BoxFit.cover,
borderRadius: BorderRadius.circular(16.w),
),
Center(child: CircularProgressIndicator()),
],
);
}
KtShortVideoBean item = state.videoList[index];
return Container(
margin: EdgeInsets.only(bottom: 18.w),
alignment: Alignment.topCenter,
child: GestureDetector(
onTap: () {
videoPlayerController.value.isPlaying
? videoPlayerController.pause()
: videoPlayerController.play();
setState(() {});
},
child: Stack(
alignment: Alignment.bottomCenter,
children: [
Positioned.fill(
child: VisibilityDetector(
key: ValueKey(
'discover-video-${state.videoList[index].id}',
),
onVisibilityChanged: (info) {
if (info.visibleFraction > 0.85 &&
state.currentPage == index &&
!videoPlayerController.value.isPlaying) {
videoPlayerController.play();
logic.update();
}
if (info.visibleFraction < 0.09 &&
videoPlayerController.value.isPlaying) {
videoPlayerController.pause();
logic.update();
}
},
child: ClipRRect(
borderRadius: BorderRadius.circular(16.w),
child: SizedBox(
width: ScreenUtil().screenWidth - 36.w,
height: 540.w,
child: VideoPlayer(videoPlayerController),
),
),
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
width: ScreenUtil().screenWidth - 56.w,
margin: EdgeInsets.only(left: 12.w),
child: Text(
item.name ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16.sp,
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
),
SizedBox(height: 11.w),
GestureDetector(
onTap: () {
Get.toNamed(
KtRoutes.shortVideo,
arguments: {
'shortPlayId': item.shortPlayId,
'imageUrl': item.imageUrl,
'isFromDiscover': true,
},
)?.then((v) {
videoPlayerController.play();
setState(() {});
});
},
child: Container(
width: ScreenUtil().screenWidth - 60.w,
margin: EdgeInsets.symmetric(horizontal: 12.w),
padding: EdgeInsets.symmetric(
horizontal: 12.w,
vertical: 9.w,
),
decoration: BoxDecoration(
color: Color(0xFF1E1E20).withValues(alpha: .3),
borderRadius: BorderRadius.circular(100),
),
child: Row(
children: [
Image.asset('ic_ep.png'.ktIcon, width: 16.w),
SizedBox(width: 6.w),
Text(
'Watch the complete series',
style: TextStyle(
fontSize: 12.sp,
color: Colors.white,
fontWeight: FontWeight.w400,
),
),
const Spacer(),
Image.asset(
'ic_right_white.png'.ktIcon,
width: 10.w,
),
],
),
),
),
SizedBox(height: 16.w),
// if (state.controllers[state.currentPage] != null)
// DrVideoProgressBar(
// controller: state.controllers[state.currentPage]!,
// width: ScreenUtil().screenWidth,
// ),
],
),
if (!videoPlayerController.value.isPlaying)
Positioned(
top: 240.h,
child: GestureDetector(
onTap: () {
videoPlayerController.value.isPlaying
? videoPlayerController.pause()
: videoPlayerController.play();
setState(() {});
},
child: Image.asset('ic_play.png'.ktIcon, width: 62.w),
),
),
],
),
),
);
},
);
}
@override
bool get wantKeepAlive => true;
}

View File

@ -0,0 +1,274 @@
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'package:flustars/flustars.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_kinetra/kt_pages/kt_routes.dart';
import 'package:flutter_kinetra/kt_utils/kt_string_extend.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart' hide ScreenUtil;
import 'package:get/get.dart';
import '../../../dio_cilent/kt_apis.dart';
import '../../../kt_utils/kt_keys.dart';
import '../../../kt_utils/kt_utils.dart';
import '../../../main.dart';
import '../../kt_widgets/kt_status_widget.dart';
class KtSearchPage extends StatefulWidget {
const KtSearchPage({super.key});
@override
SignInActivityPageState createState() => SignInActivityPageState();
}
class SignInActivityPageState extends State<KtSearchPage> with RouteAware {
InAppWebViewController? _webViewController;
late PullToRefreshController _webRefreshController;
late Map<String, String> _userData;
KtLoadStatusType loadingStatus = KtLoadStatusType.loading;
@override
void initState() {
super.initState();
_initUserData();
_initRefreshController();
}
void _initUserData() {
_userData = {
'time_zone': KtUtils.getTimeZoneOffset(DateTime.now()),
'type': Platform.isAndroid ? 'android' : 'ios',
'lang': 'en',
// 'theme': 'theme_7',
'token': SpUtil.getString(KtKeys.token) ?? '',
};
debugPrint('-----userData:$_userData');
}
_initRefreshController() {
_webRefreshController = PullToRefreshController(
settings: PullToRefreshSettings(enabled: true),
onRefresh: () async {
if (Platform.isAndroid) {
_webViewController?.reload();
} else if (Platform.isIOS) {
_webViewController?.loadUrl(
urlRequest: URLRequest(url: WebUri(KtApis.WEB_SITE_SEARCH)),
);
}
},
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
routeObserver.subscribe(this, ModalRoute.of(context)! as PageRoute);
}
//
@override
void didPopNext() {
super.didPopNext();
_webViewController?.reload();
}
@override
void dispose() {
super.dispose();
_webViewController?.dispose();
}
//
void _handleWebMessage(String jsonS) async {
if (jsonS.isEmpty) return;
Map<String, dynamic>? webParams;
webParams = jsonDecode(jsonS);
if (webParams == null) {
debugPrint("没有获取到传递过来的参数");
return;
}
Get.toNamed(
KtRoutes.shortVideo,
arguments: {'shortPlayId': webParams['short_play_id']},
);
}
@override
Widget build(BuildContext context) {
return Container(
width: ScreenUtil().screenWidth,
height: ScreenUtil().screenHeight,
padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight + 30.w),
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('bg2.png'.ktIcon),
fit: BoxFit.fill,
),
),
child: Column(
children: [
_buildAppBar(context),
Expanded(
child: Stack(
children: [
InAppWebView(
pullToRefreshController: _webRefreshController,
initialSettings: InAppWebViewSettings(
cacheEnabled: false,
javaScriptEnabled: true,
alwaysBounceVertical: true,
allowsBackForwardNavigationGestures: true,
domStorageEnabled: false,
clearCache: true,
transparentBackground: true,
useShouldOverrideUrlLoading: false,
),
// handler
initialUserScripts: UnmodifiableListView<UserScript>([
UserScript(
source: """
window.AndroidInterface = {
getUserInfo: function() {
return window.flutter_inappwebview.callHandler('getUserInfo');
},
js2app: function(jsonS) {
return window.flutter_inappwebview.callHandler('js2app',jsonS);
}
};
""",
injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START,
),
]),
onWebViewCreated: (controller) async {
_webViewController = controller;
_webViewController?.addJavaScriptHandler(
handlerName: 'getUserInfo',
callback: (_) => jsonEncode(_userData),
);
await _webViewController?.addWebMessageListener(
WebMessageListener(
jsObjectName: "openDetail",
allowedOriginRules: {"*"},
onPostMessage:
(message, sourceOrigin, isMainFrame, replyProxy) {
print('----callback--jsonS:$message');
if (message?.data != null) {
_handleWebMessage(message?.data);
}
},
),
);
await _webViewController?.loadUrl(
urlRequest: URLRequest(
url: WebUri(KtApis.WEB_SITE_SEARCH),
),
);
},
onLoadStart: (controller, url) {
setState(() {
loadingStatus = KtLoadStatusType.loading;
});
},
onLoadStop: (controller, url) async {
// await _webViewController?.evaluateJavascript(
// source: '''
// document.body.style.backgroundColor = "transparent"
// document.style.backgroundColor = "transparent"
// ''',
// );
if (Platform.isIOS) {
String userJsonStr = jsonEncode(_userData);
Future.delayed(const Duration(seconds: 1)).then((_) {
controller.evaluateJavascript(
source:
'''
if(typeof window.receiveDataFromNative === 'function') {
window.receiveDataFromNative($userJsonStr);
}
''',
);
});
} else if (Platform.isAndroid) {
await controller.evaluateJavascript(
source: """
window.AndroidInterface = {
getUserInfo: async function () {
return window.flutter_inappwebview.callHandler('getUserInfo');
},
};
""",
);
}
setState(() {
loadingStatus = KtLoadStatusType.loadSuccess;
});
_webRefreshController.endRefreshing();
},
onReceivedError: (controller, request, error) {
_webRefreshController.endRefreshing();
Future.delayed(const Duration(milliseconds: 100)).then((_) {
setState(() {
loadingStatus = KtLoadStatusType.loadFailed;
});
});
},
),
_buildWidget(),
],
),
),
],
),
);
}
Widget _buildAppBar(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: Image.asset('ic_back.png'.ktIcon, width: 10.w),
onPressed: () => Navigator.of(context).maybePop(),
),
Text(
'Search',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w500,
color: Colors.black,
),
),
Container(width: 24.w),
],
);
}
Widget _buildWidget() {
print('----loadStatus:$loadingStatus');
switch (loadingStatus) {
case KtLoadStatusType.loading:
return Center(child: CircularProgressIndicator());
case KtLoadStatusType.loadFailed:
return KtStatusWidget(
type: KtErrorStatusType.noNetwork,
onPressed: () {
_webViewController?.loadUrl(
urlRequest: URLRequest(url: WebUri(KtApis.WEB_SITE_SEARCH)),
);
},
);
case KtLoadStatusType.loadNoData:
return KtStatusWidget(
type: KtErrorStatusType.notFound,
onPressed: () {
_webViewController?.reload();
},
);
default:
return const SizedBox.shrink();
}
}
}

View File

@ -1,6 +1,7 @@
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter_kinetra/kt_model/kt_home_category_bean.dart';
import 'package:flutter_kinetra/kt_pages/kt_home/state.dart';
import 'package:get/get.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import '../../dio_cilent/kt_apis.dart';
import '../../dio_cilent/kt_request.dart';
@ -10,21 +11,39 @@ import '../../kt_widgets/kt_status_widget.dart';
class KtHomeLogic extends GetxController {
final state = KtHomeState();
final EasyRefreshController easyRefreshController = EasyRefreshController(
controlFinishRefresh: true,
controlFinishLoad: true,
);
@override
void onReady() {
// TODO: implement onReady
super.onReady();
getHomeInfo();
refreshData();
}
getHomeInfo({RefreshController? refreshCtrl}) async {
refreshData() {
getHomeInfo();
getMostSearchList();
}
loadMoreData() {
if (state.selDiscover == 0) {
easyRefreshController.finishLoad(IndicatorResult.noMore);
return;
}
state.pageIndex++;
getCategoryVideoList();
}
getHomeInfo() async {
state.loadStatus = KtLoadStatusType.loading;
try {
ApiResponse res = await HttpClient().request(
ApiResponse res = await KtHttpClient().request(
KtApis.homeAllModules,
method: HttpMethod.get,
);
refreshCtrl?.refreshCompleted();
easyRefreshController.finishRefresh();
if (res.success) {
state.loadStatus = KtLoadStatusType.loadSuccess;
@ -57,7 +76,7 @@ class KtHomeLogic extends GetxController {
.toList(),
];
} else if (item['module_key'] == 'week_highest_recommend') {
state.trendingList = [
state.hotList = [
...item['data']
.map(
(item) =>
@ -65,13 +84,24 @@ class KtHomeLogic extends GetxController {
)
.toList(),
];
int halfLength = state.trendingList.length ~/ 2;
state.trendingTopList = state.trendingList.sublist(0, halfLength);
state.trendingBottomList = state.trendingList.sublist(halfLength);
} else if (item['module_key'] == 'category_navigation') {
state.categoryList = [
...item['data']
.map(
(item) => KtHomeCategoryBean.fromJson(
item as Map<String, dynamic>,
),
)
.toList(),
];
if (state.selCategoryId == -1) {
state.selCategoryId = state.categoryList.first.categoryId ?? -1;
}
getCategoryVideoList(isRefresh: true);
}
});
if (state.trendingList.isEmpty &&
if (state.hotList.isEmpty &&
state.bannerList.isEmpty &&
state.arrivalList.isEmpty) {
state.loadStatus = KtLoadStatusType.loadNoData;
@ -85,4 +115,57 @@ class KtHomeLogic extends GetxController {
update();
}
}
getMostSearchList() async {
ApiResponse res = await KtHttpClient().request(
KtApis.searchHot,
method: HttpMethod.get,
);
if (res.success) {
state.mostSearchedList = [
...res.data['list'].map((item) => KtShortVideoBean.fromJson(item)),
];
if (state.mostSearchedList.length > 3) {
state.mostSearchedList = state.mostSearchedList.sublist(0, 3);
}
update(['trend']);
}
}
getCategoryVideoList({bool isRefresh = false}) async {
if (isRefresh) {
state.pageIndex = 1;
state.categoryVideoList.clear();
update();
}
Map<String, dynamic> params = {
'current_page': state.pageIndex,
'page_size': 20,
};
if (state.selCategoryId != -1) {
params.putIfAbsent('category_id', () => state.selCategoryId);
}
ApiResponse res = await KtHttpClient().request(
KtApis.homeVideoList,
method: HttpMethod.get,
queryParameters: params,
);
easyRefreshController.finishRefresh();
easyRefreshController.finishLoad();
if (res.success) {
List<KtShortVideoBean> list = [
...res.data['list']
.map(
(item) => KtShortVideoBean.fromJson(item as Map<String, dynamic>),
)
.toList(),
];
if (list.length < 20) {
easyRefreshController.finishLoad(IndicatorResult.noMore);
}
state.categoryVideoList.addAll(list);
update();
}
}
}

View File

@ -1,14 +1,23 @@
import '../../kt_model/kt_home_category_bean.dart';
import '../../kt_model/kt_short_video_bean.dart';
import '../../kt_widgets/kt_status_widget.dart';
class KtHomeState {
KtLoadStatusType loadStatus = KtLoadStatusType.loading;
List<KtShortVideoBean> topPickList = [];
List<KtShortVideoBean> trendingList = [];
List<KtShortVideoBean> trendingTopList = [];
List<KtShortVideoBean> trendingBottomList = [];
List<KtShortVideoBean> hotList = [];
List<KtShortVideoBean> arrivalList = [];
List<KtShortVideoBean> bannerList = [];
List<KtShortVideoBean> mostSearchedList = [];
List<KtHomeCategoryBean> categoryList = [];
List<KtShortVideoBean> categoryVideoList = [];
num selCategoryId = -1;
int pageIndex = 1;
int selDiscover = 0;
String selType = 'Hot & Rising';
List typeList = ['Hot & Rising', 'Top Charts', 'Fresh Drops'];
bool showVideo = true;
bool hasSubCoin = false;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,161 @@
import 'package:flutter/material.dart';
import 'package:flutter_kinetra/kt_utils/kt_string_extend.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../dio_cilent/kt_apis.dart';
import '../../kt_utils/kt_device_info_utils.dart';
import '../kt_webview_page.dart';
class KtAboutUsPage extends StatefulWidget {
const KtAboutUsPage({super.key});
@override
State<KtAboutUsPage> createState() => _AboutUsPageState();
}
class _AboutUsPageState extends State<KtAboutUsPage> {
String versionNum = '';
String appName = 'Kinetra';
final List serviceList = [
{'title': 'Privacy Policy', 'url': KtApis.WEB_SITE_PRIVATE},
{'title': 'User Agreement', 'url': KtApis.WEB_SITE_POLICY},
{'title': 'About Us', 'url': KtApis.WEB_SITE_INDEX},
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('bg2.png'.ktIcon),
fit: BoxFit.fill,
),
),
width: ScreenUtil().screenWidth,
height: ScreenUtil().screenHeight,
child: SafeArea(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: Image.asset('ic_back.png'.ktIcon, width: 10.w),
onPressed: () => Navigator.of(context).maybePop(),
),
Text(
'About',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w700,
color: Colors.black,
),
),
Container(width: 24.w),
],
),
SizedBox(height: 40.w),
ClipRRect(
borderRadius: BorderRadius.circular(20.r),
child: Image.asset(
'ic_logo.png'.ktIcon,
width: 84.w,
fit: BoxFit.cover,
),
),
SizedBox(height: 7.w),
Text(
appName,
style: TextStyle(
fontSize: 18.sp,
color: Colors.black,
fontWeight: FontWeight.w700,
),
),
Text(
'Version: $versionNum',
style: TextStyle(fontSize: 12.sp, color: Colors.black),
),
SizedBox(height: 40.w),
...serviceList.map(
(item) => Column(
children: [
GestureDetector(
onTap: () {
if (item['title'] == 'About Us') {
_openUrl(item['url'].toString());
return;
}
Get.to(
() => KtWebViewPage(
url: item['url'].toString(),
title: item['title'],
),
);
},
child: Container(
color: Colors.transparent,
padding: EdgeInsets.symmetric(horizontal: 30.w),
child: Row(
children: [
Text(
item['title'].toString(),
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
const Spacer(),
Image.asset(
'ic_right_arrow.png'.ktIcon,
width: 10.w,
),
],
),
),
),
Container(
margin: EdgeInsets.symmetric(
horizontal: 30.w,
vertical: 20.w,
),
color: Colors.black.withValues(alpha: .1),
width: double.infinity,
height: 1 / MediaQuery.of(context).devicePixelRatio,
),
],
),
),
],
),
),
),
);
}
@override
void initState() {
initData();
super.initState();
}
initData() {
versionNum = KtDeviceInfoUtil().appVersion ?? '';
}
void _openUrl(String url) async {
try {
final encodedUrl = Uri.encodeFull(url);
await launchUrl(
Uri.parse(encodedUrl),
mode: LaunchMode.externalApplication,
);
} catch (e) {
debugPrint('---err:$e');
}
}
}

View File

@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class KtKtWebViewPage extends StatefulWidget {
final String url;
final String? title;
const KtKtWebViewPage({super.key, required this.url, this.title});
@override
State<KtKtWebViewPage> createState() => _KtWebViewPageState();
}
class _KtWebViewPageState extends State<KtKtWebViewPage> {
late final WebViewController _controller;
bool _isLoading = true;
@override
void initState() {
super.initState();
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onPageFinished: (_) => setState(() => _isLoading = false),
),
)
..loadRequest(Uri.parse(widget.url));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title ?? '')),
body: Stack(
children: [
WebViewWidget(controller: _controller),
if (_isLoading) const Center(child: CircularProgressIndicator()),
],
),
);
}
}

View File

@ -25,7 +25,7 @@ class KtMineLogic extends GetxController {
getUserInfo() async {
try {
ApiResponse res = await HttpClient().request(
ApiResponse res = await KtHttpClient().request(
KtApis.getCustomerInfo,
method: HttpMethod.get,
);

View File

@ -1,8 +1,14 @@
import 'package:flutter/material.dart';
import 'package:flutter_kinetra/kt_pages/kt_mine/logic.dart';
import 'package:flutter_kinetra/kt_pages/kt_routes.dart';
import 'package:flutter_kinetra/kt_utils/kt_string_extend.dart';
import 'package:flutter_kinetra/kt_utils/kt_utils.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import '../../dio_cilent/kt_apis.dart';
import '../kt_webview_page.dart';
class KtMinePage extends StatefulWidget {
const KtMinePage({super.key});
@ -12,6 +18,9 @@ class KtMinePage extends StatefulWidget {
}
class _KtMinePageState extends State<KtMinePage> {
final logic = Get.put(KtMineLogic());
final state = Get.find<KtMineLogic>().state;
@override
Widget build(BuildContext context) {
return Scaffold(
@ -25,6 +34,217 @@ class _KtMinePageState extends State<KtMinePage> {
fit: BoxFit.fill,
),
),
child: GetBuilder<KtMineLogic>(
builder: (ctrl) {
return Column(
children: [
Text(
'Profile',
style: TextStyle(
fontSize: 18.sp,
color: Colors.black,
fontWeight: FontWeight.w800,
fontStyle: FontStyle.italic,
),
),
Expanded(
child: SmartRefresher(
controller: logic.refreshController,
enablePullDown: true,
onRefresh: logic.getUserInfo,
child: Column(
children: [
Container(
width: ScreenUtil().screenWidth - 30.w,
margin: EdgeInsets.only(top: 22.w),
padding: EdgeInsets.fromLTRB(6.w, 13.w, 6.w, 6.w),
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('ic_mine_top_bg.png'.ktIcon),
fit: BoxFit.fill,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Welcome Back, visitor!',
style: TextStyle(
fontSize: 12.sp,
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
Container(
padding: EdgeInsets.symmetric(
vertical: 17.w,
horizontal: 13.w,
),
margin: EdgeInsets.only(top: 13.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14.w),
),
child: Row(
children: [
Image.asset(
'ic_avatar.png'.ktIcon,
width: 56.w,
),
SizedBox(width: 13.w),
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Stack(
alignment: Alignment.bottomCenter,
children: [
Image.asset(
'text_bg_discover.png'.ktIcon,
height: 17.w,
width: 55.w,
),
Padding(
padding: EdgeInsets.only(
bottom: 2.w,
),
child: Text(
KtUtils.isEmpty(
state.userInfo.familyName,
)
? 'Visitor'
: state
.userInfo
.familyName ??
'',
style: TextStyle(
fontFamily: 'PingFang SC',
fontSize: 18.sp,
color: Colors.black,
fontWeight: FontWeight.w500,
),
),
),
],
),
Text(
'ID: ${state.userInfo.customerId}',
style: TextStyle(
fontFamily: 'PingFang SC',
fontSize: 12.sp,
color: Color(0xFF5E5E5E),
fontWeight: FontWeight.w400,
),
),
],
),
],
),
),
],
),
),
SizedBox(height: 30.w),
settingsView(),
],
),
),
),
],
);
},
),
),
);
}
Widget settingsView() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Settings & More',
style: TextStyle(
fontSize: 15.sp,
color: Colors.black,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 12.w),
Container(
width: ScreenUtil().screenWidth - 36.w,
padding: EdgeInsets.symmetric(horizontal: 12.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(18.w),
image: DecorationImage(
image: AssetImage('setting_bg.png'.ktIcon),
fit: BoxFit.fill,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
handleItem(
'ic_privacy.png',
'Privacy Policy',
() => Get.to(
() => KtWebViewPage(
url: KtApis.WEB_SITE_PRIVATE,
title: "Privacy Policy",
),
),
),
handleItem(
'ic_agreement.png',
'User Agreement',
() => Get.to(
() => KtWebViewPage(
url: KtApis.WEB_SITE_POLICY,
title: "User Agreement",
),
),
),
handleItem(
'ic_help_center.png',
'Help Center',
() => Get.toNamed(KtRoutes.search),
),
handleItem(
'ic_about_us.png',
'About Us',
() => Get.toNamed(KtRoutes.aboutUs),
),
],
),
),
],
);
}
Widget handleItem(String icon, String title, Function func) {
return GestureDetector(
onTap: () {
func.call();
},
child: Container(
padding: EdgeInsets.symmetric(vertical: 15.w),
color: Colors.transparent,
child: Row(
children: [
Image.asset(icon.ktIcon, width: 20.w),
SizedBox(width: 10.w),
Text(
title,
style: TextStyle(
fontSize: 14.sp,
color: Colors.black,
fontWeight: FontWeight.w400,
),
),
const Spacer(),
Image.asset('ic_right_arrow.png'.ktIcon, width: 14.w),
],
),
),
);
}

View File

@ -0,0 +1,158 @@
import 'package:flutter/material.dart';
import 'package:flutter_kinetra/kt_model/kt_short_video_bean.dart';
import 'package:flutter_kinetra/kt_utils/kt_string_extend.dart';
import 'package:flutter_kinetra/kt_widgets/kt_status_widget.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import '../../kt_widgets/kt_network_image.dart';
import '../kt_routes.dart';
import 'logic.dart';
class KtMyChestPage extends StatefulWidget {
const KtMyChestPage({super.key});
@override
State<KtMyChestPage> createState() => _KtMyChestPageState();
}
class _KtMyChestPageState extends State<KtMyChestPage> {
final logic = Get.put(MyListLogic());
final state = Get.find<MyListLogic>().state;
@override
void initState() {
super.initState();
logic.getCollectList(refresh: true);
}
@override
Widget build(BuildContext context) {
return Container(
width: ScreenUtil().screenWidth,
height: ScreenUtil().screenHeight,
padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight),
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('bg2.png'.ktIcon),
fit: BoxFit.fill,
),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: Image.asset('ic_back.png'.ktIcon, width: 10.w),
onPressed: () => Navigator.of(context).maybePop(),
),
Text(
'My Treasure Chest',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w500,
color: Colors.black,
),
),
Container(width: 24.w),
],
),
GetBuilder<MyListLogic>(
builder: (ctrl) {
if (state.loadStatus == KtLoadStatusType.loading) {
return Container();
}
if (state.favoriteList.isEmpty &&
state.loadStatus != KtLoadStatusType.loading) {
return Container(
margin: EdgeInsets.only(top: 60.w),
child: KtStatusWidget(
type: KtErrorStatusType.nothingYet,
onPressed: logic.initData,
),
);
}
return Expanded(
child: SmartRefresher(
controller: logic.chestRefreshCtrl,
enablePullUp: true,
enablePullDown: true,
onRefresh: () => logic.getCollectList(refresh: true),
onLoading: () => logic.getCollectList(loadMore: true),
child: GridView.builder(
padding: EdgeInsets.only(
bottom: 16.w,
left: 15.w,
right: 15.w,
),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 18.w,
crossAxisSpacing: 6.w,
childAspectRatio: 111 / 168,
),
itemCount: state.favoriteList.length,
itemBuilder: (BuildContext context, int index) {
KtShortVideoBean video = state.favoriteList[index];
return GestureDetector(
onTap: () => Get.toNamed(
KtRoutes.shortVideo,
arguments: {
'shortPlayId': video.shortPlayId,
'imageUrl': video.imageUrl ?? '',
},
),
child: Column(
children: [
Stack(
children: [
KtNetworkImage(
imageUrl: video.imageUrl ?? '',
width: 111.w,
height: 148.w,
borderRadius: BorderRadius.circular(12.w),
),
Positioned(
right: 4.w,
top: 4.w,
child: GestureDetector(
onTap: () =>
logic.cancelCollect(video.shortPlayId!),
child: Image.asset(
'ic_collect_sel.png'.ktIcon,
width: 28.w,
),
),
),
],
),
SizedBox(
width: 111.w,
child: Text(
video.name ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 12.sp,
color: Color(0xFF1E1E20),
fontWeight: FontWeight.w400,
),
),
),
],
),
);
},
),
),
);
},
),
],
),
);
}
}

Some files were not shown because too many files have changed in this diff Show More