New file |
| | |
| | | <paths xmlns:android="http://schemas.android.com/apk/res/android"> |
| | | <!--为了适配所有路径可以设置 path = "." --> |
| | | <external-path name="tt_external_root" path="." /> |
| | | <external-path name="tt_external_download" path="Download" /> |
| | | <external-files-path name="tt_external_files_download" path="Download" /> |
| | | <files-path name="tt_internal_file_download" path="Download" /> |
| | | <cache-path name="tt_internal_cache_download" path="Download" /> |
| | | </paths> |
New file |
| | |
| | | <paths> |
| | | <!-- 这个下载路径也不可以修改,必须为GDTDOWNLOAD --> |
| | | <external-cache-path |
| | | name="gdt_sdk_download_path1" |
| | | path="com_qq_e_download" /> |
| | | <cache-path |
| | | name="gdt_sdk_download_path2" |
| | | path="com_qq_e_download" /> |
| | | </paths> |
New file |
| | |
| | | package com.demo.library_flutter; |
| | | |
| | | import android.os.Bundle; |
| | | import android.view.LayoutInflater; |
| | | import android.view.View; |
| | | import android.view.ViewGroup; |
| | | |
| | | import com.idlefish.flutterboost.containers.FlutterBoostFragment; |
| | | |
| | | import androidx.annotation.NonNull; |
| | | import androidx.annotation.Nullable; |
| | | |
| | | public class FlutterMineFragment extends FlutterBoostFragment { |
| | | |
| | | @Nullable |
| | | @Override |
| | | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { |
| | | return super.onCreateView( inflater, container, savedInstanceState); |
| | | } |
| | | |
| | | @Override |
| | | public void onResume() { |
| | | super.onResume(); |
| | | } |
| | | |
| | | @Override |
| | | public void onHiddenChanged(boolean hidden) { |
| | | super.onHiddenChanged(hidden); |
| | | } |
| | | |
| | | @Override |
| | | public void setUserVisibleHint(boolean isVisibleToUser) { |
| | | super.setUserVisibleHint(isVisibleToUser); |
| | | } |
| | | } |
New file |
| | |
| | | package com.demo.library_flutter; |
| | | |
| | | import android.os.Bundle; |
| | | import android.util.Log; |
| | | import android.view.LayoutInflater; |
| | | import android.view.View; |
| | | import android.view.ViewGroup; |
| | | |
| | | import com.idlefish.flutterboost.containers.FlutterBoostFragment; |
| | | |
| | | import androidx.annotation.NonNull; |
| | | import androidx.annotation.Nullable; |
| | | import androidx.fragment.app.Fragment; |
| | | |
| | | public class FlutterRecommendFragment extends FlutterBoostFragment { |
| | | |
| | | @Nullable |
| | | @Override |
| | | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { |
| | | return super.onCreateView( inflater, container, savedInstanceState); |
| | | } |
| | | |
| | | @Override |
| | | public void onResume() { |
| | | super.onResume(); |
| | | } |
| | | |
| | | @Override |
| | | public void onHiddenChanged(boolean hidden) { |
| | | super.onHiddenChanged(hidden); |
| | | } |
| | | |
| | | @Override |
| | | public void setUserVisibleHint(boolean isVisibleToUser) { |
| | | super.setUserVisibleHint(isVisibleToUser); |
| | | } |
| | | } |
New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| | | xmlns:app="http://schemas.android.com/apk/res-auto" |
| | | xmlns:tools="http://schemas.android.com/tools" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" |
| | | tools:context=".FlutterCommonActivity"> |
| | | |
| | | <FrameLayout |
| | | android:id="@+id/fl_container" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent"></FrameLayout> |
| | | |
| | | </androidx.constraintlayout.widget.ConstraintLayout> |
New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <paths> |
| | | <cache-path |
| | | name="files_path" |
| | | path="/"></cache-path> |
| | | </paths> |
New file |
| | |
| | | |
| | | import 'package:flutter/material.dart'; |
| | | |
| | | import 'http.dart'; |
| | | |
| | | class ConfigApiUtil { |
| | | ///查阅向我求救的SOS |
| | | static Future<Map<String, dynamic>?> getConfig(BuildContext context) async { |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/config/getConfig", {}, () {}, |
| | | notifyError: true); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | } |
New file |
| | |
| | | |
| | | import 'package:flutter/material.dart'; |
| | | import '../../utils/user_util.dart'; |
| | | |
| | | import 'http.dart'; |
| | | |
| | | class FeedBackApiUtil { |
| | | ///查阅向我求救的SOS |
| | | static Future<Map<String, dynamic>?> advice( |
| | | BuildContext context, String? type, String content) async { |
| | | var uid = await UserUtil.getUid(); |
| | | Map<String, dynamic> params = {}; |
| | | if (uid != null) { |
| | | params["uid"] = uid.toString(); |
| | | } |
| | | |
| | | if (type != null) { |
| | | params["type"] = type; |
| | | } |
| | | |
| | | params["content"] = content; |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/v2/help/advice", params, () { |
| | | showLoading(context); |
| | | }, notifyError: true); |
| | | |
| | | dismissDialog(context); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | } |
| | | |
New file |
| | |
| | | import 'dart:async'; |
| | | import 'dart:convert'; |
| | | import 'dart:io'; |
| | | |
| | | import 'package:device_info/device_info.dart'; |
| | | import 'package:dio/adapter.dart'; |
| | | import 'package:dio/dio.dart'; |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import '../../utils/des/des.dart'; |
| | | import '../../utils/share_preference.dart'; |
| | | |
| | | import '../model/common/http_model.dart'; |
| | | import '../ui/widget/dialog.dart'; |
| | | import '../utils/app_util.dart'; |
| | | import '../utils/encrypt_util.dart'; |
| | | import '../utils/global.dart'; |
| | | import '../utils/ui_utils.dart'; |
| | | |
| | | typedef OnHttpRequestFinish = void Function(HttpRequestResult result); |
| | | |
| | | typedef OnHttpRequestStart = void Function(); |
| | | |
| | | showLoading(BuildContext context) { |
| | | //先丢失焦点 |
| | | FocusScope.of(context).unfocus(); |
| | | //开启加载框 |
| | | DialogUtil.showDialog(context, LoadingDialog("")); |
| | | } |
| | | |
| | | dismissDialog(BuildContext context) { |
| | | Navigator.pop(context); |
| | | } |
| | | |
| | | class HttpUtil { |
| | | static AndroidDeviceInfo? _androidInfo; |
| | | static IosDeviceInfo? _iosInfo; |
| | | |
| | | static _getSign(Map<String, dynamic> params) { |
| | | List list = []; |
| | | //签名 |
| | | params.forEach((key, value) { |
| | | list.add("$key=$value"); |
| | | }); |
| | | //排序 |
| | | list.sort(); |
| | | String signStr = ""; |
| | | list.forEach((element) { |
| | | signStr += element + "&"; |
| | | }); |
| | | |
| | | if (signStr.endsWith("&")) { |
| | | signStr = signStr.substring(0, signStr.length - 1); |
| | | } |
| | | |
| | | signStr += "8888B&*@-uWan88/',@@^"; |
| | | |
| | | return EncryptUtil.MD5(signStr); |
| | | } |
| | | |
| | | static String _decode(String content) { |
| | | String key = utf8.decode(base64Decode("VW1nT3R2WVk=")); |
| | | String iv = utf8.decode(base64Decode("WXlIeEhsY0o=")); |
| | | |
| | | List<int> result = |
| | | DES().decryptWithCBC(base64Decode(content), key.codeUnits, iv: iv); |
| | | |
| | | return utf8.decode(result); |
| | | } |
| | | |
| | | static Future<Map<String, dynamic>> getBaseParams( |
| | | Map<String, dynamic>? params) async { |
| | | params ??= {}; |
| | | |
| | | if (Platform.isIOS) { |
| | | String finalParams = |
| | | await dataMethodChannel.invokeMethod("getBaseRequestParams", params); |
| | | return jsonDecode(finalParams); |
| | | } |
| | | if (Platform.isAndroid) { |
| | | if (_androidInfo == null) { |
| | | DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); |
| | | _androidInfo = await deviceInfo.androidInfo; |
| | | } |
| | | print("androidInfo:${_androidInfo!.version}"); |
| | | params["Version"] = (await AppUtil.getVersionCode()).toString(); |
| | | } else if (Platform.isIOS) { |
| | | if (_iosInfo == null) { |
| | | DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); |
| | | _iosInfo = await deviceInfo.iosInfo; |
| | | } |
| | | params["Version"] = "105"; |
| | | } |
| | | |
| | | //添加附加参数 |
| | | params["Timestamp"] = DateTime.now().millisecondsSinceEpoch.toString(); |
| | | params["Platform"] = "Android"; // Platform.isAndroid ? "Android" : "IOS"; |
| | | params["Package"] = "com.hanju.video"; |
| | | params["System"] = "1"; |
| | | Global.utdId = "testtest"; |
| | | Global.channel = "QQ"; |
| | | if (Platform.isAndroid) { |
| | | if (Global.utdId != null) { |
| | | params["UtdId"] = Global.utdId; |
| | | params["Device"] = Global.utdId; |
| | | } |
| | | params["osVersion"] = _androidInfo!.version.release; |
| | | } else if (Platform.isIOS) { |
| | | params["Device"] = "test123123"; //_iosInfo!.identifierForVendor; |
| | | } |
| | | |
| | | if (Global.channel != null) { |
| | | params["Channel"] = Global.channel; |
| | | } |
| | | |
| | | //青少年模式 |
| | | params["YouthMode"] = false; |
| | | |
| | | params["Sign"] = _getSign(params); |
| | | |
| | | return params; |
| | | } |
| | | |
| | | static Future<HttpRequestResult> baseRequest(BuildContext context, String api, |
| | | Map<String, dynamic> params, OnHttpRequestStart? onStart, |
| | | {bool notifyError = false}) async { |
| | | // params ??= {}; |
| | | params = await getBaseParams(params); |
| | | |
| | | if (onStart != null) { |
| | | onStart(); |
| | | } |
| | | HttpRequestResult requestResult; |
| | | |
| | | try { |
| | | var dio = Dio() |
| | | ..options = BaseOptions( |
| | | baseUrl: "http://api.hanju.goxcw.com:8089/BuWan", |
| | | connectTimeout: 20000, |
| | | receiveTimeout: 1000 * 60, |
| | | contentType: "application/x-www-form-urlencoded"); |
| | | //设置代理 |
| | | (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = |
| | | (HttpClient client) { |
| | | client.findProxy = (uri) { |
| | | return 'PROXY 192.168.3.122:8888'; |
| | | }; |
| | | client.badCertificateCallback = |
| | | (X509Certificate cert, String host, int port) => true; |
| | | }; |
| | | |
| | | // FormData formData = FormData.fromMap(params); |
| | | var response = await dio.post( |
| | | api, |
| | | data: params, |
| | | onSendProgress: (int sent, int total) { |
| | | print('$sent $total'); |
| | | }, |
| | | ); |
| | | if (response.statusCode == HttpStatus.ok) { |
| | | String result = response.data.toString(); |
| | | result = _decode(result); |
| | | print("网络请求结果:$result"); |
| | | requestResult = HttpRequestResult(true, jsonDecode(result)); |
| | | } else { |
| | | requestResult = HttpRequestResult(false, null, msg: "网络请求失败"); |
| | | } |
| | | } on DioError catch (_) { |
| | | if (_.type == DioErrorType.connectTimeout || |
| | | _.type == DioErrorType.receiveTimeout || |
| | | _.type == DioErrorType.sendTimeout) { |
| | | requestResult = HttpRequestResult(false, null, msg: "网络请求超时"); |
| | | } else { |
| | | requestResult = HttpRequestResult(false, null, msg: "网络请求出错"); |
| | | } |
| | | } catch (e) { |
| | | requestResult = HttpRequestResult(false, null, msg: "网络请求出错"); |
| | | } |
| | | if (notifyError && !requestResult.success) { |
| | | ToastUtil.toast(requestResult.msg!, context); |
| | | } |
| | | return requestResult; |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:convert'; |
| | | |
| | | import 'package:flutter/material.dart'; |
| | | import '../../utils/string_util.dart'; |
| | | import '../../utils/user_util.dart'; |
| | | |
| | | import 'http.dart'; |
| | | import 'dart:io'; |
| | | |
| | | class UserApiUtil { |
| | | ///验证码发送 |
| | | static Future<Map<String, dynamic>?> sendSMS( |
| | | BuildContext context, String phone) async { |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/v1/sms/sendSMS", {"phone": phone}, () { |
| | | showLoading(context); |
| | | }); |
| | | dismissDialog(context); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | ///发送邮箱验证码 |
| | | static Future<Map<String, dynamic>?> sendEmailCode( |
| | | BuildContext context, String email) async { |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/user/sendVerifyCode", {"Email": email}, () { |
| | | showLoading(context); |
| | | }); |
| | | dismissDialog(context); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | static Future<Map<String, dynamic>?> uploadPushRegId( |
| | | BuildContext context, String regId) async { |
| | | var uid = await UserUtil.getUid(); |
| | | var params = {"regId": regId}; |
| | | if (uid != null) { |
| | | params["uid"] = uid.toString(); |
| | | } |
| | | |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/v1/user/uploadPushRegId", params, () {}); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | ///登录 |
| | | static Future<Map<String, dynamic>?> loginByPhone( |
| | | BuildContext context, String phone, String vcode, String token) async { |
| | | Map<String, dynamic> params = {}; |
| | | params["phone"] = phone; |
| | | if (!StringUtil.isNullOrEmpty(vcode)) { |
| | | params["vcode"] = vcode; |
| | | } |
| | | |
| | | if (!StringUtil.isNullOrEmpty(token)) { |
| | | params["token"] = token; |
| | | } |
| | | |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/v1/user/loginPhone", params, () { |
| | | showLoading(context); |
| | | }, notifyError: true); |
| | | dismissDialog(context); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | static Future<Map<String, dynamic>?> loginByEmail( |
| | | BuildContext context, String email, String pwd) async { |
| | | Map<String, dynamic> params = {}; |
| | | params["Email"] = email; |
| | | params["Pwd"] = pwd; |
| | | |
| | | var result = |
| | | await HttpUtil.baseRequest(context, "/api/user/login", params, () { |
| | | showLoading(context); |
| | | }, notifyError: true); |
| | | dismissDialog(context); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | static Future<Map<String, dynamic>?> registerByEmail( |
| | | BuildContext context, String email, String pwd, String code) async { |
| | | Map<String, dynamic> params = {}; |
| | | params["Email"] = email; |
| | | params["Pwd"] = pwd; |
| | | params["VerifyCode"] = code; |
| | | var result = |
| | | await HttpUtil.baseRequest(context, "/api/user/register", params, () { |
| | | showLoading(context); |
| | | }, notifyError: true); |
| | | dismissDialog(context); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | static Future<Map<String, dynamic>?> setPwd( |
| | | BuildContext context, String email, String pwd, String code) async { |
| | | Map<String, dynamic> params = {}; |
| | | params["Email"] = email; |
| | | params["Pwd"] = pwd; |
| | | params["VerifyCode"] = code; |
| | | var result = |
| | | await HttpUtil.baseRequest(context, "/api/user/setPwd", params, () { |
| | | showLoading(context); |
| | | }, notifyError: true); |
| | | dismissDialog(context); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | ///获取用户信息 |
| | | static Future<Map<String, dynamic>?> getUserInfo( |
| | | BuildContext context, String uid) async { |
| | | Map<String, dynamic> params = {}; |
| | | params["LoginUid"] = uid.toString(); |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/user/getLoginUserInfo", params, () {}); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | static Future<Map<String, dynamic>?> updateUserInfo(BuildContext context, |
| | | {String? nickName, |
| | | String? portraitPath, |
| | | int? sex, |
| | | String? sign, |
| | | String? birthDay}) async { |
| | | Map<String, dynamic> params = {}; |
| | | params["LoginUid"] = await UserUtil.getUid(); |
| | | if (nickName != null) { |
| | | params["NickName"] = nickName; |
| | | } |
| | | |
| | | if (portraitPath != null) { |
| | | List<int> bytes =await File(portraitPath).readAsBytes(); |
| | | String bs64 = base64Encode(bytes); |
| | | params["Portrait"] = bs64; |
| | | } |
| | | |
| | | if (sex != null) { |
| | | params["Sex"] = sex.toString(); |
| | | } |
| | | |
| | | if (sign != null) { |
| | | params["PersonalSign"] = sign; |
| | | } |
| | | |
| | | if (birthDay != null) { |
| | | params["BirthDay"] = birthDay; |
| | | } |
| | | |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/user/updateLoginUserInfo", params, () { |
| | | showLoading(context); |
| | | }); |
| | | dismissDialog(context); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | ///退出登录 |
| | | static Future<Map<String, dynamic>?> logout( |
| | | BuildContext context, String uid) async { |
| | | Map<String, dynamic> params = {}; |
| | | params["uid"] = uid.toString(); |
| | | var result = |
| | | await HttpUtil.baseRequest(context, "/api/user/logout", params, () {}); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | } |
New file |
| | |
| | | import 'package:flutter/material.dart'; |
| | | import '../../utils/user_util.dart'; |
| | | |
| | | import 'http.dart'; |
| | | |
| | | class HomeApiUtil { |
| | | static Future<Map<String, dynamic>?> getHomeAd( |
| | | BuildContext context, String dataKey, String vtid) async { |
| | | Map<String, dynamic> params = {}; |
| | | params["DataKey"] = dataKey; |
| | | params["Vtid"] = vtid; |
| | | params["Method"] = "getHomeAd"; |
| | | |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/v2/recommend", params, () {}, |
| | | notifyError: true); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | static Future<Map<String, dynamic>?> getHomeTypes( |
| | | BuildContext context, String dataKey, String vtid, int page) async { |
| | | Map<String, dynamic> params = {}; |
| | | params["DataKey"] = dataKey; |
| | | params["Vtid"] = vtid; |
| | | params["Method"] = "getHomeTypeNew"; |
| | | params["Page"] = "$page"; |
| | | params["PageSize"] = "5"; |
| | | |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/v2/recommend", params, () {}, |
| | | notifyError: true); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | static Future<Map<String, dynamic>?> getRecommendSearchSpecial( |
| | | BuildContext context) async { |
| | | Map<String, dynamic> params = {}; |
| | | params["Method"] = "getRecommendSearchSpecial"; |
| | | |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/v2/recommend", params, () {}, |
| | | notifyError: true); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | class SearchApiUtil { |
| | | static Future<Map<String, dynamic>?> getHotSearch( |
| | | BuildContext context) async { |
| | | Map<String, dynamic> params = {}; |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/user/getHotSearch", params, () {}, |
| | | notifyError: true); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | static Future<Map<String, dynamic>?> getSuggestSearch( |
| | | BuildContext context, String key) async { |
| | | Map<String, dynamic> params = {}; |
| | | params["Key"] = key; |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/user/suggestSearch", params, () {}, |
| | | notifyError: true); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | static Future<Map<String, dynamic>?> search( |
| | | BuildContext context, String key, int page, int? type) async { |
| | | Map<String, dynamic> params = {}; |
| | | params["Key"] = key; |
| | | params["Page"] = "$page"; |
| | | type ??= 0; |
| | | params["Type"] = "$type"; |
| | | |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/user/searchNew", params, () {}, |
| | | notifyError: true); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | class VideoApiUtil { |
| | | //获取搜索专题视频列表 |
| | | static Future<Map<String, dynamic>?> getSearchSpecialVideoList( |
| | | BuildContext context, String key, int page) async { |
| | | Map<String, dynamic> params = {}; |
| | | params["Method"] = "getSpecialVideo"; |
| | | params["key"] = key; |
| | | params["page"] = "$page"; |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/v2/search", params, () {}, |
| | | notifyError: true); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | //获取详情 |
| | | static Future<Map<String, dynamic>?> getVideoDetail( |
| | | BuildContext context, String videoId, String? resourceId) async { |
| | | Map<String, dynamic> params = {}; |
| | | params["VideoId"] = videoId; |
| | | params["Type"] = "0"; |
| | | params["Uid"] = "1"; |
| | | params["ResourceId"] = resourceId ?? ""; |
| | | |
| | | if (await UserUtil.isLogin()) { |
| | | params["LoginUid"] = await UserUtil.getUid(); |
| | | } |
| | | |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/recommend/getVideoDetailV2", params, () {}, |
| | | notifyError: true); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | //获取剧集列表 |
| | | static Future<Map<String, dynamic>?> getVideoEpisodeList( |
| | | BuildContext context, String videoId, String? resourceId, int page, |
| | | {int pageSize = 100}) async { |
| | | Map<String, dynamic> params = {}; |
| | | params["VideoId"] = videoId; |
| | | params["PageSize"] = "$pageSize"; |
| | | params["Page"] = "$page"; |
| | | params["Method"] = "getVideoEpisodeList"; |
| | | params["ResourceId"] = resourceId ?? ""; |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/v2/recommend", params, () {}, |
| | | notifyError: true); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | //获取播放列表 |
| | | static Future<Map<String, dynamic>?> getPlayUrl(BuildContext context, |
| | | String videoId, String id, String resourceId) async { |
| | | Map<String, dynamic> params = {}; |
| | | params["VideoId"] = videoId; |
| | | params["Id"] = id; |
| | | params["ResourceId"] = resourceId; |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/recommend/getPlayUrl", params, () {}, |
| | | notifyError: true); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | //相关推荐 |
| | | static Future<Map<String, dynamic>?> getRelativeVideos( |
| | | BuildContext context, String videoId) async { |
| | | Map<String, dynamic> params = {}; |
| | | params["VideoId"] = videoId; |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/user/getRelativeVideos", params, () {}, |
| | | notifyError: true); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | //是否收藏 |
| | | static Future<Map<String, dynamic>?> isCollelctedVideo( |
| | | BuildContext context, String videoId) async { |
| | | Map<String, dynamic> params = {}; |
| | | params["Id"] = videoId; |
| | | params["LoginUid"] = await UserUtil.getUid(); |
| | | params["ThirdType"] = "0"; |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/recommend/isCollect", params, () {}, |
| | | notifyError: true); |
| | | |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | //收藏/取消收藏 视频 |
| | | static Future<Map<String, dynamic>?> collelctVideo( |
| | | BuildContext context, String videoId, bool collected) async { |
| | | Map<String, dynamic> params = {}; |
| | | params["VideoId"] = videoId; |
| | | params["LoginUid"] = await UserUtil.getUid(); |
| | | |
| | | var result; |
| | | if (collected) { |
| | | result = await HttpUtil.baseRequest( |
| | | context, "/api/uservideo/collectVideo", params, () { |
| | | showLoading(context); |
| | | }, notifyError: true); |
| | | } else { |
| | | result = await HttpUtil.baseRequest( |
| | | context, "/api/uservideo/cancelCollectVideo", params, () { |
| | | showLoading(context); |
| | | }, notifyError: true); |
| | | } |
| | | dismissDialog(context); |
| | | |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | static Future<Map<String, dynamic>?> getCollelctedVideos( |
| | | BuildContext context, int page) async { |
| | | Map<String, dynamic> params = {}; |
| | | params["Page"] = "$page"; |
| | | params["LoginUid"] = await UserUtil.getUid(); |
| | | |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/uservideo/getCollectVideoList", params, () {}, |
| | | notifyError: true); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | static Future<Map<String, dynamic>?> getUserVideoCount( |
| | | BuildContext context) async { |
| | | Map<String, dynamic> params = {}; |
| | | params["LoginUid"] = await UserUtil.getUid(); |
| | | params["Method"] = "getUserVideoDataCount"; |
| | | |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/v2/userVideo", params, () {}, |
| | | notifyError: true); |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | //关注 |
| | | static Future<Map<String, dynamic>?> addAttentionVideo( |
| | | BuildContext context, String videoId) async { |
| | | Map<String, dynamic> params = {}; |
| | | params["VideoId"] = videoId; |
| | | params["LoginUid"] = await UserUtil.getUid(); |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/attention/addAttention", params, () {}, |
| | | notifyError: true); |
| | | |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | //取消关注 |
| | | static Future<Map<String, dynamic>?> cancelAttentionVideo( |
| | | BuildContext context, String videoId) async { |
| | | Map<String, dynamic> params = {}; |
| | | params["VideoId"] = videoId; |
| | | params["LoginUid"] = await UserUtil.getUid(); |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/attention/cancelAttention", params, () {}, |
| | | notifyError: true); |
| | | |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | //获取关注列表 |
| | | static Future<Map<String, dynamic>?> getAttentionVideoList( |
| | | BuildContext context, int page) async { |
| | | Map<String, dynamic> params = {}; |
| | | params["Page"] = page; |
| | | params["LoginUid"] = await UserUtil.getUid(); |
| | | var result = await HttpUtil.baseRequest( |
| | | context, "/api/attention/getAttentionList", params, () {}, |
| | | notifyError: true); |
| | | |
| | | if (result.success) { |
| | | return result.data; |
| | | } |
| | | return null; |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:convert'; |
| | | import 'dart:io'; |
| | | import 'dart:math'; |
| | | |
| | | import 'package:flutter/material.dart'; |
| | | import 'package:flutter_module/utils/ui_utils.dart'; |
| | | import 'package:flutter_swiper_null_safety/flutter_swiper_null_safety.dart'; |
| | | import '../../ui/widget/ad_express.dart'; |
| | | import '../../ui/widget/refresh_listview.dart'; |
| | | import '../../utils/ad_util.dart'; |
| | | import '../../utils/pageutils.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | |
| | | import '../../model/video/home_type_model.dart'; |
| | | import '../../model/video/search_special_model.dart'; |
| | | import 'api/video_api.dart'; |
| | | import 'model/video/home_ad_model.dart'; |
| | | import 'ui/widget/video_item.dart'; |
| | | import 'utils/jump_page.dart'; |
| | | |
| | | class HomePage extends StatefulWidget { |
| | | HomePage({Key? key, required this.title}) : super(key: key); |
| | | |
| | | final String title; |
| | | |
| | | @override |
| | | _HomePageState createState() => _HomePageState(); |
| | | } |
| | | |
| | | class _HomePageState extends State<HomePage> |
| | | with SingleTickerProviderStateMixin { |
| | | final MyRefreshController _refreshController = |
| | | MyRefreshController(initialRefresh: false); |
| | | List<HomeAdModel> bannerList = []; |
| | | List homeTypeList = []; |
| | | int page = 1; |
| | | int totalCount = 0; |
| | | List<SearchSpecialModel> categoryList = []; |
| | | bool hasDYHot = true; |
| | | String searchKw = ""; |
| | | |
| | | //信息流广告 |
| | | Widget? expressAd; |
| | | AdType? expressAdType; |
| | | String? expressPid; |
| | | |
| | | ExpressAdController _expressAdController = ExpressAdController(); |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | init(); |
| | | AdUtil.getAdType(AdPosition.other).then((value) { |
| | | setState(() { |
| | | expressAdType = value; |
| | | }); |
| | | if (expressAdType == AdType.csj) { |
| | | expressPid = CSJADConstant.PID_RECOMMEND_BIG_PICTURE; |
| | | } else if (expressAdType == AdType.gdt) { |
| | | expressPid = GDTADConstant.PID_RECOMMEND_BIG_PICTURE; |
| | | } |
| | | }); |
| | | |
| | | // Future.delayed(Duration(seconds: 5),(){ |
| | | uiMethodChannel.invokeMethod("setStatusBarLight"); |
| | | // }); |
| | | } |
| | | |
| | | void init() { |
| | | getHomeAd(); |
| | | getHomeType(page); |
| | | getSearchSpecial(); |
| | | getHotSearch(); |
| | | //刷新 |
| | | _expressAdController.refresh; |
| | | } |
| | | |
| | | void jumpPage(String name, dynamic params) { |
| | | JumpPageUtil.jumpPage(name, context, |
| | | params: params, native: true, callback: (data) {}); |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return Scaffold( |
| | | backgroundColor: Colors.white, |
| | | body: Column(children: [ |
| | | Container( |
| | | color: ColorConstant.theme, |
| | | padding: const EdgeInsets.fromLTRB(10, 12, 10, 12), |
| | | child: InkWell( |
| | | onTap: () { |
| | | jumpPage("SearchPage", {"title": searchKw}); |
| | | }, |
| | | child: Container( |
| | | height: 32, |
| | | decoration: BoxDecoration( |
| | | color: Colors.white, |
| | | borderRadius: BorderRadius.circular(16)), |
| | | child: Row( |
| | | children: [ |
| | | Container( |
| | | width: 12, |
| | | ), |
| | | Image.asset( |
| | | "assets/imgs/icon_search_home.png", |
| | | height: 17, |
| | | ), |
| | | Container( |
| | | width: 10, |
| | | ), |
| | | Text( |
| | | searchKw, |
| | | style: TextStyle( |
| | | color: const Color(0xFF787878), fontSize: 14), |
| | | ) |
| | | ], |
| | | ), |
| | | )), |
| | | ), |
| | | Expanded( |
| | | child: RefreshListView( |
| | | refreshController: _refreshController, |
| | | content: CustomScrollView(slivers: [ |
| | | SliverList( |
| | | delegate: SliverChildBuilderDelegate( |
| | | (content, index) { |
| | | if (index == 0) { |
| | | return bannerList.isEmpty ? Container() : getBannerView(); |
| | | } else if (index == 1) { |
| | | return categoryList.isEmpty |
| | | ? Container() |
| | | : getCategoryView(); |
| | | } else if (index == 2) { |
| | | return getAdView(); |
| | | } else { |
| | | return getHomeTypeView(index - 3, context); |
| | | } |
| | | }, |
| | | childCount: homeTypeList.length + 3, |
| | | )) |
| | | ]), |
| | | refresh: () { |
| | | page = 1; |
| | | init(); |
| | | }, |
| | | loadMore: () { |
| | | getHomeType(page + 1); |
| | | }, |
| | | )) |
| | | ])); |
| | | } |
| | | |
| | | void getHomeAd() async { |
| | | Map<String, dynamic>? result = |
| | | await HomeApiUtil.getHomeAd(context, "recommend", "1628826741158"); |
| | | if (result == null) { |
| | | return; |
| | | } |
| | | if (result["IsPost"] == "true") { |
| | | List<dynamic> list = result["Data"]["data"]; |
| | | List<HomeAdModel> adList = []; |
| | | for (var element in list) { |
| | | adList.add(HomeAdModel.fromJson(element)); |
| | | } |
| | | setState(() { |
| | | bannerList = adList; |
| | | }); |
| | | } |
| | | } |
| | | |
| | | void getHomeType(int _page) async { |
| | | page = _page; |
| | | Map<String, dynamic>? result = await HomeApiUtil.getHomeTypes( |
| | | context, "recommend", "1628826741158", _page); |
| | | _refreshController.refreshCompleted(resetFooterState: true); |
| | | //请求失败了 |
| | | if (result == null) { |
| | | if (page > 1) { |
| | | page = page - 1; |
| | | } |
| | | if (homeTypeList.isEmpty) { |
| | | _refreshController.apiError!(); |
| | | } |
| | | |
| | | return; |
| | | } |
| | | if (result["IsPost"] == "true") { |
| | | List<dynamic> list = result["Data"]["data"]; |
| | | totalCount = int.parse(result["Data"]["count"]); |
| | | List<HomeTypeModel> tempHomeTypeList = []; |
| | | for (var element in list) { |
| | | tempHomeTypeList.add(HomeTypeModel.fromJson(element)); |
| | | } |
| | | setState(() { |
| | | if (_page == 1) { |
| | | homeTypeList = tempHomeTypeList; |
| | | } else { |
| | | if (tempHomeTypeList.isNotEmpty) { |
| | | homeTypeList.addAll(tempHomeTypeList); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | if (totalCount <= homeTypeList.length) { |
| | | _refreshController.loadNoData(); |
| | | } else { |
| | | if (_page > 1) { |
| | | _refreshController.loadComplete(); |
| | | } |
| | | } |
| | | if (homeTypeList.isEmpty) { |
| | | _refreshController.dataEmpty!(); |
| | | } else { |
| | | //正常的状态 |
| | | _refreshController.dataNormal!(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | void getSearchSpecial() async { |
| | | Map<String, dynamic>? result = |
| | | await HomeApiUtil.getRecommendSearchSpecial(context); |
| | | _refreshController.refreshCompleted(resetFooterState: true); |
| | | //请求失败了 |
| | | if (result == null) { |
| | | if (page > 1) { |
| | | page = page - 1; |
| | | } |
| | | return; |
| | | } |
| | | if (result["IsPost"] == "true") { |
| | | List<dynamic> list = result["Data"]; |
| | | List<SearchSpecialModel> tempList = []; |
| | | for (var element in list) { |
| | | tempList.add(SearchSpecialModel.fromJson(element)); |
| | | } |
| | | // tempList.add(SearchSpecialModel( |
| | | // id: "novel", |
| | | // icon: "assets/imgs/home/icon_home_category_novel.png", |
| | | // name: "小说")); |
| | | setState(() { |
| | | categoryList = tempList; |
| | | }); |
| | | } |
| | | } |
| | | |
| | | void getHotSearch() async { |
| | | Map<String, dynamic>? result = await SearchApiUtil.getHotSearch(context); |
| | | if (result == null) { |
| | | return; |
| | | } |
| | | if (result["IsPost"] == "true") { |
| | | List<dynamic> list = result["Data"]["data"]; |
| | | setState(() { |
| | | searchKw = list[Random().nextInt(list.length)]; |
| | | }); |
| | | } |
| | | } |
| | | |
| | | Widget getBannerView() { |
| | | double width = MediaQuery.of(context).size.width; |
| | | double itemWidth = width - 40; |
| | | double itemHeight = itemWidth * 0.4382; |
| | | CustomLayoutOption customLayoutOption = |
| | | CustomLayoutOption(startIndex: -1, stateCount: 3); |
| | | customLayoutOption.addTranslate([ |
| | | Offset(-(width - 65), 0), |
| | | const Offset(0.0, 0.0), |
| | | Offset(width - 65, 0) |
| | | ]); |
| | | customLayoutOption.addScale([0.8, 1, 0.8], Alignment.center); |
| | | |
| | | return KeepAliveWrapper( |
| | | child: bannerList.isNotEmpty |
| | | ? Container( |
| | | padding: EdgeInsets.only(top: 10), |
| | | child: SizedBox( |
| | | width: width, |
| | | height: itemHeight, |
| | | child: Swiper( |
| | | itemBuilder: (BuildContext context, int index) { |
| | | return ClipRRect( |
| | | child: VideoImage( |
| | | bannerList[index].picture, |
| | | ), |
| | | borderRadius: BorderRadius.circular(8), |
| | | ); |
| | | }, |
| | | layout: SwiperLayout.DEFAULT, |
| | | customLayoutOption: customLayoutOption, |
| | | indicatorLayout: PageIndicatorLayout.COLOR, |
| | | autoplay: true, |
| | | duration: 500, |
| | | itemCount: bannerList.length, |
| | | pagination: const SwiperPagination( |
| | | margin: EdgeInsets.all(10), |
| | | builder: DotSwiperPaginationBuilder( |
| | | size: 8, |
| | | activeColor: ColorConstant.theme, |
| | | color: Colors.grey)), |
| | | itemWidth: itemWidth, |
| | | outer: false, |
| | | scale: 0.86, |
| | | viewportFraction: 0.86, |
| | | // containerHeight: itemHeight-30, |
| | | // containerWidth:width, |
| | | itemHeight: itemHeight, |
| | | onTap: (index) { |
| | | print("banner点击:$index"); |
| | | if (bannerList[index].linkType == 1) { |
| | | jumpPage("VideoDetailPage", { |
| | | "video": bannerList[index].video!.toJson(), |
| | | }); |
| | | } else if (bannerList[index].linkType == 2) { |
| | | dynamic json = jsonDecode(bannerList[index].params!); |
| | | String url = json["url"]; |
| | | jumpPage("BrowserPage", { |
| | | "url": url, |
| | | }); |
| | | } |
| | | }, |
| | | )), |
| | | ) |
| | | : Container()); |
| | | } |
| | | |
| | | Widget getCategoryView() { |
| | | return categoryList.isNotEmpty |
| | | ? Container( |
| | | padding: const EdgeInsets.fromLTRB(10, 20, 10, 10), |
| | | child: Row( |
| | | mainAxisAlignment: MainAxisAlignment.spaceBetween, |
| | | children: categoryList |
| | | .map((e) => InkWell( |
| | | onTap: () { |
| | | jumpPage( |
| | | "VideoListPage", {"kw": e.id!, "title": e.name}); |
| | | }, |
| | | child: Column(children: [ |
| | | CommonImage( |
| | | e.icon!, |
| | | width: MediaQuery.of(context).size.width * 55 / 375, |
| | | ), |
| | | Text( |
| | | e.name!, |
| | | style: const TextStyle( |
| | | color: Color(0xFF3B3B3B), fontSize: 12), |
| | | ) |
| | | ]))) |
| | | .toList())) |
| | | : Container(); |
| | | } |
| | | |
| | | Widget getAdView() { |
| | | return expressAdType != null |
| | | ? Container( |
| | | padding: const EdgeInsets.fromLTRB(10, 10, 10, 10), |
| | | child: Column( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | // const Text( |
| | | // "今日热点", |
| | | // style: TextStyle(color: Color(0xFF5F5F5F), fontSize: 16), |
| | | // ), |
| | | SizedBox( |
| | | height: (MediaQuery.of(context).size.width - 20) * 0.75, |
| | | width: MediaQuery.of(context).size.width, |
| | | child: _nativeView(), |
| | | ) |
| | | ], |
| | | ), |
| | | ) |
| | | : Container(); |
| | | } |
| | | |
| | | Widget _nativeView() { |
| | | return expressAdType == AdType.csj |
| | | ? CSJEXpressAd( |
| | | expressPid!, |
| | | MediaQuery.of(context).size.width - 20, |
| | | (MediaQuery.of(context).size.width - 20) * 0.8, |
| | | controller: _expressAdController, |
| | | close: () { |
| | | setState(() { |
| | | expressAdType = null; |
| | | }); |
| | | }, |
| | | loadFail: () { |
| | | setState(() { |
| | | expressAdType = null; |
| | | }); |
| | | }, |
| | | ) |
| | | : GDTEXpressAd( |
| | | expressPid!, |
| | | MediaQuery.of(context).size.width - 20, |
| | | (MediaQuery.of(context).size.width - 20) * 0.8, |
| | | controller: _expressAdController, |
| | | close: () { |
| | | setState(() { |
| | | expressAdType = null; |
| | | }); |
| | | }, |
| | | loadFail: () { |
| | | setState(() { |
| | | expressAdType = null; |
| | | }); |
| | | }, |
| | | ); |
| | | } |
| | | |
| | | Widget getHomeTypeView(index, BuildContext context) { |
| | | HomeTypeModel homeType = homeTypeList[index]; |
| | | double mx = MediaQuery.of(context).size.width; |
| | | return VideoListUIUtil.getHomeTypeItem(mx, homeType, context); |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:io'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import 'package:flutter/services.dart'; |
| | | import 'package:flutter_boost/flutter_boost.dart'; |
| | | import '../../api/video_api.dart'; |
| | | import '../../ui/widget/button.dart'; |
| | | import '../../ui/widget/video_item.dart'; |
| | | import '../../utils/db_manager.dart'; |
| | | import '../../utils/jump_page.dart'; |
| | | import '../../utils/string_util.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | import '../../utils/user_util.dart'; |
| | | |
| | | import 'model/user/user_info.dart'; |
| | | import 'model/video/watch_record_model.dart'; |
| | | import 'utils/share_preference.dart'; |
| | | import 'package:share_plus/share_plus.dart'; |
| | | |
| | | class MinePage extends StatefulWidget { |
| | | MinePage({Key? key, required this.title}) : super(key: key); |
| | | |
| | | final String title; |
| | | |
| | | @override |
| | | _MinePageState createState() => _MinePageState(); |
| | | } |
| | | |
| | | class _MinePageState extends State<MinePage> |
| | | with SingleTickerProviderStateMixin, PageVisibilityObserver { |
| | | int index = 1; |
| | | |
| | | UserInfo? _user; |
| | | List<WatchRecordModel> recordList = []; |
| | | |
| | | int collectedVideoCount = 0; |
| | | int favoriteVideoCount = 0; |
| | | String applink = ""; |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | if (Platform.isIOS) { |
| | | MySharedPreferences.getInstance().getString("appLink").then((value) { |
| | | setState(() { |
| | | applink = value!; |
| | | }); |
| | | }); |
| | | } |
| | | _refreshData(); |
| | | } |
| | | |
| | | void _refreshData() { |
| | | updateUserInfo(); |
| | | loadWatchRecord(); |
| | | loadUserVideoCount(); |
| | | } |
| | | |
| | | void updateUserInfo() async { |
| | | if (!await UserUtil.isLogin()) { |
| | | setState(() { |
| | | _user = null; |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | UserUtil.updateUserInfo(context).then((value) { |
| | | print("user:$value"); |
| | | setState(() { |
| | | _user = value; |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | void loadWatchRecord() async { |
| | | List<WatchRecordModel> temp = await DBManager.listWatchRecord(1, 20); |
| | | setState(() { |
| | | recordList = temp; |
| | | }); |
| | | } |
| | | |
| | | void loadUserVideoCount() async { |
| | | if (!await UserUtil.isLogin()) { |
| | | return; |
| | | } |
| | | Map<String, dynamic>? result = |
| | | await VideoApiUtil.getUserVideoCount(context); |
| | | if (result == null) { |
| | | return; |
| | | } |
| | | if (result["IsPost"] == "true") { |
| | | setState(() { |
| | | collectedVideoCount = result["Data"]["collectionCount"]; |
| | | favoriteVideoCount = result["Data"]["attentionCount"]; |
| | | }); |
| | | } |
| | | } |
| | | |
| | | void jumpPage(String name, {Map<String, dynamic>? params}) { |
| | | JumpPageUtil.jumpPage(name, context, native: Platform.isIOS,params: params, |
| | | callback: (data) { |
| | | _refreshData(); |
| | | }); |
| | | } |
| | | |
| | | @override |
| | | void didChangeDependencies() { |
| | | super.didChangeDependencies(); |
| | | |
| | | ///注册监听器 |
| | | PageVisibilityBinding.instance.addObserver(this, ModalRoute.of(context)!); |
| | | } |
| | | |
| | | @override |
| | | void dispose() { |
| | | ///移除监听器 |
| | | PageVisibilityBinding.instance.removeObserver(this); |
| | | super.dispose(); |
| | | } |
| | | |
| | | @override |
| | | void onBackground() { |
| | | super.onBackground(); |
| | | print("LifecycleTestPage - onBackground"); |
| | | } |
| | | |
| | | @override |
| | | void onForeground() { |
| | | super.onForeground(); |
| | | print("LifecycleTestPage - onForeground"); |
| | | } |
| | | |
| | | @override |
| | | void onPageHide() { |
| | | super.onPageHide(); |
| | | print("LifecycleTestPage - onPageHide"); |
| | | } |
| | | |
| | | @override |
| | | void onPageShow() { |
| | | super.onPageShow(); |
| | | print("LifecycleTestPage - onPageShow"); |
| | | _refreshData(); |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return Scaffold( |
| | | backgroundColor: Colors.white, |
| | | body: Column( |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | mainAxisAlignment: MainAxisAlignment.start, |
| | | children: [ |
| | | Container( |
| | | alignment: Alignment.center, |
| | | padding: EdgeInsets.only( |
| | | top: 15 + MediaQuery.of(context).viewPadding.top, bottom: 15), |
| | | color: ColorConstant.theme, |
| | | child: const Text("我的", |
| | | style: TextStyle(fontSize: 20, color: Colors.white)), |
| | | ), |
| | | Expanded( |
| | | child: Container( |
| | | height: 20, |
| | | color: Colors.white, |
| | | child: SingleChildScrollView( |
| | | child: Column( |
| | | children: [ |
| | | Container( |
| | | height: 51, |
| | | ), |
| | | |
| | | //个人信息 |
| | | InkWell( |
| | | child: Row( |
| | | children: [ |
| | | Container( |
| | | width: 18, |
| | | ), |
| | | portraitWidget(), |
| | | Container( |
| | | width: 10, |
| | | ), |
| | | _user != null |
| | | ? personInfoWidget() |
| | | : |
| | | //登录框 |
| | | loginBtnWidget(), |
| | | Container( |
| | | width: 18, |
| | | ), |
| | | ], |
| | | ), |
| | | onTap: () { |
| | | UserUtil.isLogin().then((value) { |
| | | if (value) { |
| | | jumpPage("PersonInfoPage"); |
| | | } |
| | | }); |
| | | }, |
| | | ), |
| | | Container( |
| | | height: 8, |
| | | ), |
| | | //签名 |
| | | signWidget(), |
| | | Container( |
| | | height: 23, |
| | | ), |
| | | //收藏,追剧,缓存 |
| | | Row( |
| | | mainAxisAlignment: MainAxisAlignment.spaceAround, |
| | | children: [ |
| | | InkWell( |
| | | onTap: () { |
| | | UserUtil.isLogin().then((value) { |
| | | if (!value) { |
| | | jumpPage("EmailLoginPage"); |
| | | return; |
| | | } |
| | | jumpPage("VideoCollectedPage"); |
| | | }); |
| | | }, |
| | | child: Column( |
| | | children: [ |
| | | const Text( |
| | | "收藏", |
| | | style: TextStyle( |
| | | fontSize: 18, color: Color(0xFF888888)), |
| | | ), |
| | | Text( |
| | | "$collectedVideoCount", |
| | | style: const TextStyle( |
| | | fontSize: 12, color: Color(0xFF888888)), |
| | | ), |
| | | ], |
| | | )), |
| | | InkWell( |
| | | onTap: () { |
| | | UserUtil.isLogin().then((value) { |
| | | if (!value) { |
| | | jumpPage("EmailLoginPage"); |
| | | return; |
| | | } |
| | | jumpPage("VideoAttentionPage"); |
| | | }); |
| | | }, |
| | | child: Column( |
| | | children: [ |
| | | const Text( |
| | | "追剧", |
| | | style: TextStyle( |
| | | fontSize: 18, color: Color(0xFF888888)), |
| | | ), |
| | | Text( |
| | | "$favoriteVideoCount", |
| | | style: const TextStyle( |
| | | fontSize: 12, color: Color(0xFF888888)), |
| | | ), |
| | | ], |
| | | ), |
| | | ), |
| | | InkWell( |
| | | onTap: () { |
| | | jumpPage("VideoDownloadPage"); |
| | | }, |
| | | child: Column( |
| | | children: const [ |
| | | Text( |
| | | "缓存", |
| | | style: TextStyle( |
| | | fontSize: 18, color: Color(0xFF888888)), |
| | | ), |
| | | Text( |
| | | "0", |
| | | style: TextStyle( |
| | | fontSize: 12, color: Color(0xFF888888)), |
| | | ), |
| | | ], |
| | | )), |
| | | ], |
| | | ), |
| | | //历史记录 |
| | | Container( |
| | | height: 20, |
| | | ), |
| | | recordList.isNotEmpty |
| | | ? Column( |
| | | children: [ |
| | | //title |
| | | InkWell( |
| | | onTap: () { |
| | | jumpPage("VideoScanRecordPage"); |
| | | }, |
| | | child: Row( |
| | | children: [ |
| | | Container( |
| | | width: 16, |
| | | ), |
| | | const Text( |
| | | "历史记录", |
| | | style: TextStyle( |
| | | color: Color(0xFFAAAAAA), |
| | | fontSize: 15), |
| | | ), |
| | | Expanded(child: Container()), |
| | | const Text( |
| | | "全部", |
| | | style: TextStyle( |
| | | color: Color(0xFFAAAAAA), |
| | | fontSize: 15), |
| | | ), |
| | | Container( |
| | | width: 6, |
| | | ), |
| | | Image.asset( |
| | | "assets/imgs/icon_mine_fun_input.png", |
| | | width: 6.5, |
| | | ), |
| | | Container( |
| | | width: 16, |
| | | ), |
| | | ], |
| | | )), |
| | | //内容 |
| | | Container( |
| | | margin: const EdgeInsets.only(top: 15), |
| | | height: 270, |
| | | child: SingleChildScrollView( |
| | | scrollDirection: Axis.horizontal, |
| | | child: Container( |
| | | constraints: BoxConstraints( |
| | | minWidth: MediaQuery.of(context) |
| | | .size |
| | | .width), |
| | | alignment: Alignment.centerLeft, |
| | | child: Wrap( |
| | | direction: Axis.vertical, |
| | | alignment: WrapAlignment.start, |
| | | spacing: 10, |
| | | runSpacing: 10, |
| | | children: getRecordListWidget()))), |
| | | ) |
| | | ], |
| | | ) |
| | | : Container(), |
| | | |
| | | //功能 |
| | | Container( |
| | | margin: const EdgeInsets.fromLTRB(16, 23, 16, 16), |
| | | padding: const EdgeInsets.fromLTRB(24, 10, 24, 10), |
| | | decoration: BoxDecoration( |
| | | borderRadius: BorderRadius.circular(12.5), |
| | | border: Border.all( |
| | | color: const Color(0xFFDDDDDD), width: 1)), |
| | | child: Column( |
| | | children: [ |
| | | functionWidget( |
| | | "assets/imgs/icon_mine_fun_share.png", "分享APP", 16, |
| | | () { |
| | | Share.share("海量韩剧,尽在应用:$applink"); |
| | | }), |
| | | functionWidget("assets/imgs/icon_mine_fun_privacy.png", |
| | | "隐私政策", 13.5, () { |
| | | jumpPage("BrowserPage", params: {"url": Constant.PRIVACY_URL,"title":"隐私政策"}); |
| | | }), |
| | | functionWidget("assets/imgs/icon_mine_fun_feedback.png", |
| | | "反馈吐槽", 13.5, () { |
| | | jumpPage("AdvicePage"); |
| | | }), |
| | | functionWidget("assets/imgs/icon_mine_fun_about_us.png", |
| | | "关于我们", 17, () { |
| | | MySharedPreferences.getInstance() |
| | | .getString("aboutUsLink") |
| | | .then((value) { |
| | | if (!StringUtil.isNullOrEmpty(value)) { |
| | | jumpPage("AboutUsPage"); |
| | | } |
| | | }); |
| | | }), |
| | | functionWidget("assets/imgs/icon_mine_fun_settings.png", |
| | | "设置", 17.5, () { |
| | | jumpPage("SettingPage"); |
| | | }), |
| | | ], |
| | | ), |
| | | ) |
| | | ], |
| | | )), |
| | | )) |
| | | ], |
| | | )); |
| | | } |
| | | |
| | | List<Widget> getRecordListWidget() { |
| | | List<Widget> list = recordList.map((e) { |
| | | //是否为主分类 |
| | | List<int> mainTypes = [150, 151, 152, 153]; |
| | | if (mainTypes.contains(e.video!.videoType!.id!)) { |
| | | return InkWell( |
| | | child: getBigRecordItem(e), |
| | | onTap: () { |
| | | jumpVideoDetail(context, e.video, Platform.isIOS, |
| | | position: e.position!); |
| | | }, |
| | | ); |
| | | } else { |
| | | return InkWell( |
| | | child: getSmallRecordItem(e), |
| | | onTap: () { |
| | | jumpVideoDetail(context, e.video, Platform.isIOS, |
| | | position: e.position!); |
| | | }, |
| | | ); |
| | | } |
| | | }).toList(); |
| | | list.insert( |
| | | 0, |
| | | InkWell( |
| | | child: Container( |
| | | width: 5, |
| | | height: 270, |
| | | ))); |
| | | list.add(InkWell( |
| | | child: Container( |
| | | width: 5, |
| | | height: 270, |
| | | ))); |
| | | return list; |
| | | } |
| | | |
| | | Widget getSmallRecordItem(WatchRecordModel record) { |
| | | return Container( |
| | | height: 130, |
| | | child: Stack( |
| | | children: [ |
| | | Container( |
| | | margin: const EdgeInsets.only(top: 20), |
| | | height: 110, |
| | | width: 120 * 1.4, |
| | | decoration: BoxDecoration( |
| | | color: Colors.black38, |
| | | borderRadius: BorderRadius.circular(10)), |
| | | ), |
| | | Container( |
| | | alignment: Alignment.center, |
| | | width: 120 * 1.4, |
| | | padding: const EdgeInsets.only(left: 10, right: 10), |
| | | child: Column( |
| | | children: [ |
| | | ClipRRect( |
| | | borderRadius: BorderRadius.circular(10), |
| | | child: SizedBox( |
| | | height: 100, |
| | | width: 120 * 1.4 - 20, |
| | | child: VideoImage(record.video!.picture), |
| | | ), |
| | | ), |
| | | Container( |
| | | height: 5, |
| | | ), |
| | | Text( |
| | | record.video!.name!, |
| | | maxLines: 1, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: const TextStyle(color: Colors.white), |
| | | ) |
| | | ], |
| | | )) |
| | | ], |
| | | )); |
| | | } |
| | | |
| | | Widget getBigRecordItem(WatchRecordModel record) { |
| | | return Stack( |
| | | children: [ |
| | | Container( |
| | | margin: const EdgeInsets.only(top: 20), |
| | | height: 250, |
| | | width: 150, |
| | | decoration: BoxDecoration( |
| | | color: Colors.black38, borderRadius: BorderRadius.circular(10)), |
| | | ), |
| | | Container( |
| | | alignment: Alignment.center, |
| | | width: 150, |
| | | padding: EdgeInsets.only(left: 10, right: 10), |
| | | child: Column( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | ClipRRect( |
| | | borderRadius: BorderRadius.circular(10), |
| | | child: Container( |
| | | height: 200, |
| | | child: VideoImage( |
| | | record.video!.vpicture ?? record.video!.picture), |
| | | ), |
| | | ), |
| | | Container( |
| | | height: 5, |
| | | ), |
| | | Text( |
| | | record.video!.name!, |
| | | maxLines: 2, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: const TextStyle(color: Colors.white), |
| | | ) |
| | | ], |
| | | )) |
| | | ], |
| | | ); |
| | | } |
| | | |
| | | Widget portraitWidget() { |
| | | return Container( |
| | | width: 80, |
| | | height: 80, |
| | | padding: const EdgeInsets.all(2), |
| | | alignment: Alignment.center, |
| | | decoration: BoxDecoration( |
| | | color: const Color(0xFFAAAAAA), |
| | | borderRadius: BorderRadius.circular(40)), |
| | | child: ClipRRect( |
| | | borderRadius: BorderRadius.circular(40), |
| | | child: CommonImage( |
| | | _user == null |
| | | ? "assets/imgs/ic_portrait_default.png" |
| | | : _user!.portrait!, |
| | | defaultWidget: |
| | | Image.asset("assets/imgs/ic_portrait_default.png")), |
| | | )); |
| | | } |
| | | |
| | | Widget loginBtnWidget() { |
| | | return MyFillButton( |
| | | "立即登录", |
| | | 12.5, |
| | | width: 138, |
| | | fontSize: 14, |
| | | onClick: () { |
| | | jumpPage("EmailLoginPage"); |
| | | }, |
| | | ); |
| | | } |
| | | |
| | | Widget personInfoWidget() { |
| | | return Column( |
| | | children: [ |
| | | Text(_user!.nickname!, |
| | | maxLines: 1, |
| | | style: const TextStyle( |
| | | fontSize: 18, |
| | | color: ColorConstant.theme, |
| | | )), |
| | | Container( |
| | | height: 6, |
| | | ), |
| | | Text("ID:${_user!.id!}", |
| | | style: const TextStyle( |
| | | fontSize: 15, |
| | | color: Color(0xFFA9A9A9), |
| | | )), |
| | | ], |
| | | ); |
| | | } |
| | | |
| | | Widget signWidget() { |
| | | return Container( |
| | | padding: const EdgeInsets.only(top: 21.5, left: 12.5, right: 12.5), |
| | | decoration: BoxDecoration( |
| | | gradient: LinearGradient( |
| | | begin: Alignment.topCenter, |
| | | end: Alignment.bottomCenter, |
| | | colors: [ |
| | | ColorConstant.theme.withAlpha(0), |
| | | ColorConstant.theme.withAlpha(108), |
| | | ], |
| | | ), |
| | | ), |
| | | child: Container( |
| | | height: 64, |
| | | padding: const EdgeInsets.only(left: 23, right: 23), |
| | | alignment: Alignment.centerLeft, |
| | | decoration: const BoxDecoration( |
| | | color: ColorConstant.theme, |
| | | borderRadius: BorderRadius.only( |
| | | topLeft: Radius.circular(15), topRight: Radius.circular(15))), |
| | | child: Text( |
| | | "个性签名:${(_user == null || _user!.sign == null || _user!.sign!.isEmpty) ? "这个人非常有趣,但是什么也没写~" : _user!.sign}", |
| | | maxLines: 3, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: const TextStyle(fontSize: 12, color: Colors.white), |
| | | ), |
| | | ), |
| | | ); |
| | | } |
| | | |
| | | Widget functionWidget( |
| | | String icon, String text, double width, GestureTapCallback click) { |
| | | return InkWell( |
| | | onTap: () { |
| | | click(); |
| | | }, |
| | | child: Container( |
| | | padding: const EdgeInsets.only(top: 15, bottom: 15), |
| | | child: Row( |
| | | children: [ |
| | | Container( |
| | | width: 20, |
| | | alignment: Alignment.center, |
| | | child: Image.asset( |
| | | icon, |
| | | width: width, |
| | | )), |
| | | Container( |
| | | width: 10, |
| | | ), |
| | | Text( |
| | | text, |
| | | style: const TextStyle(color: Color(0xFFAAAAAA), fontSize: 15), |
| | | ), |
| | | Expanded(child: Container()), |
| | | Image.asset( |
| | | "assets/imgs/icon_mine_fun_input.png", |
| | | width: 6.5, |
| | | ), |
| | | ], |
| | | )), |
| | | ); |
| | | } |
| | | } |
New file |
| | |
| | | /// type : "csj" |
| | | /// pid : "123456" |
| | | |
| | | class AdinfoModel { |
| | | AdinfoModel({ |
| | | String? type, |
| | | String? pid,}){ |
| | | _type = type; |
| | | _pid = pid; |
| | | } |
| | | |
| | | AdinfoModel.fromJson(dynamic json) { |
| | | _type = json['type']; |
| | | _pid = json['pid']; |
| | | } |
| | | String? _type; |
| | | String? _pid; |
| | | |
| | | String? get type => _type; |
| | | String? get pid => _pid; |
| | | |
| | | Map<String, dynamic> toJson() { |
| | | final map = <String, dynamic>{}; |
| | | map['type'] = _type; |
| | | map['pid'] = _pid; |
| | | return map; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | import '../../api/http.dart'; |
| | | |
| | | class HttpRequestResult { |
| | | final Map<String, dynamic>? data; |
| | | final bool success; |
| | | final String? msg; |
| | | |
| | | HttpRequestResult(this.success, this.data, {this.msg}); |
| | | } |
| | | |
| | | class HttpRequestListener { |
| | | OnHttpRequestFinish? onFinish; |
| | | OnHttpRequestStart? onStart; |
| | | |
| | | HttpRequestListener({this.onStart, this.onFinish}); |
| | | } |
New file |
| | |
| | | /// Id : "1003703" |
| | | /// Email : "2780501319@qq.com" |
| | | /// Nickname : "测试" |
| | | /// Portrait : "http://buwan-1255749512.file.myqcloud.com/portrait_1640746276175.jpg" |
| | | /// Sex : "0" |
| | | /// Sign : "" |
| | | |
| | | class UserInfo { |
| | | UserInfo({ |
| | | String? id, |
| | | String? email, |
| | | String? nickname, |
| | | String? portrait, |
| | | String? sex, |
| | | String? sign, |
| | | String? birthday, |
| | | }) { |
| | | _id = id; |
| | | _email = email; |
| | | _nickname = nickname; |
| | | _portrait = portrait; |
| | | _sex = sex; |
| | | _sign = sign; |
| | | _birthday = birthday; |
| | | } |
| | | |
| | | UserInfo.fromJson(dynamic json) { |
| | | _id = json['Id']; |
| | | _email = json['Email']; |
| | | _nickname = json['Nickname']; |
| | | _portrait = json['Portrait']; |
| | | _sex = json['Sex']; |
| | | _sign = json['Sign']; |
| | | _birthday = json['Birthday']; |
| | | } |
| | | |
| | | String? _id; |
| | | String? _email; |
| | | String? _nickname; |
| | | String? _portrait; |
| | | String? _sex; |
| | | String? _sign; |
| | | int? _vipExpireTime; |
| | | String? _birthday; |
| | | |
| | | String? get id => _id; |
| | | |
| | | String? get email => _email; |
| | | |
| | | String? get nickname => _nickname; |
| | | |
| | | String? get portrait => _portrait; |
| | | |
| | | String? get sex => _sex; |
| | | |
| | | String? get sign => _sign; |
| | | |
| | | int? get vipExpireTime => _vipExpireTime; |
| | | |
| | | String? get birthday => _birthday; |
| | | |
| | | Map<String, dynamic> toJson() { |
| | | final map = <String, dynamic>{}; |
| | | map['Id'] = _id; |
| | | map['Email'] = _email; |
| | | map['Nickname'] = _nickname; |
| | | map['Portrait'] = _portrait; |
| | | map['Sex'] = _sex; |
| | | map['Sign'] = _sign; |
| | | map['Birthday'] = _birthday; |
| | | return map; |
| | | } |
| | | } |
New file |
| | |
| | | import '../../model/video/video_model.dart'; |
| | | |
| | | /// Id : "728" |
| | | /// Picture : "https://hbimg.huabanimg.com/e96071279c6ee30f7e9ab54eac68738db2c0d77d1a123-DqVlxn_fw658/format/webp" |
| | | /// Createtime : "1629191385368" |
| | | /// Video : {"Id":"257727","Tag":"16集全","Createtime":1440729119000,"Picture":"http://pic6.iqiyipic.com/image/20200609/ac/2b/a_100014741_m_601_m17.jpg","Name":"太阳的后裔","Introduction":"","Duration":"3006","MainActor":"","Year":"2016","Score":"9.3","Share":"0","WatchCount":"137468","NowNumber":"1","Finish":"1","Area":"韩国","Vpicture":"http://pic6.iqiyipic.com/image/20200609/ac/2b/a_100014741_m_601_m17_260_360.jpg","Hpicture":"http://pic6.iqiyipic.com/image/20200609/ac/2b/a_100014741_m_601_m17_480_270.jpg","CanSave":false,"VideoType":{"Id":150,"Name":"电视剧","Icon":"https://hbimg.huabanimg.com/40b7d467ac5bd3abdedb70aa8d29dbc144d27fc51876-hvReYJ_fw658/format/webp","Createtime":"0"},"CommentCount":0,"ThirdType":"0","ShowType":0,"Free":0,"Definition":0,"VideoDetailList":[]} |
| | | /// Introduction : "太阳的后裔" |
| | | /// LinkType : 1 |
| | | |
| | | class HomeAdModel { |
| | | HomeAdModel( |
| | | {String? id, |
| | | String? picture, |
| | | String? createtime, |
| | | VideoInfoModel? video, |
| | | String? introduction, |
| | | int? linkType, |
| | | String? params}) { |
| | | _id = id; |
| | | _picture = picture; |
| | | _createtime = createtime; |
| | | _video = video; |
| | | _introduction = introduction; |
| | | _linkType = linkType; |
| | | _params = params; |
| | | } |
| | | |
| | | HomeAdModel.fromJson(dynamic json) { |
| | | _id = json['Id']; |
| | | _picture = json['Picture']; |
| | | _createtime = json['Createtime']; |
| | | _video = |
| | | json['Video'] != null ? VideoInfoModel.fromJson(json['Video']) : null; |
| | | _introduction = json['Introduction']; |
| | | _linkType = json['LinkType']; |
| | | _params = json['Params']; |
| | | } |
| | | |
| | | String? _id; |
| | | String? _picture; |
| | | String? _createtime; |
| | | VideoInfoModel? _video; |
| | | String? _introduction; |
| | | int? _linkType; |
| | | String? _params; |
| | | |
| | | String? get id => _id; |
| | | |
| | | String? get picture => _picture; |
| | | |
| | | String? get createtime => _createtime; |
| | | |
| | | VideoInfoModel? get video => _video; |
| | | |
| | | String? get introduction => _introduction; |
| | | |
| | | int? get linkType => _linkType; |
| | | |
| | | String? get params => _params; |
| | | |
| | | Map<String, dynamic> toJson() { |
| | | final map = <String, dynamic>{}; |
| | | map['Id'] = _id; |
| | | map['Picture'] = _picture; |
| | | map['Createtime'] = _createtime; |
| | | if (_video != null) { |
| | | map['Video'] = _video?.toJson(); |
| | | } |
| | | map['Introduction'] = _introduction; |
| | | map['LinkType'] = _linkType; |
| | | map['Params'] = _params; |
| | | return map; |
| | | } |
| | | } |
New file |
| | |
| | | import '../../model/video/video_model.dart'; |
| | | |
| | | /// Id : "134" |
| | | /// Name : "大家都在看" |
| | | /// Createtime : "1640316645416" |
| | | /// HomeVideoList : [{"Id":"77684","Video":{"Id":""},"Picture":"","Tag":"","Orderby":90,"BigPicture":false,"VideoId":"38abe4cc5c733290101fa0699dbace2a","Createtime":"1640340085417"}] |
| | | /// Activity : "" |
| | | /// Params : "" |
| | | /// HasMore : false |
| | | /// NeedAd : false |
| | | /// IosControl : "" |
| | | /// Icon : "" |
| | | /// Columns : 3 |
| | | /// Number : 18 |
| | | /// RefreshPosition : 0 |
| | | /// MoreTag : "" |
| | | /// Count : 19 |
| | | |
| | | class HomeTypeModel { |
| | | HomeTypeModel({ |
| | | String? id, |
| | | String? name, |
| | | String? createtime, |
| | | List<HomeVideoModel>? homeVideoList, |
| | | String? activity, |
| | | String? params, |
| | | bool? hasMore, |
| | | bool? needAd, |
| | | String? iosControl, |
| | | String? icon, |
| | | int? columns, |
| | | int? number, |
| | | int? refreshPosition, |
| | | String? moreTag, |
| | | int? count, |
| | | }) { |
| | | _id = id; |
| | | _name = name; |
| | | _createtime = createtime; |
| | | _homeVideoList = homeVideoList; |
| | | _activity = activity; |
| | | _params = params; |
| | | _hasMore = hasMore; |
| | | _needAd = needAd; |
| | | _iosControl = iosControl; |
| | | _icon = icon; |
| | | _columns = columns; |
| | | _number = number; |
| | | _refreshPosition = refreshPosition; |
| | | _moreTag = moreTag; |
| | | _count = count; |
| | | } |
| | | |
| | | HomeTypeModel.fromJson(dynamic json) { |
| | | _id = json['Id']; |
| | | _name = json['Name']; |
| | | _createtime = json['Createtime']; |
| | | if (json['HomeVideoList'] != null) { |
| | | _homeVideoList = []; |
| | | json['HomeVideoList'].forEach((v) { |
| | | _homeVideoList?.add(HomeVideoModel.fromJson(v)); |
| | | }); |
| | | } |
| | | _activity = json['Activity']; |
| | | _params = json['Params']; |
| | | _hasMore = json['HasMore']; |
| | | _needAd = json['NeedAd']; |
| | | _iosControl = json['IosControl']; |
| | | _icon = json['Icon']; |
| | | _columns = json['Columns']; |
| | | _number = json['Number']; |
| | | _refreshPosition = json['RefreshPosition']; |
| | | _moreTag = json['MoreTag']; |
| | | _count = json['Count']; |
| | | } |
| | | |
| | | String? _id; |
| | | String? _name; |
| | | String? _createtime; |
| | | List<HomeVideoModel>? _homeVideoList; |
| | | String? _activity; |
| | | String? _params; |
| | | bool? _hasMore; |
| | | bool? _needAd; |
| | | String? _iosControl; |
| | | String? _icon; |
| | | int? _columns; |
| | | int? _number; |
| | | int? _refreshPosition; |
| | | String? _moreTag; |
| | | int? _count; |
| | | |
| | | String? get id => _id; |
| | | |
| | | String? get name => _name; |
| | | |
| | | String? get createtime => _createtime; |
| | | |
| | | List<HomeVideoModel>? get homeVideoList => _homeVideoList; |
| | | |
| | | String? get activity => _activity; |
| | | |
| | | String? get params => _params; |
| | | |
| | | bool? get hasMore => _hasMore; |
| | | |
| | | bool? get needAd => _needAd; |
| | | |
| | | String? get iosControl => _iosControl; |
| | | |
| | | String? get icon => _icon; |
| | | |
| | | int? get columns => _columns; |
| | | |
| | | int? get number => _number; |
| | | |
| | | int? get refreshPosition => _refreshPosition; |
| | | |
| | | String? get moreTag => _moreTag; |
| | | |
| | | int? get count => _count; |
| | | |
| | | Map<String, dynamic> toJson() { |
| | | final map = <String, dynamic>{}; |
| | | map['Id'] = _id; |
| | | map['Name'] = _name; |
| | | map['Createtime'] = _createtime; |
| | | if (_homeVideoList != null) { |
| | | map['HomeVideoList'] = _homeVideoList?.map((v) => v.toJson()).toList(); |
| | | } |
| | | map['Activity'] = _activity; |
| | | map['Params'] = _params; |
| | | map['HasMore'] = _hasMore; |
| | | map['NeedAd'] = _needAd; |
| | | map['IosControl'] = _iosControl; |
| | | map['Icon'] = _icon; |
| | | map['Columns'] = _columns; |
| | | map['Number'] = _number; |
| | | map['RefreshPosition'] = _refreshPosition; |
| | | map['MoreTag'] = _moreTag; |
| | | map['Count'] = _count; |
| | | return map; |
| | | } |
| | | } |
| | | |
| | | /// Id : "77684" |
| | | /// Video : {"Id":""} |
| | | /// Picture : "" |
| | | /// Tag : "" |
| | | /// Orderby : 90 |
| | | /// BigPicture : false |
| | | /// VideoId : "38abe4cc5c733290101fa0699dbace2a" |
| | | /// Createtime : "1640340085417" |
| | | |
| | | class HomeVideoModel { |
| | | HomeVideoModel({ |
| | | String? id, |
| | | VideoInfoModel? video, |
| | | String? picture, |
| | | String? tag, |
| | | int? orderby, |
| | | bool? bigPicture, |
| | | String? videoId, |
| | | }) { |
| | | _id = id; |
| | | _video = video; |
| | | _picture = picture; |
| | | _tag = tag; |
| | | _orderby = orderby; |
| | | _bigPicture = bigPicture; |
| | | _videoId = videoId; |
| | | } |
| | | |
| | | HomeVideoModel.fromJson(dynamic json) { |
| | | _id = json['Id']; |
| | | _video = |
| | | json['Video'] != null ? VideoInfoModel.fromJson(json['Video']) : null; |
| | | _picture = json['Picture']; |
| | | _tag = json['Tag']; |
| | | _orderby = json['Orderby']; |
| | | _bigPicture = json['BigPicture']; |
| | | _videoId = json['VideoId']; |
| | | } |
| | | |
| | | String? _id; |
| | | VideoInfoModel? _video; |
| | | String? _picture; |
| | | String? _tag; |
| | | int? _orderby; |
| | | bool? _bigPicture; |
| | | String? _videoId; |
| | | |
| | | String? get id => _id; |
| | | |
| | | VideoInfoModel? get video => _video; |
| | | |
| | | String? get picture => _picture; |
| | | |
| | | String? get tag => _tag; |
| | | |
| | | int? get orderby => _orderby; |
| | | |
| | | bool? get bigPicture => _bigPicture; |
| | | |
| | | String? get videoId => _videoId; |
| | | |
| | | |
| | | Map<String, dynamic> toJson() { |
| | | final map = <String, dynamic>{}; |
| | | map['Id'] = _id; |
| | | if (_video != null) { |
| | | map['Video'] = _video?.toJson(); |
| | | } |
| | | map['Picture'] = _picture; |
| | | map['Tag'] = _tag; |
| | | map['Orderby'] = _orderby; |
| | | map['BigPicture'] = _bigPicture; |
| | | map['VideoId'] = _videoId; |
| | | return map; |
| | | } |
| | | } |
New file |
| | |
| | | /// icon : "https://buwan-1255749512.cos.ap-guangzhou.myqcloud.com/resource/hanju/icon_tv.png" |
| | | /// name : "电视剧" |
| | | /// id : "2#hanju-tv" |
| | | |
| | | class SearchSpecialModel { |
| | | SearchSpecialModel({ |
| | | String? icon, |
| | | String? name, |
| | | String? id,}){ |
| | | _icon = icon; |
| | | _name = name; |
| | | _id = id; |
| | | } |
| | | |
| | | SearchSpecialModel.fromJson(dynamic json) { |
| | | _icon = json['icon']; |
| | | _name = json['name']; |
| | | _id = json['id']; |
| | | } |
| | | String? _icon; |
| | | String? _name; |
| | | String? _id; |
| | | |
| | | String? get icon => _icon; |
| | | String? get name => _name; |
| | | String? get id => _id; |
| | | |
| | | Map<String, dynamic> toJson() { |
| | | final map = <String, dynamic>{}; |
| | | map['icon'] = _icon; |
| | | map['name'] = _name; |
| | | map['id'] = _id; |
| | | return map; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | /// Picture : "http://pic2.iqiyipic.com/image/20200902/65/f6/a_50181443_m_601_m3.jpg" |
| | | /// Name : "绅士的品格" |
| | | /// UpdateInfo : "更新:2年前" |
| | | /// IsAttention : false |
| | | |
| | | class VideoAttentionModel { |
| | | VideoAttentionModel({ |
| | | String? picture, |
| | | String? name, |
| | | String? updateInfo, |
| | | bool? attention,}){ |
| | | _picture = picture; |
| | | _name = name; |
| | | _updateInfo = updateInfo; |
| | | _attention = attention; |
| | | } |
| | | |
| | | VideoAttentionModel.fromJson(dynamic json) { |
| | | _picture = json['Picture']; |
| | | _name = json['Name']; |
| | | _updateInfo = json['UpdateInfo']; |
| | | _attention = json['Attention']; |
| | | } |
| | | String? _picture; |
| | | String? _name; |
| | | String? _updateInfo; |
| | | bool? _attention; |
| | | |
| | | String? get picture => _picture; |
| | | String? get name => _name; |
| | | String? get updateInfo => _updateInfo; |
| | | bool? get attention => _attention; |
| | | |
| | | |
| | | set attention(value) { |
| | | _attention = value; |
| | | } |
| | | |
| | | |
| | | Map<String, dynamic> toJson() { |
| | | final map = <String, dynamic>{}; |
| | | map['Picture'] = _picture; |
| | | map['Name'] = _name; |
| | | map['UpdateInfo'] = _updateInfo; |
| | | map['Attention'] = _attention; |
| | | return map; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | class VideoInfoModel { |
| | | VideoInfoModel({ |
| | | String? id, |
| | | String? picture, |
| | | String? name, |
| | | String? introduction, |
| | | String? duration, |
| | | String? mainActor, |
| | | String? year, |
| | | VideoType? videoType, |
| | | int? thirdType, |
| | | String? createtime, |
| | | String? score, |
| | | String? watchCount, |
| | | String? share, |
| | | bool? canSave, |
| | | String? tag, |
| | | List<VideoResource>? resourceList, |
| | | String? playPicture, |
| | | int? showType, |
| | | String? vpicture, |
| | | String? hpicture, |
| | | int? commentCount, |
| | | String? updatetime, |
| | | String? area, |
| | | String? nowNumber, |
| | | String? finish, |
| | | List<VideoDetailInfo>? videoDetailList, |
| | | }) { |
| | | _id = id; |
| | | _picture = picture; |
| | | _name = name; |
| | | _introduction = introduction; |
| | | _duration = duration; |
| | | _mainActor = mainActor; |
| | | _year = year; |
| | | _videoType = videoType; |
| | | _score = score; |
| | | _watchCount = watchCount; |
| | | _share = share; |
| | | _canSave = canSave; |
| | | _tag = tag; |
| | | _resourceList = resourceList; |
| | | _playPicture = playPicture; |
| | | _showType = showType; |
| | | _vpicture = vpicture; |
| | | _hpicture = hpicture; |
| | | _commentCount = commentCount; |
| | | _updatetime = updatetime; |
| | | _area = area; |
| | | _nowNumber = nowNumber; |
| | | _finish = finish; |
| | | _videoDetailList = videoDetailList; |
| | | } |
| | | |
| | | VideoInfoModel.fromJson(dynamic json) { |
| | | _id = json['Id']; |
| | | _picture = json['Picture']; |
| | | _name = json['Name']; |
| | | _introduction = json['Introduction']; |
| | | _duration = json['Duration']; |
| | | _mainActor = json['MainActor']; |
| | | _year = json['Year']; |
| | | _videoType = json['VideoType'] != null |
| | | ? VideoType.fromJson(json['VideoType']) |
| | | : null; |
| | | _score = json['Score']; |
| | | _watchCount = json['WatchCount']; |
| | | _share = json['Share']; |
| | | _canSave = json['CanSave']; |
| | | _tag=json['Tag']; |
| | | if (json['ResourceList'] != null) { |
| | | _resourceList = []; |
| | | json['ResourceList'].forEach((v) { |
| | | _resourceList?.add(VideoResource.fromJson(v)); |
| | | }); |
| | | } |
| | | _playPicture = json['PlayPicture']; |
| | | _showType = json['ShowType']; |
| | | _vpicture = json['Vpicture']; |
| | | _hpicture = json['Hpicture']; |
| | | _commentCount = json['CommentCount']; |
| | | _updatetime = json['Updatetime']; |
| | | _area = json['Area']; |
| | | _nowNumber = json['NowNumber']; |
| | | _finish = json['Finish']; |
| | | if (json['VideoDetailList'] != null) { |
| | | _videoDetailList = []; |
| | | json['VideoDetailList'].forEach((v) { |
| | | _videoDetailList?.add(VideoDetailInfo.fromJson(v)); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | String? _id; |
| | | String? _picture; |
| | | String? _name; |
| | | String? _introduction; |
| | | String? _duration; |
| | | String? _mainActor; |
| | | String? _year; |
| | | VideoType? _videoType; |
| | | String? _score; |
| | | String? _watchCount; |
| | | String? _share; |
| | | bool? _canSave; |
| | | String? _tag; |
| | | List<VideoResource>? _resourceList; |
| | | String? _playPicture; |
| | | int? _showType; |
| | | String? _vpicture; |
| | | String? _hpicture; |
| | | int? _commentCount; |
| | | String? _updatetime; |
| | | String? _area; |
| | | String? _nowNumber; |
| | | String? _finish; |
| | | List<VideoDetailInfo>? _videoDetailList; |
| | | |
| | | String? get id => _id; |
| | | |
| | | String? get picture => _picture; |
| | | |
| | | String? get name => _name; |
| | | |
| | | String? get introduction => _introduction; |
| | | |
| | | String? get duration => _duration; |
| | | |
| | | String? get mainActor => _mainActor; |
| | | |
| | | String? get year => _year; |
| | | |
| | | VideoType? get videoType => _videoType; |
| | | |
| | | |
| | | |
| | | String? get score => _score; |
| | | |
| | | String? get watchCount => _watchCount; |
| | | |
| | | String? get share => _share; |
| | | |
| | | bool? get canSave => _canSave; |
| | | |
| | | String? get tag => _tag; |
| | | |
| | | List<VideoResource>? get resourceList => _resourceList; |
| | | |
| | | String? get playPicture => _playPicture; |
| | | |
| | | int? get showType => _showType; |
| | | |
| | | String? get vpicture => _vpicture; |
| | | |
| | | String? get hpicture => _hpicture; |
| | | |
| | | int? get commentCount => _commentCount; |
| | | |
| | | String? get updatetime => _updatetime; |
| | | |
| | | String? get area => _area; |
| | | |
| | | String? get nowNumber => _nowNumber; |
| | | |
| | | String? get finish => _finish; |
| | | |
| | | List<VideoDetailInfo>? get videoDetailList => _videoDetailList; |
| | | |
| | | set videoDetailList(value) { |
| | | _videoDetailList = value; |
| | | } |
| | | |
| | | set tag(value) { |
| | | _tag = value; |
| | | } |
| | | |
| | | Map<String, dynamic> toJson() { |
| | | final map = <String, dynamic>{}; |
| | | map['Id'] = _id; |
| | | map['Picture'] = _picture; |
| | | map['Name'] = _name; |
| | | map['Introduction'] = _introduction; |
| | | map['Duration'] = _duration; |
| | | map['MainActor'] = _mainActor; |
| | | map['Year'] = _year; |
| | | if (_videoType != null) { |
| | | map['VideoType'] = _videoType?.toJson(); |
| | | } |
| | | map['Score'] = _score; |
| | | map['WatchCount'] = _watchCount; |
| | | map['Share'] = _share; |
| | | map['CanSave'] = _canSave; |
| | | map['Tag'] = _tag; |
| | | if (_resourceList != null) { |
| | | map['ResourceList'] = _resourceList?.map((v) => v.toJson()).toList(); |
| | | } |
| | | map['PlayPicture'] = _playPicture; |
| | | map['ShowType'] = _showType; |
| | | map['Vpicture'] = _vpicture; |
| | | map['Hpicture'] = _hpicture; |
| | | map['CommentCount'] = _commentCount; |
| | | map['Updatetime'] = _updatetime; |
| | | map['Area'] = _area; |
| | | map['NowNumber'] = _nowNumber; |
| | | map['Finish'] = _finish; |
| | | if (_videoDetailList != null) { |
| | | map['VideoDetailList'] = |
| | | _videoDetailList?.map((v) => v.toJson()).toList(); |
| | | } |
| | | return map; |
| | | } |
| | | } |
| | | |
| | | /// Introduction : "告密者父女赴神都告密在南市遇害,内卫月华君武思月抢先一步带走嫌疑凶犯,大理寺卿高升却以刺客刺杀长乐郡主为由将其从内卫抢回。\n高秉烛潜入大理寺狱冒充春秋道道众套话凶犯,得知“告密者父女已死,神道大业将成”。\n高秉烛从告密者尸身上看到熟悉的十字贯穿伤,且找到一张公验,表明告密者自奁山而来,想到告密者在南市与百里弘毅见面的一幕,高秉烛怀疑此事和百里弘毅有关。\n内卫奉御郎武攸决授意思月与大理寺联手调查告密者被杀一案,人犯在狱中重伤而亡,大理寺亭长裴谏欲对出入过监牢的人一一审问,此时,高秉烛站出,表示人犯是自己所杀。\n" |
| | | /// Name : "风起洛阳第1集" |
| | | /// Type : "album" |
| | | /// Id : "2152712324153900" |
| | | /// WatchCount : 0 |
| | | /// Tag : "1" |
| | | |
| | | class VideoDetailInfo { |
| | | VideoDetailInfo({ |
| | | String? introduction, |
| | | String? name, |
| | | String? type, |
| | | String? id, |
| | | int? watchCount, |
| | | String? tag, |
| | | }) { |
| | | _introduction = introduction; |
| | | _name = name; |
| | | _type = type; |
| | | _id = id; |
| | | _watchCount = watchCount; |
| | | _tag = tag; |
| | | } |
| | | |
| | | VideoDetailInfo.fromJson(dynamic json) { |
| | | _introduction = json['Introduction']; |
| | | _name = json['Name']; |
| | | _type = json['Type']; |
| | | _id = json['Id']; |
| | | _watchCount = json['WatchCount']; |
| | | _tag = json['Tag']; |
| | | } |
| | | |
| | | String? _introduction; |
| | | String? _name; |
| | | String? _type; |
| | | String? _id; |
| | | int? _watchCount; |
| | | String? _tag; |
| | | |
| | | String? get introduction => _introduction; |
| | | |
| | | String? get name => _name; |
| | | |
| | | String? get type => _type; |
| | | |
| | | String? get id => _id; |
| | | |
| | | int? get watchCount => _watchCount; |
| | | |
| | | String? get tag => _tag; |
| | | |
| | | Map<String, dynamic> toJson() { |
| | | final map = <String, dynamic>{}; |
| | | map['Introduction'] = _introduction; |
| | | map['Name'] = _name; |
| | | map['Type'] = _type; |
| | | map['Id'] = _id; |
| | | map['WatchCount'] = _watchCount; |
| | | map['Tag'] = _tag; |
| | | return map; |
| | | } |
| | | } |
| | | |
| | | /// Id : "22" |
| | | /// Name : "爱奇艺2" |
| | | /// Createtime : "1464659723000" |
| | | /// Picture : "http://buwan-1255749512.cos.ap-guangzhou.myqcloud.com/resource/source/iqiyi.png" |
| | | /// Checked : true |
| | | |
| | | class VideoResource { |
| | | VideoResource({ |
| | | String? id, |
| | | String? name, |
| | | String? createtime, |
| | | String? picture, |
| | | bool? checked, |
| | | }) { |
| | | _id = id; |
| | | _name = name; |
| | | _createtime = createtime; |
| | | _picture = picture; |
| | | _checked = checked; |
| | | } |
| | | |
| | | VideoResource.fromJson(dynamic json) { |
| | | _id = json['Id']; |
| | | _name = json['Name']; |
| | | _createtime = json['Createtime']; |
| | | _picture = json['Picture']; |
| | | _checked = json['Checked']; |
| | | } |
| | | |
| | | String? _id; |
| | | String? _name; |
| | | String? _createtime; |
| | | String? _picture; |
| | | bool? _checked; |
| | | |
| | | String? get id => _id; |
| | | |
| | | String? get name => _name; |
| | | |
| | | String? get createtime => _createtime; |
| | | |
| | | String? get picture => _picture; |
| | | |
| | | bool? get checked => _checked; |
| | | |
| | | Map<String, dynamic> toJson() { |
| | | final map = <String, dynamic>{}; |
| | | map['Id'] = _id; |
| | | map['Name'] = _name; |
| | | map['Createtime'] = _createtime; |
| | | map['Picture'] = _picture; |
| | | map['Checked'] = _checked; |
| | | return map; |
| | | } |
| | | } |
| | | |
| | | /// Id : 150 |
| | | /// Name : "电视剧" |
| | | /// Icon : "https://hbimg.huabanimg.com/40b7d467ac5bd3abdedb70aa8d29dbc144d27fc51876-hvReYJ_fw658/format/webp" |
| | | /// Createtime : "0" |
| | | |
| | | class VideoType { |
| | | VideoType({ |
| | | int? id, |
| | | String? name, |
| | | String? icon, |
| | | String? createtime, |
| | | }) { |
| | | _id = id; |
| | | _name = name; |
| | | _icon = icon; |
| | | _createtime = createtime; |
| | | } |
| | | |
| | | VideoType.fromJson(dynamic json) { |
| | | _id = json['Id']; |
| | | _name = json['Name']; |
| | | _icon = json['Icon']; |
| | | _createtime = json['Createtime']; |
| | | } |
| | | |
| | | int? _id; |
| | | String? _name; |
| | | String? _icon; |
| | | String? _createtime; |
| | | |
| | | int? get id => _id; |
| | | |
| | | String? get name => _name; |
| | | |
| | | String? get icon => _icon; |
| | | |
| | | String? get createtime => _createtime; |
| | | |
| | | Map<String, dynamic> toJson() { |
| | | final map = <String, dynamic>{}; |
| | | map['Id'] = _id; |
| | | map['Name'] = _name; |
| | | map['Icon'] = _icon; |
| | | map['Createtime'] = _createtime; |
| | | return map; |
| | | } |
| | | } |
New file |
| | |
| | | import '../../model/video/video_model.dart'; |
| | | |
| | | /// Resource : {"Id":"22","Name":"爱奇艺2","Createtime":"1464659723000","Picture":"http://buwan-1255749512.cos.ap-guangzhou.myqcloud.com/resource/source/iqiyi.png","Checked":false} |
| | | /// Url : "http://m.iqiyi.com/v_19rrk83jpg.html?vfm=m_330_hjvap" |
| | | /// PlayType : 1 |
| | | /// Params : "" |
| | | |
| | | class VideoPlayUrlModel { |
| | | VideoPlayUrlModel({ |
| | | VideoResource? resource, |
| | | String? url, |
| | | int? playType, |
| | | String? params, |
| | | }) { |
| | | _resource = resource; |
| | | _url = url; |
| | | _playType = playType; |
| | | _params = params; |
| | | } |
| | | |
| | | VideoPlayUrlModel.fromJson(dynamic json) { |
| | | _resource = json['Resource'] != null |
| | | ? VideoResource.fromJson(json['Resource']) |
| | | : null; |
| | | _url = json['Url']; |
| | | _playType = json['PlayType']; |
| | | _params = json['Params']; |
| | | } |
| | | |
| | | VideoResource? _resource; |
| | | String? _url; |
| | | int? _playType; |
| | | String? _params; |
| | | |
| | | VideoResource? get resource => _resource; |
| | | |
| | | String? get url => _url; |
| | | |
| | | int? get playType => _playType; |
| | | |
| | | String? get params => _params; |
| | | |
| | | Map<String, dynamic> toJson() { |
| | | final map = <String, dynamic>{}; |
| | | if (_resource != null) { |
| | | map['Resource'] = _resource?.toJson(); |
| | | } |
| | | map['Url'] = _url; |
| | | map['PlayType'] = _playType; |
| | | map['Params'] = _params; |
| | | return map; |
| | | } |
| | | } |
New file |
| | |
| | | import '../../model/video/video_model.dart'; |
| | | |
| | | /// id : 123 |
| | | /// videoId : "123123" |
| | | /// video : {"id":"123"} |
| | | /// videoDetail : {"id":"123"} |
| | | /// position : 1 |
| | | /// createTime : 123 |
| | | /// updateTime : 123 |
| | | |
| | | class WatchRecordModel { |
| | | WatchRecordModel({ |
| | | int? id, |
| | | String? videoId, |
| | | VideoInfoModel? video, |
| | | VideoDetailInfo? videoDetail, |
| | | int? position, |
| | | int? createTime, |
| | | int? updateTime,}){ |
| | | _id = id; |
| | | _videoId = videoId; |
| | | _video = video; |
| | | _videoDetail = videoDetail; |
| | | _position = position; |
| | | _createTime = createTime; |
| | | _updateTime = updateTime; |
| | | } |
| | | |
| | | WatchRecordModel.fromJson(dynamic json) { |
| | | _id = json['id']; |
| | | _videoId = json['videoId']; |
| | | _video = json['video'] != null ? VideoInfoModel.fromJson(json['video']) : null; |
| | | _videoDetail = json['videoDetail'] != null ? VideoDetailInfo.fromJson(json['videoDetail']) : null; |
| | | _position = json['position']; |
| | | _createTime = json['createTime']; |
| | | _updateTime = json['updateTime']; |
| | | } |
| | | int? _id; |
| | | String? _videoId; |
| | | VideoInfoModel? _video; |
| | | VideoDetailInfo? _videoDetail; |
| | | int? _position; |
| | | int? _createTime; |
| | | int? _updateTime; |
| | | |
| | | int? get id => _id; |
| | | String? get videoId => _videoId; |
| | | VideoInfoModel? get video => _video; |
| | | VideoDetailInfo? get videoDetail => _videoDetail; |
| | | int? get position => _position; |
| | | int? get createTime => _createTime; |
| | | int? get updateTime => _updateTime; |
| | | |
| | | Map<String, dynamic> toJson() { |
| | | final map = <String, dynamic>{}; |
| | | map['id'] = _id; |
| | | map['videoId'] = _videoId; |
| | | if (_video != null) { |
| | | map['video'] = _video?.toJson(); |
| | | } |
| | | if (_videoDetail != null) { |
| | | map['videoDetail'] = _videoDetail?.toJson(); |
| | | } |
| | | map['position'] = _position; |
| | | map['createTime'] = _createTime; |
| | | map['updateTime'] = _updateTime; |
| | | return map; |
| | | } |
| | | } |
New file |
| | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import 'mine.dart'; |
| | | |
| | | void main(){ |
| | | runApp(MinePage(title: "")); |
| | | } |
| | | |
| | | |
| | | |
New file |
| | |
| | | import 'dart:io'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | import 'package:url_launcher/url_launcher.dart'; |
| | | import 'package:webview_flutter/webview_flutter.dart'; |
| | | |
| | | import '../../ui/widget/nav.dart'; |
| | | import '../../utils/jsinterface.dart'; |
| | | |
| | | class BrowserPage extends StatefulWidget { |
| | | BrowserPage({Key? key, required this.title, required this.url}) |
| | | : super(key: key); |
| | | final String title; |
| | | final String url; |
| | | |
| | | @override |
| | | _BrowserPageState createState() => _BrowserPageState(title, url); |
| | | } |
| | | |
| | | class _BrowserPageState extends State<BrowserPage> |
| | | with SingleTickerProviderStateMixin { |
| | | String title = ""; |
| | | String? url; |
| | | double progress = 0; |
| | | |
| | | _BrowserPageState(this.title, this.url); |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | if (Platform.isAndroid) { |
| | | WebView.platform = SurfaceAndroidWebView(); |
| | | } |
| | | } |
| | | |
| | | WebViewController? _webViewController; |
| | | |
| | | _back() { |
| | | if (_webViewController == null) { |
| | | popPage(context); |
| | | } else { |
| | | _webViewController!.canGoBack().then((value) { |
| | | if (value) { |
| | | _webViewController!.goBack(); |
| | | } else { |
| | | popPage(context); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return WillPopScope( |
| | | onWillPop: () async { |
| | | _back(); |
| | | return false; |
| | | }, |
| | | child: Scaffold( |
| | | backgroundColor: Colors.white, |
| | | body: Flex( |
| | | direction: Axis.vertical, |
| | | children: [ |
| | | TopNavBar( |
| | | title: title, |
| | | back: () { |
| | | _back(); |
| | | }, |
| | | rightIcon: const Icon(Icons.refresh,size: 30,), |
| | | rightClick: (){ |
| | | if(_webViewController!=null){ |
| | | _webViewController!.reload(); |
| | | } |
| | | }, |
| | | ), |
| | | SizedBox( |
| | | height: 1, |
| | | child: LinearProgressIndicator( |
| | | backgroundColor: Colors.white, |
| | | valueColor: const AlwaysStoppedAnimation(ColorConstant.theme), |
| | | value: progress, |
| | | ), |
| | | ), |
| | | Expanded( |
| | | child: WebView( |
| | | //http://192.168.3.122:8848/test/JsTest.html |
| | | initialUrl:url, |
| | | onWebViewCreated: (WebViewController webViewController) { |
| | | _webViewController = webViewController; |
| | | }, |
| | | javascriptMode: JavascriptMode.unrestricted, |
| | | javascriptChannels: |
| | | JavascriptInterface(context, _webViewController) |
| | | .getInterfaces(), |
| | | navigationDelegate: (NavigationRequest request) { |
| | | print("链接:${request.url}"); |
| | | if (!request.url.startsWith("http")) { |
| | | launch(request.url); |
| | | return NavigationDecision.prevent; |
| | | } |
| | | return NavigationDecision.navigate; |
| | | }, |
| | | onPageStarted: (url) { |
| | | print("process:onPageStarted-$url"); |
| | | }, |
| | | onPageFinished: (url) { |
| | | print("process:onPageFinished-$url"); |
| | | _webViewController!.getTitle().then((value) { |
| | | if (value != null) { |
| | | setState(() { |
| | | title = value; |
| | | }); |
| | | } |
| | | }); |
| | | }, |
| | | onProgress: (int process) { |
| | | print("process:$process"); |
| | | setState(() { |
| | | if (process == 100) { |
| | | progress = 0; |
| | | } else { |
| | | progress = process / 100.0; |
| | | } |
| | | }); |
| | | }, |
| | | )) |
| | | ], |
| | | ))); |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import '../api/http.dart'; |
| | | import 'common/browser.dart'; |
| | | import 'widget/dialog.dart'; |
| | | import 'widget/nav.dart'; |
| | | import '../utils/cache_util.dart'; |
| | | import '../utils/config_util.dart'; |
| | | import '../utils/event_bus_util.dart'; |
| | | import '../utils/pageutils.dart'; |
| | | import '../utils/push_util.dart'; |
| | | import '../utils/setting_util.dart'; |
| | | import '../utils/string_util.dart'; |
| | | import '../utils/ui_constant.dart'; |
| | | import '../utils/ui_utils.dart'; |
| | | import '../utils/user_util.dart'; |
| | | import 'package:package_info/package_info.dart'; |
| | | |
| | | void main() { |
| | | runApp(MyApp()); |
| | | } |
| | | |
| | | class MyApp extends StatelessWidget { |
| | | // This widget is the root of your application. |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return MaterialApp( |
| | | title: '视频收藏', |
| | | theme: ThemeData(primaryColor: const Color(0xFFF5F5F5)), |
| | | home: DemoPage(title: ''), |
| | | ); |
| | | } |
| | | } |
| | | |
| | | class DemoPage extends StatefulWidget { |
| | | DemoPage({Key? key, required this.title}) : super(key: key); |
| | | |
| | | // This widget is the home page of your application. It is stateful, meaning |
| | | // that it has a State object (defined below) that contains fields that affect |
| | | // how it looks. |
| | | |
| | | // This class is the configuration for the state. It holds the values (in this |
| | | // case the title) provided by the parent (in this case the App widget) and |
| | | // used by the build method of the State. Fields in a Widget subclass are |
| | | // always marked "final". |
| | | |
| | | final String title; |
| | | |
| | | @override |
| | | _DemoPageState createState() => _DemoPageState(); |
| | | } |
| | | |
| | | class _DemoPageState extends State<DemoPage> |
| | | with SingleTickerProviderStateMixin { |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | } |
| | | |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return Scaffold( |
| | | backgroundColor: const Color(0xFFF5F5F5), |
| | | body: Flex( |
| | | direction: Axis.vertical, |
| | | children: [ |
| | | TopNavBar(title: "我的收藏"), |
| | | InkWell(onTap: (){ |
| | | popPage(context); |
| | | }, child: Text("结束页面"),) |
| | | |
| | | ], |
| | | |
| | | |
| | | )); |
| | | } |
| | | } |
New file |
| | |
| | | import 'package:flutter/material.dart'; |
| | | import 'dart:ui'; |
| | | |
| | | import 'test.dart'; |
| | | |
| | | void main() => runApp(_widgetForRoute(window.defaultRouteName)); |
| | | |
| | | Widget _widgetForRoute(String route) { |
| | | print("flutter page: $route"); |
| | | switch (route) { |
| | | case "home": |
| | | return SplashPage(title: ""); |
| | | } |
| | | |
| | | return Container(); |
| | | } |
New file |
| | |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import '../../api/user_api.dart'; |
| | | |
| | | import '../../model/user/user_info.dart'; |
| | | import '../../ui/common/browser.dart'; |
| | | import '../../ui/mine/advice.dart'; |
| | | import '../../ui/mine/login.dart'; |
| | | import '../../ui/mine/settings.dart'; |
| | | import '../../utils/config_util.dart'; |
| | | import '../../utils/pageutils.dart'; |
| | | import '../../utils/string_util.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | import '../../utils/ui_utils.dart'; |
| | | import '../../utils/user_util.dart'; |
| | | |
| | | void main() { |
| | | runApp(MyApp()); |
| | | } |
| | | |
| | | class MyApp extends StatelessWidget { |
| | | // This widget is the root of your application. |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return MaterialApp( |
| | | title: 'Flutter Demo', |
| | | theme: ThemeData( |
| | | // This is the theme of your application. |
| | | // |
| | | // Try running your application with "flutter run". You'll see the |
| | | // application has a blue toolbar. Then, without quitting the app, try |
| | | // changing the primarySwatch below to Colors.green and then invoke |
| | | // "hot reload" (press "r" in the console where you ran "flutter run", |
| | | // or simply save your changes to "hot reload" in a Flutter IDE). |
| | | // Notice that the counter didn't reset back to zero; the application |
| | | // is not restarted. |
| | | primaryColor: Color.fromARGB(255, 150, 150, 150)), |
| | | home: MinePage(title: ''), |
| | | ); |
| | | } |
| | | } |
| | | |
| | | class MinePage extends StatefulWidget { |
| | | MinePage({Key? key, required this.title}) : super(key: key); |
| | | |
| | | // This widget is the home page of your application. It is stateful, meaning |
| | | // that it has a State object (defined below) that contains fields that affect |
| | | // how it looks. |
| | | |
| | | // This class is the configuration for the state. It holds the values (in this |
| | | // case the title) provided by the parent (in this case the App widget) and |
| | | // used by the build method of the State. Fields in a Widget subclass are |
| | | // always marked "final". |
| | | |
| | | final String title; |
| | | |
| | | @override |
| | | _MinePageState createState() => _MinePageState(); |
| | | } |
| | | |
| | | class _MinePageState extends State<MinePage> |
| | | with AutomaticKeepAliveClientMixin { |
| | | UserInfo? userInfo = null; |
| | | bool isLogin = false; |
| | | bool isVIP = false; |
| | | List<Widget> list = []; |
| | | Widget? adView; |
| | | bool adDeleted = false; |
| | | final List<Functions> data = [ |
| | | Functions("assets/imgs/mine/icon_mine_permission.png", "权限设置", |
| | | "permission", false), |
| | | ]; |
| | | |
| | | void _onClick(Functions function) async { |
| | | if (function.needLogin) { |
| | | bool login = await UserUtil.isLogin(); |
| | | if (!login) { |
| | | NavigatorUtil.navigateToNextPage(context, LoginPage(title: ""), (data) { |
| | | _getUserInfo(); |
| | | }); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | String? uid = await UserUtil.getUid(); |
| | | |
| | | switch (function.key) { |
| | | case "kefu": |
| | | ConfigUtil.getConfig(context, ConfigKey.kefu).then((value) { |
| | | if (!StringUtil.isNullOrEmpty(value)) { |
| | | NavigatorUtil.navigateToNextPage( |
| | | context, BrowserPage(title: "在线客服", url: value!), (data) {}); |
| | | } |
| | | }); |
| | | break; |
| | | |
| | | case "advice": |
| | | NavigatorUtil.navigateToNextPage( |
| | | context, AdvicePage(title: ""), (data) {}); |
| | | break; |
| | | case "protocol": |
| | | NavigatorUtil.navigateToNextPage( |
| | | context, |
| | | BrowserPage( |
| | | title: "用户协议", |
| | | url: Constant.PROTOCOL_URL, |
| | | ), |
| | | (data) {}); |
| | | break; |
| | | case "privacy": |
| | | NavigatorUtil.navigateToNextPage( |
| | | context, |
| | | BrowserPage( |
| | | title: "隐私政策", |
| | | url: Constant.PRIVACY_URL, |
| | | ), |
| | | (data) {}); |
| | | break; |
| | | case "setting": |
| | | NavigatorUtil.navigateToNextPage(context, SettingPage(title: ""), |
| | | (data) { |
| | | _getUserInfo(); |
| | | }); |
| | | |
| | | break; |
| | | } |
| | | } |
| | | |
| | | @override |
| | | void initState() { |
| | | data.forEach((item) { |
| | | list.add(InkWell( |
| | | child: Column( |
| | | children: [ |
| | | Image.asset(item.icon, height: 31), |
| | | Container( |
| | | child: Text( |
| | | item.name, |
| | | style: new TextStyle(color: ColorConstant.theme), |
| | | ), |
| | | margin: const EdgeInsets.fromLTRB(0, 13, 0, 0)) |
| | | ], |
| | | ), |
| | | onTap: () { |
| | | _onClick(item); |
| | | }, |
| | | )); |
| | | }); |
| | | |
| | | //获取用户信息 |
| | | _getUserInfo(); |
| | | |
| | | super.initState(); |
| | | } |
| | | |
| | | void _login() { |
| | | NavigatorUtil.navigateToNextPage(context, LoginPage(title: ""), (data) { |
| | | _getUserInfo(); |
| | | }); |
| | | } |
| | | |
| | | void _loadAd() async { |
| | | if (adView != null) { |
| | | return; |
| | | } |
| | | if (adDeleted) { |
| | | return; |
| | | } |
| | | |
| | | // Widget? ad = AdUtil.loadExpress( |
| | | // await AdUtil.getAdInfo(context, AdPosition.mineExpress), |
| | | // MediaQuery.of(context).size.width - 20, |
| | | // 200, (success, msg) { |
| | | // adDeleted = true; |
| | | // setState(() { |
| | | // adView = null; |
| | | // }); |
| | | // }); |
| | | // |
| | | // setState(() { |
| | | // adView = ad; |
| | | // }); |
| | | } |
| | | |
| | | void _getUserInfo() async { |
| | | var user = await UserUtil.getUserInfo(); |
| | | if (user == null) { |
| | | setState(() { |
| | | isLogin = false; |
| | | userInfo = null; |
| | | }); |
| | | return; |
| | | } |
| | | setState(() { |
| | | isLogin = true; |
| | | userInfo = user; |
| | | }); |
| | | |
| | | Map<String, dynamic>? result = |
| | | await UserApiUtil.getUserInfo(context, user.id!); |
| | | var code = result!["code"]; |
| | | if (code == 0) { |
| | | UserInfo user = UserInfo.fromJson(result["data"]); |
| | | //保存用户信息 |
| | | UserUtil.setUserInfo(user); |
| | | setState(() { |
| | | userInfo = user; |
| | | if (userInfo != null && |
| | | userInfo!.vipExpireTime != null && |
| | | userInfo!.vipExpireTime! > DateTime.now().millisecondsSinceEpoch) { |
| | | isVIP = true; |
| | | } else { |
| | | isVIP = false; |
| | | } |
| | | }); |
| | | } else if (code == 80001) { |
| | | //账号被封禁 |
| | | setState(() { |
| | | isLogin = false; |
| | | }); |
| | | ToastUtil.toast("账号已被封禁",context); |
| | | await UserUtil.logout(); |
| | | } else if (code == 80002) { |
| | | //账号被删除 |
| | | setState(() { |
| | | isLogin = false; |
| | | }); |
| | | ToastUtil.toast("账号已被删除",context); |
| | | await UserUtil.logout(); |
| | | } |
| | | } |
| | | |
| | | Widget getLoginContentView() { |
| | | if (isLogin) { |
| | | return getUserInfoWidget(); |
| | | } else { |
| | | return getLoginBtnWidget(); |
| | | } |
| | | } |
| | | |
| | | Widget getLoginBtnWidget() { |
| | | return Expanded( |
| | | child: InkWell( |
| | | onTap: () { |
| | | _login(); |
| | | }, |
| | | child: Container( |
| | | margin: const EdgeInsets.fromLTRB(10, 0, 10, 0), |
| | | height: 38, |
| | | alignment: Alignment.center, |
| | | child: const Text( |
| | | "登录/注册", |
| | | style: TextStyle(color: Colors.white, fontSize: 15), |
| | | ), |
| | | decoration: const BoxDecoration( |
| | | color: Color(0xFF0E96FF), |
| | | borderRadius: BorderRadius.all(Radius.elliptical(10, 10))), |
| | | ), |
| | | )); |
| | | } |
| | | |
| | | Widget getUserInfoWidget() { |
| | | return Container( |
| | | height: 50, |
| | | padding: EdgeInsets.zero, |
| | | margin: const EdgeInsets.fromLTRB(10, 0, 0, 0), |
| | | alignment: Alignment.topLeft, |
| | | child: Flex( |
| | | direction: Axis.vertical, |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | mainAxisAlignment: MainAxisAlignment.start, |
| | | children: [ |
| | | Text( |
| | | userInfo!.nickname!, |
| | | style: const TextStyle(color: Color(0xFF40C7FF), fontSize: 18), |
| | | ), |
| | | Text( |
| | | "ID:" + userInfo!.id!.toString(), |
| | | style: const TextStyle(color: Color(0xFF40C7FF), fontSize: 12), |
| | | ), |
| | | ], |
| | | ), |
| | | ); |
| | | } |
| | | |
| | | Widget _getVIPLeftTime() { |
| | | List<TextSpan> spanList = [const TextSpan(text: '剩余')]; |
| | | if (userInfo != null && userInfo!.vipExpireTime != null) { |
| | | int leftTime = |
| | | userInfo!.vipExpireTime! - DateTime.now().millisecondsSinceEpoch; |
| | | leftTime = leftTime ~/ 1000; |
| | | if (leftTime >= 60 * 60 * 24) { |
| | | spanList.add(TextSpan( |
| | | text: '${leftTime ~/ (60 * 60 * 24)}', |
| | | style: const TextStyle(fontWeight: FontWeight.bold))); |
| | | spanList.add(const TextSpan(text: '天到期')); |
| | | } else if (leftTime >= 60 * 60) { |
| | | spanList.add(TextSpan( |
| | | text: '${leftTime ~/ (60 * 60)}', |
| | | style: const TextStyle(fontWeight: FontWeight.bold))); |
| | | spanList.add(const TextSpan(text: '小时到期')); |
| | | } else { |
| | | spanList.add(TextSpan( |
| | | text: '${leftTime ~/ (60)}', |
| | | style: const TextStyle(fontWeight: FontWeight.bold))); |
| | | spanList.add(const TextSpan(text: '分钟到期')); |
| | | } |
| | | } |
| | | |
| | | return Text.rich(TextSpan( |
| | | children: spanList, |
| | | style: const TextStyle(color: Colors.white, fontSize: 17.5), |
| | | )); |
| | | } |
| | | |
| | | List<Widget> getVIPContent() { |
| | | List<Widget> list = []; |
| | | list.add(Image.asset( |
| | | "assets/imgs/mine/icon_vip.png", |
| | | width: 44, |
| | | )); |
| | | if (isVIP&&userInfo!=null) { |
| | | list.add(Container( |
| | | margin: const EdgeInsets.fromLTRB(6, 0, 0, 0), |
| | | child: _getVIPLeftTime())); |
| | | } else { |
| | | list.add(Container( |
| | | margin: const EdgeInsets.fromLTRB(6, 0, 0, 0), |
| | | child: Image.asset( |
| | | "assets/imgs/mine/label_vip.png", |
| | | height: 20, |
| | | ), |
| | | )); |
| | | } |
| | | return list; |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | _loadAd(); |
| | | return Scaffold( |
| | | backgroundColor: Colors.white, |
| | | body: SingleChildScrollView( |
| | | child: Column( |
| | | children: [ |
| | | Container( |
| | | decoration: const BoxDecoration( |
| | | image: DecorationImage( |
| | | image: |
| | | AssetImage("assets/imgs/mine/ic_mine_top_bg.png"), |
| | | fit: BoxFit.cover)), |
| | | height: 350, |
| | | width: 400, |
| | | padding: const EdgeInsets.all(10), |
| | | child: Column( |
| | | children: [ |
| | | //登录与用户信息 |
| | | Container( |
| | | margin: EdgeInsets.fromLTRB(0, 105, 0, 0), |
| | | padding: EdgeInsets.all(15), |
| | | alignment: Alignment.centerLeft, |
| | | child: Container( |
| | | height: 50, |
| | | child: Flex( |
| | | direction: Axis.horizontal, |
| | | children: [ |
| | | Image.asset( |
| | | "assets/imgs/mine/icon_mine_default_portrait.png", |
| | | width: 50, |
| | | ), |
| | | //登录按钮 |
| | | getLoginContentView(), |
| | | //信息 |
| | | ], |
| | | )), |
| | | height: 105, |
| | | decoration: const BoxDecoration( |
| | | color: Colors.white, |
| | | borderRadius: |
| | | BorderRadius.all(Radius.elliptical(10, 10)))), |
| | | //会员 |
| | | Container( |
| | | margin: EdgeInsets.fromLTRB(0, 10, 0, 0), |
| | | height: 105, |
| | | width: 400, |
| | | child: Stack( |
| | | children: [ |
| | | Container( |
| | | padding: const EdgeInsets.fromLTRB(18.5, 0, 0, 0), |
| | | child: Flex( |
| | | direction: Axis.vertical, |
| | | mainAxisAlignment: MainAxisAlignment.center, |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | Flex( |
| | | direction: Axis.horizontal, |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | children: getVIPContent(), |
| | | ), |
| | | Container( |
| | | margin: const EdgeInsets.fromLTRB(0, 8, 0, 0), |
| | | child: const Text( |
| | | "定位守护亲友,黑科技保驾护航", |
| | | style: TextStyle( |
| | | fontSize: 15, color: Colors.white), |
| | | ), |
| | | ) |
| | | ], |
| | | ), |
| | | ), |
| | | Positioned( |
| | | right: 0, |
| | | top: 17, |
| | | child: InkWell( |
| | | onTap: () { |
| | | //查看详情或开通 |
| | | print(isVIP ? "查看详情" : "开通会员"); |
| | | UserUtil.isLogin().then((value) { |
| | | if (!value) { |
| | | NavigatorUtil.navigateToNextPage( |
| | | context, LoginPage(title: ""), |
| | | (data) { |
| | | _getUserInfo(); |
| | | }); |
| | | } |
| | | }); |
| | | }, |
| | | child: InkWell( |
| | | onTap: () { |
| | | UserUtil.isLogin().then((value) { |
| | | if (!value) { |
| | | NavigatorUtil.navigateToNextPage( |
| | | context, LoginPage(title: ""), |
| | | (data) { |
| | | _getUserInfo(); |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | ConfigUtil.getConfig(context, |
| | | ConfigKey.vipLink) |
| | | .then((value) { |
| | | if (!StringUtil.isNullOrEmpty( |
| | | value)) { |
| | | NavigatorUtil.navigateToNextPage( |
| | | context, |
| | | BrowserPage( |
| | | title: "会员", |
| | | url: value!), (data) { |
| | | _getUserInfo(); |
| | | }); |
| | | } |
| | | }); |
| | | }); |
| | | }, |
| | | child: Container( |
| | | width: 99, |
| | | height: 26, |
| | | alignment: Alignment.center, |
| | | child: Text( |
| | | isVIP ? "查看详情" : "立即开通", |
| | | style: const TextStyle( |
| | | color: Color(0xFFD4A880)), |
| | | ), |
| | | decoration: const BoxDecoration( |
| | | color: Color(0xFFFAEAB9), |
| | | borderRadius: BorderRadius.only( |
| | | topLeft: Radius.circular(13), |
| | | bottomLeft: |
| | | Radius.circular(13)), |
| | | boxShadow: [ |
| | | BoxShadow( |
| | | color: Color(0x4D0E96FF), |
| | | blurRadius: 2.0, |
| | | offset: Offset(0.0, 3.0), |
| | | //阴影y轴偏移量 |
| | | spreadRadius: 0 //阴影扩散程度 |
| | | ) |
| | | ], |
| | | ))))) |
| | | ], |
| | | ), |
| | | decoration: const BoxDecoration( |
| | | boxShadow: [ |
| | | BoxShadow( |
| | | color: Color(0x4D0E96FF), |
| | | blurRadius: 5.0, |
| | | offset: Offset(0.0, 8.0), //阴影y轴偏移量 |
| | | spreadRadius: 0 //阴影扩散程度 |
| | | ) |
| | | ], |
| | | gradient: LinearGradient( |
| | | stops: [.5, 1], |
| | | colors: [Color(0xFF4699FF), Color(0xFF00DEFF)]), |
| | | color: Colors.white, |
| | | borderRadius: |
| | | BorderRadius.all(Radius.elliptical(10, 10)))) |
| | | ], |
| | | ), |
| | | ), |
| | | |
| | | Container( |
| | | child: adView ?? Container(), |
| | | margin: EdgeInsets.only(left: 10, right: 10), |
| | | ), |
| | | //广告 |
| | | |
| | | //功能区域 |
| | | Container( |
| | | height: 340, |
| | | margin: const EdgeInsets.all(10), |
| | | padding: const EdgeInsets.all(10), |
| | | decoration: const BoxDecoration( |
| | | borderRadius: BorderRadius.all(Radius.elliptical(10, 10)), |
| | | color: Color(0xFFF4FFFF)), |
| | | child: GridView( |
| | | gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( |
| | | crossAxisCount: 4, mainAxisSpacing: 10), |
| | | physics: const NeverScrollableScrollPhysics(), |
| | | children: list), |
| | | ), |
| | | ], |
| | | ))); |
| | | } |
| | | |
| | | @override |
| | | bool get wantKeepAlive => true; |
| | | } |
| | | |
| | | class Functions { |
| | | Functions(this.icon, this.name, this.key, this.needLogin); |
| | | |
| | | String icon; |
| | | String name; |
| | | String key; |
| | | bool needLogin; |
| | | } |
New file |
| | |
| | | import 'dart:io'; |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import '../../ui/common/browser.dart'; |
| | | import '../../utils/config_util.dart'; |
| | | import '../../utils/share_preference.dart'; |
| | | import '../../utils/string_util.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | import '../../ui/widget/nav.dart'; |
| | | import '../../utils/pageutils.dart'; |
| | | |
| | | import 'advice_submit.dart'; |
| | | import 'package:launch_review/launch_review.dart'; |
| | | import 'package:package_info/package_info.dart'; |
| | | |
| | | class AboutUsPage extends StatefulWidget { |
| | | AboutUsPage({Key? key, required this.title}) : super(key: key); |
| | | |
| | | // This widget is the home page of your application. It is stateful, meaning |
| | | // that it has a State object (defined below) that contains fields that affect |
| | | // how it looks. |
| | | |
| | | // This class is the configuration for the state. It holds the values (in this |
| | | // case the title) provided by the parent (in this case the App widget) and |
| | | // used by the build method of the State. Fields in a Widget subclass are |
| | | // always marked "final". |
| | | |
| | | final String title; |
| | | |
| | | @override |
| | | _AboutUsPageState createState() => _AboutUsPageState(); |
| | | } |
| | | |
| | | class _AboutUsPageState extends State<AboutUsPage> |
| | | with SingleTickerProviderStateMixin { |
| | | String versionName = ""; |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | PackageInfo.fromPlatform().then((PackageInfo packageInfo) { |
| | | setState(() { |
| | | versionName = packageInfo.version; |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | BoxDecoration getItemDecoration(Color bgColor, Color shadowColor) { |
| | | return BoxDecoration( |
| | | borderRadius: const BorderRadius.all(Radius.elliptical(10, 10)), |
| | | color: bgColor, |
| | | boxShadow: [ |
| | | BoxShadow( |
| | | color: shadowColor, |
| | | blurRadius: 2.0, |
| | | offset: const Offset(0.0, 5.0), //阴影y轴偏移量 |
| | | spreadRadius: 1 //阴影扩散程度 |
| | | ) |
| | | ]); |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return Scaffold( |
| | | backgroundColor: Color(0xFFFFFFFF), |
| | | body: Container( |
| | | child: Flex( |
| | | direction: Axis.vertical, |
| | | children: [ |
| | | TopNavBar(title: "关于我们"), |
| | | const SizedBox( |
| | | height: 50, |
| | | ), |
| | | Column( |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | mainAxisSize: MainAxisSize.min, |
| | | children: [ |
| | | ClipRRect( |
| | | borderRadius: BorderRadius.circular(12), |
| | | child: Image.asset( |
| | | "assets/imgs/icon.png", |
| | | height: 82, |
| | | ), |
| | | ), |
| | | const SizedBox( |
| | | height: 17, |
| | | ), |
| | | Text( |
| | | "版本号:$versionName", |
| | | style: |
| | | const TextStyle(fontSize: 13, color: Color(0xFF999999)), |
| | | ) |
| | | ], |
| | | ), |
| | | const SizedBox( |
| | | height: 80, |
| | | ), |
| | | getItemView( |
| | | title: "用户协议", |
| | | onTap: () { |
| | | NavigatorUtil.navigateToNextPage( |
| | | context, |
| | | BrowserPage(title: "用户协议", url: Constant.PROTOCOL_URL), |
| | | (data) {}); |
| | | }), |
| | | getItemView( |
| | | title: "联系我们", |
| | | onTap: () { |
| | | MySharedPreferences.getInstance() |
| | | .getString("contactUsLink") |
| | | .then((value) { |
| | | if (StringUtil.isNullOrEmpty(value)) { |
| | | return; |
| | | } |
| | | |
| | | NavigatorUtil.navigateToNextPage(context, |
| | | BrowserPage(title: "联系我们", url: value!), (data) {}); |
| | | }); |
| | | }), |
| | | getItemView( |
| | | title: "注销账户", |
| | | onTap: () { |
| | | MySharedPreferences.getInstance() |
| | | .getString("unRegisterLink") |
| | | .then((value) { |
| | | if (!StringUtil.isNullOrEmpty(value)) { |
| | | NavigatorUtil.navigateToNextPage(context, |
| | | BrowserPage(title: "账户注销", url: value!), (data) {}); |
| | | } |
| | | }); |
| | | }), |
| | | ], |
| | | ), |
| | | )); |
| | | } |
| | | |
| | | Widget getItemView( |
| | | {required String title, required GestureTapCallback onTap}) { |
| | | return InkWell( |
| | | onTap: () { |
| | | onTap(); |
| | | }, |
| | | child: Container( |
| | | height: 40, |
| | | padding: const EdgeInsets.fromLTRB(25, 0, 25, 0), |
| | | margin: const EdgeInsets.fromLTRB(10, 0, 10, 0), |
| | | child: Row( |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | children: [ |
| | | Text( |
| | | title, |
| | | style: TextStyle(fontSize: 15, color: const Color(0xFF00A8FF)), |
| | | ), |
| | | Expanded(child: Container()), |
| | | const Icon( |
| | | Icons.chevron_right, |
| | | color: Color(0xFF00A8FF), |
| | | ) |
| | | ], |
| | | ), |
| | | ), |
| | | ); |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:io'; |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | import '../../ui/widget/nav.dart'; |
| | | import '../../utils/pageutils.dart'; |
| | | |
| | | import 'advice_submit.dart'; |
| | | import 'package:launch_review/launch_review.dart'; |
| | | |
| | | void main() { |
| | | runApp(MyApp()); |
| | | } |
| | | |
| | | class MyApp extends StatelessWidget { |
| | | // This widget is the root of your application. |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return MaterialApp( |
| | | title: '意见反馈', |
| | | theme: ThemeData(primaryColor: Color(0xFFF5F5F5)), |
| | | home: AdvicePage(title: ''), |
| | | ); |
| | | } |
| | | } |
| | | |
| | | class AdvicePage extends StatefulWidget { |
| | | AdvicePage({Key? key, required this.title}) : super(key: key); |
| | | |
| | | // This widget is the home page of your application. It is stateful, meaning |
| | | // that it has a State object (defined below) that contains fields that affect |
| | | // how it looks. |
| | | |
| | | // This class is the configuration for the state. It holds the values (in this |
| | | // case the title) provided by the parent (in this case the App widget) and |
| | | // used by the build method of the State. Fields in a Widget subclass are |
| | | // always marked "final". |
| | | |
| | | final String title; |
| | | |
| | | @override |
| | | _AdvicePageState createState() => _AdvicePageState(); |
| | | } |
| | | |
| | | class _AdvicePageState extends State<AdvicePage> |
| | | with SingleTickerProviderStateMixin { |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | } |
| | | |
| | | BoxDecoration getItemDecoration(Color bgColor, Color shadowColor) { |
| | | return BoxDecoration( |
| | | borderRadius: BorderRadius.all(Radius.elliptical(10, 10)), |
| | | color: bgColor, |
| | | boxShadow: [ |
| | | BoxShadow( |
| | | color: shadowColor, |
| | | blurRadius: 2.0, |
| | | offset: Offset(0.0, 5.0), //阴影y轴偏移量 |
| | | spreadRadius: 1 //阴影扩散程度 |
| | | ) |
| | | ]); |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return Scaffold( |
| | | backgroundColor: Color(0xFFFFFFFF), |
| | | body: Container( |
| | | child: Flex( |
| | | direction: Axis.vertical, |
| | | children: [ |
| | | TopNavBar(title: "意见反馈"), |
| | | Image.asset("assets/imgs/advice/ic_advice_top.png"), |
| | | getItemView( |
| | | bgColor: Color(0xFFFFC945), |
| | | shadowColor: Color(0x4DFCA235), |
| | | title: "提意见", |
| | | subTitle: "有建议,请留言", |
| | | icon: "assets/imgs/advice/icon_advice_msg.png", |
| | | onTap: () { |
| | | Navigator.of(context) |
| | | .push(CustomRouteSlide(AdviceSubmitPage(title: ""))); |
| | | }, |
| | | ), |
| | | getItemView( |
| | | bgColor: const Color(0xFF29D5FF), |
| | | shadowColor: const Color(0x4D0E96FF), |
| | | title: "给鼓励", |
| | | subTitle: "您的鼓励是我们前进的动力", |
| | | icon: "assets/imgs/advice/icon_advice_like.png", |
| | | onTap: () { |
| | | if (Platform.isIOS) { |
| | | LaunchReview.launch(iOSAppId: Constant.IOS_APP_ID); |
| | | } else { |
| | | LaunchReview.launch(); |
| | | } |
| | | }, |
| | | ), |
| | | ], |
| | | ), |
| | | )); |
| | | } |
| | | |
| | | Widget getItemView( |
| | | {required Color bgColor, |
| | | required Color shadowColor, |
| | | required String title, |
| | | required String subTitle, |
| | | required String icon, |
| | | required GestureTapCallback onTap}) { |
| | | return InkWell( |
| | | onTap: () { |
| | | onTap(); |
| | | }, |
| | | child: Container( |
| | | height: 92.5, |
| | | padding: EdgeInsets.fromLTRB(20, 0, 24, 0), |
| | | margin: EdgeInsets.fromLTRB(10, 12.5, 10, 4), |
| | | decoration: getItemDecoration(bgColor, shadowColor), |
| | | child: Flex( |
| | | direction: Axis.horizontal, |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | children: [ |
| | | Flex( |
| | | direction: Axis.vertical, |
| | | mainAxisAlignment: MainAxisAlignment.center, |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | Text( |
| | | title, |
| | | style: TextStyle(color: Colors.white, fontSize: 24), |
| | | ), |
| | | Text( |
| | | subTitle, |
| | | style: TextStyle(color: Colors.white, fontSize: 14), |
| | | ) |
| | | ], |
| | | ), |
| | | Expanded(child: Container()), |
| | | Image.asset( |
| | | icon, |
| | | height: 62, |
| | | ) |
| | | ], |
| | | ), |
| | | ), |
| | | ); |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import '../../api/feed_back_api.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | |
| | | import '../../ui/widget/nav.dart'; |
| | | import '../../utils/string_util.dart'; |
| | | import '../../utils/ui_utils.dart'; |
| | | |
| | | void main() { |
| | | runApp(MyApp()); |
| | | } |
| | | |
| | | class MyApp extends StatelessWidget { |
| | | // This widget is the root of your application. |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return MaterialApp( |
| | | title: '提意见', |
| | | theme: ThemeData(primaryColor: Color(0xFFF5F5F5)), |
| | | home: AdviceSubmitPage(title: ''), |
| | | ); |
| | | } |
| | | } |
| | | |
| | | class AdviceSubmitPage extends StatefulWidget { |
| | | AdviceSubmitPage({Key? key, required this.title}) : super(key: key); |
| | | |
| | | // This widget is the home page of your application. It is stateful, meaning |
| | | // that it has a State object (defined below) that contains fields that affect |
| | | // how it looks. |
| | | |
| | | // This class is the configuration for the state. It holds the values (in this |
| | | // case the title) provided by the parent (in this case the App widget) and |
| | | // used by the build method of the State. Fields in a Widget subclass are |
| | | // always marked "final". |
| | | |
| | | final String title; |
| | | |
| | | @override |
| | | _AdviceSubmitPageState createState() => _AdviceSubmitPageState(); |
| | | } |
| | | |
| | | class _AdviceSubmitPageState extends State<AdviceSubmitPage> |
| | | with SingleTickerProviderStateMixin { |
| | | List<String> questions = ["账号问题", "视频播放问题", "其他问题"]; |
| | | |
| | | final TextEditingController _contentController = TextEditingController(); |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | } |
| | | |
| | | BoxDecoration getItemDecoration(Color bgColor, Color shadowColor) { |
| | | return BoxDecoration( |
| | | borderRadius: const BorderRadius.all(Radius.elliptical(10, 10)), |
| | | color: bgColor, |
| | | boxShadow: [ |
| | | BoxShadow( |
| | | color: shadowColor, |
| | | blurRadius: 2.0, |
| | | offset: const Offset(0.0, 5.0), //阴影y轴偏移量 |
| | | spreadRadius: 1 //阴影扩散程度 |
| | | ) |
| | | ]); |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return Scaffold( |
| | | resizeToAvoidBottomInset: false, |
| | | backgroundColor: Color(0xFFFFFFFF), |
| | | body: Container( |
| | | child: Flex( |
| | | direction: Axis.vertical, |
| | | children: [ |
| | | TopNavBar(title: "提意见"), |
| | | Container( |
| | | padding: const EdgeInsets.fromLTRB(17.5, 15, 17.5, 15), |
| | | decoration: BoxDecoration( |
| | | color: const Color(0xFFFAFAFA), |
| | | border: Border.all(color: Color(0xFFDBDBDB))), |
| | | child: Flex( |
| | | direction: Axis.vertical, |
| | | mainAxisAlignment: MainAxisAlignment.center, |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | Wrap( |
| | | children: getQuestionTypes(), |
| | | ), |
| | | TextField( |
| | | controller: _contentController, |
| | | maxLines: 9, |
| | | maxLength: 200, |
| | | decoration: const InputDecoration( |
| | | focusedBorder: InputBorder.none, |
| | | border: InputBorder.none, |
| | | counterStyle: TextStyle(color: Color(0xFF666666)), |
| | | hintText: "请描述您要反馈的问题或意见", |
| | | hintStyle: TextStyle( |
| | | fontSize: 15, color: Color(0xFF999999))), |
| | | style: |
| | | const TextStyle(color: Color(0xFF666666), fontSize: 15), |
| | | onChanged: (value) {}, |
| | | ) |
| | | ], |
| | | ), |
| | | ), |
| | | //按钮 |
| | | Container( |
| | | margin: const EdgeInsets.fromLTRB(18, 50, 18, 0), |
| | | child: InkWell( |
| | | onTap: () { |
| | | var content = _contentController.text; |
| | | if (StringUtil.isNullOrEmpty(content)) { |
| | | ToastUtil.toast("请填写问题",context); |
| | | return; |
| | | } |
| | | var type; |
| | | if (checkIndex > -1) { |
| | | type = questions[checkIndex]; |
| | | } |
| | | |
| | | FeedBackApiUtil.advice(context, type, content).then((value) { |
| | | if (value == null) { |
| | | return; |
| | | } |
| | | if (value["code"] == 0) { |
| | | popPage(context); |
| | | } else { |
| | | ToastUtil.toast(value["msg"],context); |
| | | } |
| | | }); |
| | | }, |
| | | child: Container( |
| | | height: 42.5, |
| | | alignment: Alignment.center, |
| | | decoration: const BoxDecoration( |
| | | color: ColorConstant.theme, |
| | | borderRadius: BorderRadius.all(Radius.circular(10))), |
| | | child: const Text( |
| | | "提交反馈", |
| | | style: TextStyle(color: Colors.white, fontSize: 18), |
| | | ), |
| | | ), |
| | | ), |
| | | ) |
| | | ], |
| | | ), |
| | | )); |
| | | } |
| | | |
| | | int checkIndex = -1; |
| | | |
| | | List<Widget> getQuestionTypes() { |
| | | List<Widget> list = []; |
| | | |
| | | for (int i = 0; i < questions.length; i++) { |
| | | list.add(InkWell( |
| | | onTap: () { |
| | | if (checkIndex == i) { |
| | | setState(() { |
| | | checkIndex = -1; |
| | | }); |
| | | } else { |
| | | setState(() { |
| | | checkIndex = i; |
| | | }); |
| | | } |
| | | }, |
| | | child: Container( |
| | | padding: const EdgeInsets.fromLTRB(10, 5, 10, 5), |
| | | margin: const EdgeInsets.fromLTRB(0, 0, 17.5, 0), |
| | | // height: 35, |
| | | // width: 80, |
| | | decoration: BoxDecoration( |
| | | color: Colors.white, |
| | | borderRadius: BorderRadius.circular(17.5), |
| | | border: Border.all( |
| | | color: i == checkIndex |
| | | ? ColorConstant.theme |
| | | : const Color(0xFFB4B4B4))), |
| | | child: Text( |
| | | questions[i], |
| | | style: TextStyle( |
| | | color: |
| | | i == checkIndex ? ColorConstant.theme : Color(0xFF666666), |
| | | fontSize: 14), |
| | | ), |
| | | ))); |
| | | } |
| | | |
| | | return list; |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:async'; |
| | | import 'dart:convert'; |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import 'package:flutter/services.dart'; |
| | | import 'package:fluwx_no_pay/fluwx_no_pay.dart' as fluwx; |
| | | import '../../api/user_api.dart'; |
| | | import 'email_pwd_find.dart'; |
| | | import 'email_register.dart'; |
| | | import '../widget/nav.dart'; |
| | | import '../../utils/push_util.dart'; |
| | | import 'package:html/dom.dart' as dom; |
| | | import '../../api/http.dart'; |
| | | import '../../model/user/user_info.dart'; |
| | | import '../../ui/common/browser.dart'; |
| | | import '../../ui/widget/button.dart'; |
| | | import '../../utils/event_bus_util.dart'; |
| | | import '../../utils/pageutils.dart'; |
| | | import '../../utils/string_util.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | import '../../utils/ui_utils.dart'; |
| | | import '../../utils/user_util.dart'; |
| | | import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart'; |
| | | |
| | | void main() { |
| | | runApp(MyApp()); |
| | | } |
| | | |
| | | class MyApp extends StatelessWidget { |
| | | // This widget is the root of your application. |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return MaterialApp( |
| | | title: '登录', |
| | | theme: ThemeData(primaryColor: Color(0xFFF5F5F5)), |
| | | home: EmailLoginPage(title: ''), |
| | | ); |
| | | } |
| | | } |
| | | |
| | | class EmailLoginPage extends StatefulWidget { |
| | | //阿里云一键登录 |
| | | static const messageChannel = |
| | | BasicMessageChannel('AliyunPhoneNumberAuth', StandardMessageCodec()); |
| | | |
| | | EmailLoginPage({Key? key, required this.title}) : super(key: key); |
| | | |
| | | // This widget is the home page of your application. It is stateful, meaning |
| | | // that it has a State object (defined below) that contains fields that affect |
| | | // how it looks. |
| | | |
| | | // This class is the configuration for the state. It holds the values (in this |
| | | // case the title) provided by the parent (in this case the App widget) and |
| | | // used by the build method of the State. Fields in a Widget subclass are |
| | | // always marked "final". |
| | | |
| | | final String title; |
| | | |
| | | @override |
| | | _EmailLoginPageState createState() => _EmailLoginPageState(); |
| | | } |
| | | |
| | | class _EmailLoginPageState extends State<EmailLoginPage> |
| | | with SingleTickerProviderStateMixin { |
| | | bool oneKeyLogin = false; |
| | | bool checked = false; |
| | | TextEditingController? emailController = TextEditingController(); |
| | | TextEditingController? pwdController = TextEditingController(); |
| | | String phone = ""; |
| | | String code = ""; |
| | | Timer? timer; |
| | | |
| | | //重新发送验证码倒计时 |
| | | int? reSendSMSTimeLeft; |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | reSendSMSTimeLeft = -1; |
| | | //初始化微信登录监听 |
| | | fluwx.weChatResponseEventHandler.distinct((a, b) => a == b).listen((res) { |
| | | if (res is fluwx.WeChatAuthResponse) { |
| | | int errCode = res.errCode; |
| | | if (errCode == 0) { |
| | | String? code = res.code; |
| | | //TODO 把微信登录返回的code传给后台,剩下的事就交给后台处理 |
| | | } else if (errCode == -4) { |
| | | //showToast("用户拒绝授权"); |
| | | } else if (errCode == -2) { |
| | | // showToast("用户取消授权"); |
| | | } |
| | | } |
| | | }); |
| | | } |
| | | |
| | | |
| | | |
| | | void qqLogin() async { |
| | | Map value = await UserUtil.loginQQ(); |
| | | } |
| | | |
| | | void _loginSuccess(UserInfo user) { |
| | | UserUtil.setUserInfo(user).then((value) { |
| | | print("登录成功"); |
| | | eventBus.fire(LoginEventBus(true)); |
| | | PushUtil.setAlias(user.id!.toString()).then((value) { |
| | | popPage(context); |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | @override |
| | | void dispose() { |
| | | if (timer != null) { |
| | | timer!.cancel(); |
| | | } |
| | | super.dispose(); |
| | | } |
| | | |
| | | BoxDecoration getItemDecoration(Color bgColor, Color shadowColor) { |
| | | return BoxDecoration( |
| | | borderRadius: const BorderRadius.all(Radius.elliptical(10, 10)), |
| | | color: bgColor, |
| | | boxShadow: [ |
| | | BoxShadow( |
| | | color: shadowColor, |
| | | blurRadius: 2.0, |
| | | offset: const Offset(0.0, 5.0), //阴影y轴偏移量 |
| | | spreadRadius: 1 //阴影扩散程度 |
| | | ) |
| | | ]); |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return Scaffold( |
| | | resizeToAvoidBottomInset: false, |
| | | backgroundColor: Colors.white, |
| | | body: Stack( |
| | | children: [ |
| | | Column(children: [ |
| | | //登录内容区域 |
| | | Expanded( |
| | | child: SingleChildScrollView( |
| | | child: Padding( |
| | | padding: const EdgeInsets.fromLTRB(40, 120, 40, 14), |
| | | child: Column( |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | children: [ |
| | | Image.asset( |
| | | "assets/imgs/login/ic_login_logo.png", |
| | | width: 131, |
| | | ), |
| | | Container( |
| | | height: 70, |
| | | ), |
| | | Container( |
| | | constraints: const BoxConstraints(minHeight: 180), |
| | | child: getLoginContent()), |
| | | Row( |
| | | children: [ |
| | | InkWell( |
| | | child: const Text( |
| | | "注册", |
| | | style: TextStyle( |
| | | color: ColorConstant.theme), |
| | | ), |
| | | onTap: () { |
| | | NavigatorUtil.navigateToNextPage( |
| | | context, |
| | | EmailRegisterPage(title: ""), |
| | | (data) { |
| | | FocusScope.of(context).requestFocus(FocusNode()); |
| | | }); |
| | | }, |
| | | ), |
| | | Expanded(child: Container()), |
| | | InkWell( |
| | | child: const Text("找回密码?", |
| | | style: TextStyle( |
| | | color: ColorConstant.theme)), |
| | | onTap: () { |
| | | NavigatorUtil.navigateToNextPage( |
| | | context, |
| | | EmailFindPwdPage(title: ""), |
| | | (data) { |
| | | FocusScope.of(context).requestFocus(FocusNode()); |
| | | }); |
| | | |
| | | }, |
| | | ), |
| | | ], |
| | | ) |
| | | ])))), |
| | | //用户协议与隐私政策 |
| | | Row(children: [ |
| | | Container( |
| | | width: 25, |
| | | ), |
| | | RoundCheckBox( |
| | | value: checked, |
| | | onChanged: (value) { |
| | | setState(() { |
| | | checked = value; |
| | | }); |
| | | }, |
| | | ), |
| | | Expanded( |
| | | child: Container( |
| | | child: HtmlWidget( |
| | | "<p>登录即表明同意 <a href='${Constant.PROTOCOL_URL}'>用户协议</a> 和 <a href='${Constant.PRIVACY_URL}'>隐私政策</a> </p>", |
| | | textStyle: |
| | | const TextStyle(color: Color(0xFF999999)), |
| | | customStylesBuilder: (element) { |
| | | // print(element.localName); |
| | | // if (element.localName == 'a') { |
| | | // return {'color': 'red'}; |
| | | // } |
| | | |
| | | return null; |
| | | }, onTapUrl: (url) { |
| | | String? title = ""; |
| | | if (url == Constant.PROTOCOL_URL) { |
| | | title = "用户协议"; |
| | | } else { |
| | | title = "隐私政策"; |
| | | } |
| | | |
| | | NavigatorUtil.navigateToNextPage( |
| | | context, |
| | | BrowserPage( |
| | | title: title, |
| | | url: url, |
| | | ), |
| | | (data) {}); |
| | | |
| | | return true; |
| | | }))), |
| | | ]) |
| | | ]), |
| | | //关闭按钮 |
| | | Positioned( |
| | | top: 30, |
| | | left: 20, |
| | | child: InkWell( |
| | | onTap: () { |
| | | popPage(context); |
| | | }, |
| | | child: const Icon( |
| | | Icons.close, |
| | | size: 30, |
| | | ))) |
| | | ], |
| | | )); |
| | | } |
| | | |
| | | Widget getLoginContent() { |
| | | return Column( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | Container( |
| | | alignment: Alignment.centerLeft, |
| | | padding: const EdgeInsets.fromLTRB(20, 0, 5, 0), |
| | | decoration: BoxDecoration( |
| | | color: const Color(0xFFF5F5F5), |
| | | borderRadius: BorderRadius.circular(10)), |
| | | child: Row( |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | children: [ |
| | | Image.asset( |
| | | "assets/imgs/login/icon_email.png", |
| | | width: 17, |
| | | height: 15, |
| | | ), |
| | | Container(width: 14), |
| | | Expanded( |
| | | child: TextField( |
| | | style: const TextStyle(color: Color(0xFF333333), fontSize: 17), |
| | | onChanged: (value) { |
| | | setState(() { |
| | | phone = value; |
| | | }); |
| | | }, |
| | | textAlign: TextAlign.start, |
| | | keyboardType: TextInputType.emailAddress, |
| | | controller: emailController, |
| | | maxLength: 60, |
| | | decoration: const InputDecoration( |
| | | counterText: "", |
| | | hintText: "请输入邮箱", |
| | | hintStyle: |
| | | TextStyle(color: Color(0xFFCCCCCC), fontSize: 17), |
| | | contentPadding: EdgeInsets.only(bottom: 3), |
| | | border: InputBorder.none, |
| | | focusedBorder: InputBorder.none, |
| | | ), |
| | | )), |
| | | ], |
| | | ), |
| | | ), |
| | | Container(height: 10), |
| | | Container( |
| | | alignment: Alignment.centerLeft, |
| | | padding: const EdgeInsets.fromLTRB(20, 0, 7, 0), |
| | | decoration: BoxDecoration( |
| | | color: const Color(0xFFF5F5F5), |
| | | borderRadius: BorderRadius.circular(10)), |
| | | child: Row( |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | children: [ |
| | | Image.asset( |
| | | "assets/imgs/login/icon_pwd.png", |
| | | width: 16.5, |
| | | ), |
| | | Container(width: 14), |
| | | Expanded( |
| | | child: TextField( |
| | | style:const TextStyle(color: Color(0xFF333333), fontSize: 17), |
| | | onChanged: (value) { |
| | | setState(() { |
| | | phone = value; |
| | | }); |
| | | }, |
| | | obscureText: true, |
| | | textAlign: TextAlign.start, |
| | | keyboardType: TextInputType.visiblePassword, |
| | | controller: pwdController, |
| | | maxLength: 32, |
| | | decoration: const InputDecoration( |
| | | counterText: "", |
| | | hintText: "请输入密码", |
| | | hintStyle: |
| | | TextStyle(color: Color(0xFFCCCCCC), fontSize: 17), |
| | | contentPadding: EdgeInsets.only(bottom: 3), |
| | | border: InputBorder.none, |
| | | focusedBorder: InputBorder.none, |
| | | ), |
| | | )), |
| | | ], |
| | | ), |
| | | ), |
| | | Container(height: 20), |
| | | MyFillButton( |
| | | "登 录", |
| | | 10, |
| | | height: 45, |
| | | color: const Color(0xFFFF2B4B), |
| | | fontSize: 17, |
| | | enable: StringUtil.isEmail(emailController!.value.text) && |
| | | pwdController!.value.text.length >= 6, |
| | | |
| | | onClick: () { |
| | | if (!(StringUtil.isEmail(emailController!.value.text) && |
| | | pwdController!.value.text.length >= 6)) { |
| | | return; |
| | | } |
| | | |
| | | if (!checked) { |
| | | ToastUtil.toast("请同意用户协议与隐私政策", context); |
| | | return; |
| | | } |
| | | |
| | | UserApiUtil.loginByEmail(context, emailController!.value.text, |
| | | pwdController!.value.text) |
| | | .then((value) { |
| | | print("结果: $value"); |
| | | if (value!["IsPost"] == "true") { |
| | | UserInfo user = UserInfo.fromJson(value["Data"]); |
| | | _loginSuccess(user); |
| | | } else { |
| | | ToastUtil.toast(value["Error"], context); |
| | | } |
| | | }); |
| | | }, |
| | | ), |
| | | ], |
| | | ); |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:async'; |
| | | import 'dart:convert'; |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import 'package:flutter/services.dart'; |
| | | import 'package:fluwx_no_pay/fluwx_no_pay.dart' as fluwx; |
| | | import '../../api/user_api.dart'; |
| | | import 'package:html/dom.dart' as dom; |
| | | import '../../api/http.dart'; |
| | | import '../../model/user/user_info.dart'; |
| | | import '../../ui/common/browser.dart'; |
| | | import '../../ui/widget/button.dart'; |
| | | import '../../utils/event_bus_util.dart'; |
| | | import '../../utils/pageutils.dart'; |
| | | import '../../utils/push_util.dart'; |
| | | import '../../utils/string_util.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | import '../../utils/ui_utils.dart'; |
| | | import '../../utils/user_util.dart'; |
| | | import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart'; |
| | | import '../widget/nav.dart'; |
| | | |
| | | void main() { |
| | | runApp(MyApp()); |
| | | } |
| | | |
| | | class MyApp extends StatelessWidget { |
| | | // This widget is the root of your application. |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return MaterialApp( |
| | | title: '注册', |
| | | theme: ThemeData(primaryColor: Color(0xFFF5F5F5)), |
| | | home: EmailFindPwdPage(title: ''), |
| | | ); |
| | | } |
| | | } |
| | | |
| | | class EmailFindPwdPage extends StatefulWidget { |
| | | //阿里云一键登录 |
| | | static const messageChannel = |
| | | BasicMessageChannel('AliyunPhoneNumberAuth', StandardMessageCodec()); |
| | | |
| | | EmailFindPwdPage({Key? key, required this.title}) : super(key: key); |
| | | |
| | | // This widget is the home page of your application. It is stateful, meaning |
| | | // that it has a State object (defined below) that contains fields that affect |
| | | // how it looks. |
| | | |
| | | // This class is the configuration for the state. It holds the values (in this |
| | | // case the title) provided by the parent (in this case the App widget) and |
| | | // used by the build method of the State. Fields in a Widget subclass are |
| | | // always marked "final". |
| | | |
| | | final String title; |
| | | |
| | | @override |
| | | _EmailFindPwdPageState createState() => _EmailFindPwdPageState(); |
| | | } |
| | | |
| | | class _EmailFindPwdPageState extends State<EmailFindPwdPage> |
| | | with SingleTickerProviderStateMixin { |
| | | TextEditingController? emailController = TextEditingController(); |
| | | TextEditingController? codeController = TextEditingController(); |
| | | TextEditingController? pwdController = TextEditingController(); |
| | | String email = ""; |
| | | String code = ""; |
| | | Timer? timer; |
| | | |
| | | //重新发送验证码倒计时 |
| | | int? reSendSMSTimeLeft; |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | reSendSMSTimeLeft = -1; |
| | | } |
| | | |
| | | @override |
| | | void dispose() { |
| | | if (timer != null) { |
| | | timer!.cancel(); |
| | | } |
| | | super.dispose(); |
| | | } |
| | | |
| | | BoxDecoration getItemDecoration(Color bgColor, Color shadowColor) { |
| | | return BoxDecoration( |
| | | borderRadius: const BorderRadius.all(Radius.elliptical(10, 10)), |
| | | color: bgColor, |
| | | boxShadow: [ |
| | | BoxShadow( |
| | | color: shadowColor, |
| | | blurRadius: 2.0, |
| | | offset: const Offset(0.0, 5.0), //阴影y轴偏移量 |
| | | spreadRadius: 1 //阴影扩散程度 |
| | | ) |
| | | ]); |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return Scaffold( |
| | | resizeToAvoidBottomInset: false, |
| | | backgroundColor: Colors.white, |
| | | body: Stack( |
| | | children: [ |
| | | Column(children: [ |
| | | //登录内容区域 |
| | | Expanded( |
| | | child: SingleChildScrollView( |
| | | child: Padding( |
| | | padding: EdgeInsets.fromLTRB(40, 120, 40, 14), |
| | | child: Column( |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | children: [ |
| | | Image.asset( |
| | | "assets/imgs/login/ic_login_logo.png", |
| | | width: 131, |
| | | ), |
| | | Container( |
| | | height: 70, |
| | | ), |
| | | Container( |
| | | constraints: const BoxConstraints(minHeight: 200), |
| | | child: getLoginContent()), |
| | | Container( |
| | | height: 30, |
| | | ) |
| | | ])))), |
| | | ]), |
| | | //关闭按钮 |
| | | Positioned( |
| | | top: 30, |
| | | left: 20, |
| | | child: InkWell( |
| | | onTap: () { |
| | | popPage(context); |
| | | }, |
| | | child: const Icon( |
| | | Icons.arrow_back_ios_new, |
| | | size: 25, |
| | | ))) |
| | | ], |
| | | )); |
| | | } |
| | | |
| | | Widget getLoginContent() { |
| | | return Column( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | Container( |
| | | alignment: Alignment.centerLeft, |
| | | padding: const EdgeInsets.fromLTRB(20, 0, 5, 0), |
| | | decoration: BoxDecoration( |
| | | color: const Color(0xFFF5F5F5), |
| | | borderRadius: BorderRadius.circular(10)), |
| | | child: Row( |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | children: [ |
| | | Image.asset( |
| | | "assets/imgs/login/icon_email.png", |
| | | width: 17, |
| | | height: 15, |
| | | ), |
| | | Container(width: 14), |
| | | Expanded( |
| | | child: TextField( |
| | | style: const TextStyle(color: Color(0xFF333333), fontSize: 17), |
| | | onChanged: (value) { |
| | | setState(() { |
| | | email = value; |
| | | }); |
| | | }, |
| | | textAlign: TextAlign.start, |
| | | keyboardType: TextInputType.emailAddress, |
| | | controller: emailController, |
| | | maxLength: 60, |
| | | decoration: const InputDecoration( |
| | | counterText: "", |
| | | hintText: "请输入邮箱", |
| | | hintStyle: TextStyle(color: Color(0xFFCCCCCC), fontSize: 17), |
| | | contentPadding: EdgeInsets.only(bottom: 3), |
| | | border: InputBorder.none, |
| | | focusedBorder: InputBorder.none, |
| | | ), |
| | | )), |
| | | ], |
| | | ), |
| | | ), |
| | | Container(height: 10), |
| | | Container( |
| | | alignment: Alignment.centerLeft, |
| | | padding: const EdgeInsets.fromLTRB(20, 0, 7, 0), |
| | | decoration: BoxDecoration( |
| | | color: const Color(0xFFF5F5F5), |
| | | borderRadius: BorderRadius.circular(10)), |
| | | child: Row( |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | children: [ |
| | | Image.asset( |
| | | "assets/imgs/login/icon_code.png", |
| | | width: 16.5, |
| | | ), |
| | | Container(width: 14), |
| | | Expanded( |
| | | child: TextField( |
| | | style: const TextStyle(color: Color(0xFF333333), fontSize: 17), |
| | | onChanged: (value) { |
| | | setState(() { |
| | | email = value; |
| | | }); |
| | | }, |
| | | textAlign: TextAlign.start, |
| | | keyboardType: TextInputType.phone, |
| | | controller: codeController, |
| | | maxLength: 8, |
| | | decoration: const InputDecoration( |
| | | counterText: "", |
| | | hintText: "请输入验证码", |
| | | hintStyle: TextStyle(color: Color(0xFFCCCCCC), fontSize: 17), |
| | | contentPadding: EdgeInsets.only(bottom: 3), |
| | | border: InputBorder.none, |
| | | focusedBorder: InputBorder.none, |
| | | ), |
| | | )), |
| | | MyFillButton( |
| | | (reSendSMSTimeLeft! > 0 |
| | | ? (reSendSMSTimeLeft.toString() + "S重新获取") |
| | | : reSendSMSTimeLeft == -1 |
| | | ? "获取验证码" |
| | | : "重新获取") |
| | | .toString(), |
| | | 10, |
| | | height: 34, |
| | | padding: const EdgeInsets.fromLTRB(10, 0, 10, 0), |
| | | enable: (reSendSMSTimeLeft! < 1 && |
| | | StringUtil.isEmail(emailController!.value.text)), |
| | | onClick: () { |
| | | if (!(reSendSMSTimeLeft! < 1 && |
| | | StringUtil.isEmail(emailController!.value.text))) { |
| | | return; |
| | | } |
| | | //发送验证码 |
| | | UserApiUtil.sendEmailCode(context, emailController!.value.text) |
| | | .then((value) { |
| | | if (value != null && value["IsPost"] == "true") { |
| | | setState(() { |
| | | reSendSMSTimeLeft = 60; |
| | | //倒计时 |
| | | timer = |
| | | Timer.periodic(const Duration(seconds: 1), (timer) { |
| | | if (reSendSMSTimeLeft! > 0) { |
| | | setState(() { |
| | | reSendSMSTimeLeft = reSendSMSTimeLeft! - 1; |
| | | }); |
| | | } else { |
| | | timer.cancel(); |
| | | } |
| | | }); |
| | | }); |
| | | } else { |
| | | ToastUtil.toast(value!["Error"], context); |
| | | } |
| | | }); |
| | | }, |
| | | ), |
| | | ], |
| | | ), |
| | | ), |
| | | Container(height: 10), |
| | | Container( |
| | | alignment: Alignment.centerLeft, |
| | | padding: const EdgeInsets.fromLTRB(20, 0, 7, 0), |
| | | decoration: BoxDecoration( |
| | | color: const Color(0xFFF5F5F5), |
| | | borderRadius: BorderRadius.circular(10)), |
| | | child: Row( |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | children: [ |
| | | Image.asset( |
| | | "assets/imgs/login/icon_pwd.png", |
| | | width: 16.5, |
| | | ), |
| | | Container(width: 14), |
| | | Expanded( |
| | | child: TextField( |
| | | style: TextStyle(color: Color(0xFF333333), fontSize: 17), |
| | | onChanged: (value) { |
| | | setState(() { |
| | | email = value; |
| | | }); |
| | | }, |
| | | obscureText: true, |
| | | textAlign: TextAlign.start, |
| | | keyboardType: TextInputType.visiblePassword, |
| | | controller: pwdController, |
| | | maxLength: 32, |
| | | decoration: const InputDecoration( |
| | | counterText: "", |
| | | hintText: "请输入密码", |
| | | hintStyle: TextStyle(color: Color(0xFFCCCCCC), fontSize: 17), |
| | | contentPadding: EdgeInsets.only(bottom: 3), |
| | | border: InputBorder.none, |
| | | focusedBorder: InputBorder.none, |
| | | ), |
| | | )), |
| | | ], |
| | | ), |
| | | ), |
| | | Container(height: 52), |
| | | MyFillButton( |
| | | "设置密码", |
| | | 10, |
| | | height: 45, |
| | | color: ColorConstant.theme, |
| | | fontSize: 17, |
| | | enable: StringUtil.isEmail(emailController!.value.text) && |
| | | codeController!.value.text.length >= 4 && |
| | | pwdController!.value.text.length >= 6, |
| | | onClick: () { |
| | | if (!(StringUtil.isEmail(emailController!.value.text) && |
| | | codeController!.value.text.length >= 4 && |
| | | pwdController!.value.text.length >= 6)) { |
| | | return; |
| | | } |
| | | |
| | | UserApiUtil.setPwd(context, emailController!.value.text, |
| | | pwdController!.value.text, codeController!.value.text) |
| | | .then((value) { |
| | | print("结果: $value"); |
| | | if (value!["IsPost"] == "true") { |
| | | ToastUtil.toast("密码设置成功", context); |
| | | popPage(context); |
| | | }else{ |
| | | ToastUtil.toast(value["Error"], context); |
| | | } |
| | | }); |
| | | }, |
| | | ), |
| | | ], |
| | | ); |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:async'; |
| | | import 'dart:convert'; |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import 'package:flutter/services.dart'; |
| | | import 'package:fluwx_no_pay/fluwx_no_pay.dart' as fluwx; |
| | | import '../../api/user_api.dart'; |
| | | import 'package:html/dom.dart' as dom; |
| | | import '../../api/http.dart'; |
| | | import '../../model/user/user_info.dart'; |
| | | import '../../ui/common/browser.dart'; |
| | | import '../../ui/widget/button.dart'; |
| | | import '../../utils/event_bus_util.dart'; |
| | | import '../../utils/pageutils.dart'; |
| | | import '../../utils/push_util.dart'; |
| | | import '../../utils/string_util.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | import '../../utils/ui_utils.dart'; |
| | | import '../../utils/user_util.dart'; |
| | | import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart'; |
| | | import '../widget/nav.dart'; |
| | | |
| | | void main() { |
| | | runApp(MyApp()); |
| | | } |
| | | |
| | | class MyApp extends StatelessWidget { |
| | | // This widget is the root of your application. |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return MaterialApp( |
| | | title: '注册', |
| | | theme: ThemeData(primaryColor: Color(0xFFF5F5F5)), |
| | | home: EmailRegisterPage(title: ''), |
| | | ); |
| | | } |
| | | } |
| | | |
| | | class EmailRegisterPage extends StatefulWidget { |
| | | //阿里云一键登录 |
| | | static const messageChannel = |
| | | BasicMessageChannel('AliyunPhoneNumberAuth', StandardMessageCodec()); |
| | | |
| | | EmailRegisterPage({Key? key, required this.title}) : super(key: key); |
| | | |
| | | // This widget is the home page of your application. It is stateful, meaning |
| | | // that it has a State object (defined below) that contains fields that affect |
| | | // how it looks. |
| | | |
| | | // This class is the configuration for the state. It holds the values (in this |
| | | // case the title) provided by the parent (in this case the App widget) and |
| | | // used by the build method of the State. Fields in a Widget subclass are |
| | | // always marked "final". |
| | | |
| | | final String title; |
| | | |
| | | @override |
| | | _EmailRegisterPageState createState() => _EmailRegisterPageState(); |
| | | } |
| | | |
| | | class _EmailRegisterPageState extends State<EmailRegisterPage> |
| | | with SingleTickerProviderStateMixin { |
| | | bool checked = false; |
| | | TextEditingController? emailController = TextEditingController(); |
| | | TextEditingController? codeController = TextEditingController(); |
| | | TextEditingController? pwdController = TextEditingController(); |
| | | String email = ""; |
| | | String code = ""; |
| | | Timer? timer; |
| | | |
| | | //重新发送验证码倒计时 |
| | | int? reSendSMSTimeLeft; |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | reSendSMSTimeLeft = -1; |
| | | //初始化微信登录监听 |
| | | fluwx.weChatResponseEventHandler.distinct((a, b) => a == b).listen((res) { |
| | | if (res is fluwx.WeChatAuthResponse) { |
| | | int errCode = res.errCode; |
| | | if (errCode == 0) { |
| | | String? code = res.code; |
| | | //TODO 把微信登录返回的code传给后台,剩下的事就交给后台处理 |
| | | } else if (errCode == -4) { |
| | | //showToast("用户拒绝授权"); |
| | | } else if (errCode == -2) { |
| | | // showToast("用户取消授权"); |
| | | } |
| | | } |
| | | }); |
| | | } |
| | | |
| | | void _loginSuccess(UserInfo user) { |
| | | UserUtil.setUserInfo(user).then((value) { |
| | | print("登录成功"); |
| | | eventBus.fire(LoginEventBus(true)); |
| | | PushUtil.setAlias(user.id!.toString()).then((value) { |
| | | popPage(context); |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | @override |
| | | void dispose() { |
| | | if (timer != null) { |
| | | timer!.cancel(); |
| | | } |
| | | super.dispose(); |
| | | } |
| | | |
| | | BoxDecoration getItemDecoration(Color bgColor, Color shadowColor) { |
| | | return BoxDecoration( |
| | | borderRadius: const BorderRadius.all(Radius.elliptical(10, 10)), |
| | | color: bgColor, |
| | | boxShadow: [ |
| | | BoxShadow( |
| | | color: shadowColor, |
| | | blurRadius: 2.0, |
| | | offset: const Offset(0.0, 5.0), //阴影y轴偏移量 |
| | | spreadRadius: 1 //阴影扩散程度 |
| | | ) |
| | | ]); |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return Scaffold( |
| | | resizeToAvoidBottomInset: false, |
| | | backgroundColor: Colors.white, |
| | | body: Stack( |
| | | children: [ |
| | | Column(children: [ |
| | | //登录内容区域 |
| | | Expanded( |
| | | child: SingleChildScrollView( |
| | | child: Padding( |
| | | padding: const EdgeInsets.fromLTRB(40, 120, 40, 14), |
| | | child: Column( |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | children: [ |
| | | Image.asset( |
| | | "assets/imgs/login/ic_login_logo.png", |
| | | width: 131, |
| | | ), |
| | | Container( |
| | | height: 70, |
| | | ), |
| | | Container( |
| | | constraints: |
| | | const BoxConstraints(minHeight: 200), |
| | | child: getLoginContent()), |
| | | Container( |
| | | height: 30, |
| | | ) |
| | | ])))), |
| | | //用户协议与隐私政策 |
| | | Row(children: [ |
| | | Container( |
| | | width: 25, |
| | | ), |
| | | RoundCheckBox( |
| | | value: checked, |
| | | onChanged: (value) { |
| | | setState(() { |
| | | checked = value; |
| | | }); |
| | | }, |
| | | ), |
| | | Expanded( |
| | | child: HtmlWidget( |
| | | "<p>登录即表明同意 <a href='${Constant.PROTOCOL_URL}'>用户协议</a> 和 <a href='${Constant.PRIVACY_URL}'>隐私政策</a> </p>", |
| | | textStyle: const TextStyle(color: Color(0xFF999999)), |
| | | onTapUrl: (url) { |
| | | String? title = ""; |
| | | if (url == Constant.PROTOCOL_URL) { |
| | | title = "用户协议"; |
| | | } else { |
| | | title = "隐私政策"; |
| | | } |
| | | |
| | | NavigatorUtil.navigateToNextPage( |
| | | context, |
| | | BrowserPage( |
| | | title: title, |
| | | url: url, |
| | | ), |
| | | (data) {}); |
| | | |
| | | return true; |
| | | })), |
| | | ]) |
| | | ]), |
| | | //关闭按钮 |
| | | Positioned( |
| | | top: 30, |
| | | left: 20, |
| | | child: InkWell( |
| | | onTap: () { |
| | | popPage(context); |
| | | }, |
| | | child: const Icon( |
| | | Icons.arrow_back_ios_new, |
| | | size: 25, |
| | | ))) |
| | | ], |
| | | )); |
| | | } |
| | | |
| | | Widget getLoginContent() { |
| | | return Column( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | Container( |
| | | alignment: Alignment.centerLeft, |
| | | padding: EdgeInsets.fromLTRB(20, 0, 5, 0), |
| | | decoration: BoxDecoration( |
| | | color: const Color(0xFFF5F5F5), |
| | | borderRadius: BorderRadius.circular(10)), |
| | | child: Row( |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | children: [ |
| | | Image.asset( |
| | | "assets/imgs/login/icon_email.png", |
| | | width: 17, |
| | | height: 15, |
| | | ), |
| | | Container(width: 14), |
| | | Expanded( |
| | | child: TextField( |
| | | style: TextStyle(color: Color(0xFF333333), fontSize: 17), |
| | | onChanged: (value) { |
| | | setState(() { |
| | | email = value; |
| | | }); |
| | | }, |
| | | textAlign: TextAlign.start, |
| | | keyboardType: TextInputType.emailAddress, |
| | | controller: emailController, |
| | | maxLength: 60, |
| | | decoration: const InputDecoration( |
| | | counterText: "", |
| | | hintText: "请输入邮箱", |
| | | hintStyle: TextStyle(color: Color(0xFFCCCCCC), fontSize: 17), |
| | | contentPadding: EdgeInsets.only(bottom: 3), |
| | | border: InputBorder.none, |
| | | focusedBorder: InputBorder.none, |
| | | ), |
| | | )), |
| | | ], |
| | | ), |
| | | ), |
| | | Container(height: 10), |
| | | Container( |
| | | alignment: Alignment.centerLeft, |
| | | padding: EdgeInsets.fromLTRB(20, 0, 7, 0), |
| | | decoration: BoxDecoration( |
| | | color: const Color(0xFFF5F5F5), |
| | | borderRadius: BorderRadius.circular(10)), |
| | | child: Row( |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | children: [ |
| | | Image.asset( |
| | | "assets/imgs/login/icon_code.png", |
| | | width: 16.5, |
| | | ), |
| | | Container(width: 14), |
| | | Expanded( |
| | | child: TextField( |
| | | style: TextStyle(color: Color(0xFF333333), fontSize: 17), |
| | | onChanged: (value) { |
| | | setState(() { |
| | | email = value; |
| | | }); |
| | | }, |
| | | textAlign: TextAlign.start, |
| | | keyboardType: TextInputType.phone, |
| | | controller: codeController, |
| | | maxLength: 8, |
| | | decoration: const InputDecoration( |
| | | counterText: "", |
| | | hintText: "请输入验证码", |
| | | hintStyle: TextStyle(color: Color(0xFFCCCCCC), fontSize: 17), |
| | | contentPadding: EdgeInsets.only(bottom: 3), |
| | | border: InputBorder.none, |
| | | focusedBorder: InputBorder.none, |
| | | ), |
| | | )), |
| | | MyFillButton( |
| | | (reSendSMSTimeLeft! > 0 |
| | | ? (reSendSMSTimeLeft.toString() + "S重新获取") |
| | | : reSendSMSTimeLeft == -1 |
| | | ? "获取验证码" |
| | | : "重新获取") |
| | | .toString(), |
| | | 10, |
| | | height: 34, |
| | | padding: const EdgeInsets.fromLTRB(10, 0, 10, 0), |
| | | enable: (reSendSMSTimeLeft! < 1 && |
| | | StringUtil.isEmail(emailController!.value.text)), |
| | | onClick: () { |
| | | if (!(reSendSMSTimeLeft! < 1 && |
| | | StringUtil.isEmail(emailController!.value.text))) { |
| | | return; |
| | | } |
| | | //发送验证码 |
| | | UserApiUtil.sendEmailCode(context, emailController!.value.text) |
| | | .then((value) { |
| | | if (value != null && value["IsPost"] == "true") { |
| | | setState(() { |
| | | reSendSMSTimeLeft = 60; |
| | | //倒计时 |
| | | timer = |
| | | Timer.periodic(const Duration(seconds: 1), (timer) { |
| | | if (reSendSMSTimeLeft! > 0) { |
| | | setState(() { |
| | | reSendSMSTimeLeft = reSendSMSTimeLeft! - 1; |
| | | }); |
| | | } else { |
| | | timer.cancel(); |
| | | } |
| | | }); |
| | | }); |
| | | } else { |
| | | ToastUtil.toast(value!["Error"], context); |
| | | } |
| | | }); |
| | | }, |
| | | ), |
| | | ], |
| | | ), |
| | | ), |
| | | Container(height: 10), |
| | | Container( |
| | | alignment: Alignment.centerLeft, |
| | | padding: const EdgeInsets.fromLTRB(20, 0, 7, 0), |
| | | decoration: BoxDecoration( |
| | | color: const Color(0xFFF5F5F5), |
| | | borderRadius: BorderRadius.circular(10)), |
| | | child: Row( |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | children: [ |
| | | Image.asset( |
| | | "assets/imgs/login/icon_pwd.png", |
| | | width: 16.5, |
| | | ), |
| | | Container(width: 14), |
| | | Expanded( |
| | | child: TextField( |
| | | style: const TextStyle(color: Color(0xFF333333), fontSize: 17), |
| | | onChanged: (value) { |
| | | setState(() { |
| | | email = value; |
| | | }); |
| | | }, |
| | | obscureText: true, |
| | | textAlign: TextAlign.start, |
| | | keyboardType: TextInputType.visiblePassword, |
| | | controller: pwdController, |
| | | maxLength: 32, |
| | | decoration: const InputDecoration( |
| | | counterText: "", |
| | | hintText: "请输入密码", |
| | | hintStyle: TextStyle(color: Color(0xFFCCCCCC), fontSize: 17), |
| | | contentPadding: EdgeInsets.only(bottom: 3), |
| | | border: InputBorder.none, |
| | | focusedBorder: InputBorder.none, |
| | | ), |
| | | )), |
| | | ], |
| | | ), |
| | | ), |
| | | Container(height: 52), |
| | | MyFillButton( |
| | | "注 册", |
| | | 10, |
| | | height: 45, |
| | | color: ColorConstant.theme, |
| | | fontSize: 17, |
| | | enable: StringUtil.isEmail(emailController!.value.text) && |
| | | codeController!.value.text.length >= 4 && |
| | | pwdController!.value.text.length >= 6, |
| | | onClick: () { |
| | | if (!(StringUtil.isEmail(emailController!.value.text) && |
| | | codeController!.value.text.length >= 4 && |
| | | pwdController!.value.text.length >= 6)) { |
| | | return; |
| | | } |
| | | |
| | | if (!checked) { |
| | | ToastUtil.toast("请同意用户协议与隐私政策", context); |
| | | return; |
| | | } |
| | | |
| | | UserApiUtil.registerByEmail(context, emailController!.value.text, |
| | | pwdController!.value.text, codeController!.value.text) |
| | | .then((value) { |
| | | print("结果: $value"); |
| | | if (value!["IsPost"] == "true") { |
| | | ToastUtil.toast("注册成功", context); |
| | | //去登录 |
| | | UserApiUtil.loginByEmail( |
| | | context, email, pwdController!.value.text) |
| | | .then((value) { |
| | | if (value!["IsPost"] == "true") { |
| | | UserInfo user = UserInfo.fromJson(value["Data"]); |
| | | _loginSuccess(user); |
| | | } |
| | | }); |
| | | } else { |
| | | ToastUtil.toast(value["Error"], context); |
| | | } |
| | | }); |
| | | }, |
| | | ), |
| | | ], |
| | | ); |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:async'; |
| | | import 'dart:convert'; |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import 'package:flutter/services.dart'; |
| | | import 'package:fluwx_no_pay/fluwx_no_pay.dart' as fluwx; |
| | | import '../../api/user_api.dart'; |
| | | import 'package:html/dom.dart' as dom; |
| | | import '../../api/http.dart'; |
| | | import '../../model/user/user_info.dart'; |
| | | import '../../ui/common/browser.dart'; |
| | | import '../../ui/widget/button.dart'; |
| | | import '../../utils/event_bus_util.dart'; |
| | | import '../../utils/pageutils.dart'; |
| | | import '../../utils/push_util.dart'; |
| | | import '../../utils/string_util.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | import '../../utils/ui_utils.dart'; |
| | | import '../../utils/user_util.dart'; |
| | | import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart'; |
| | | import '../widget/nav.dart'; |
| | | |
| | | void main() { |
| | | runApp(MyApp()); |
| | | } |
| | | |
| | | class MyApp extends StatelessWidget { |
| | | // This widget is the root of your application. |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return MaterialApp( |
| | | title: '登录', |
| | | theme: ThemeData(primaryColor: Color(0xFFF5F5F5)), |
| | | home: LoginPage(title: ''), |
| | | ); |
| | | } |
| | | } |
| | | |
| | | class LoginPage extends StatefulWidget { |
| | | //阿里云一键登录 |
| | | static const messageChannel = |
| | | BasicMessageChannel('AliyunPhoneNumberAuth', StandardMessageCodec()); |
| | | |
| | | LoginPage({Key? key, required this.title}) : super(key: key); |
| | | |
| | | // This widget is the home page of your application. It is stateful, meaning |
| | | // that it has a State object (defined below) that contains fields that affect |
| | | // how it looks. |
| | | |
| | | // This class is the configuration for the state. It holds the values (in this |
| | | // case the title) provided by the parent (in this case the App widget) and |
| | | // used by the build method of the State. Fields in a Widget subclass are |
| | | // always marked "final". |
| | | |
| | | final String title; |
| | | |
| | | @override |
| | | _LoginPageState createState() => _LoginPageState(); |
| | | } |
| | | |
| | | class _LoginPageState extends State<LoginPage> |
| | | with SingleTickerProviderStateMixin { |
| | | bool oneKeyLogin = false; |
| | | bool checked = false; |
| | | TextEditingController? phoneController = TextEditingController(); |
| | | TextEditingController? codeController = TextEditingController(); |
| | | String phone = ""; |
| | | String code = ""; |
| | | Timer? timer; |
| | | |
| | | //重新发送验证码倒计时 |
| | | int? reSendSMSTimeLeft; |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | reSendSMSTimeLeft = -1; |
| | | //初始化微信登录监听 |
| | | fluwx.weChatResponseEventHandler.distinct((a, b) => a == b).listen((res) { |
| | | if (res is fluwx.WeChatAuthResponse) { |
| | | int errCode = res.errCode; |
| | | if (errCode == 0) { |
| | | String? code = res.code; |
| | | //TODO 把微信登录返回的code传给后台,剩下的事就交给后台处理 |
| | | } else if (errCode == -4) { |
| | | //showToast("用户拒绝授权"); |
| | | } else if (errCode == -2) { |
| | | // showToast("用户取消授权"); |
| | | } |
| | | } |
| | | }); |
| | | } |
| | | |
| | | bool _aliyunLogin = false; |
| | | |
| | | void aliyunOneKeyLogin() async { |
| | | if (_aliyunLogin) { |
| | | return; |
| | | } |
| | | _aliyunLogin = true; |
| | | Future.delayed(const Duration(milliseconds: 1000), () { |
| | | _aliyunLogin = false; |
| | | }); |
| | | |
| | | showLoading(context); |
| | | |
| | | Map value = |
| | | await LoginPage.messageChannel.send({"method": "checkEnv"}) as Map; |
| | | dismissDialog(context); |
| | | if (value["code"] == 0) { |
| | | value = |
| | | await LoginPage.messageChannel.send({"method": "startLogin"}) as Map; |
| | | print("返回内容:${jsonEncode(value)}"); |
| | | if (value["code"] == 0) { |
| | | Map<String, dynamic>? resultValue = |
| | | await UserApiUtil.loginByPhone(context, "", "", value["token"]); |
| | | |
| | | LoginPage.messageChannel.send({"method": "closeLogin"}); |
| | | |
| | | if (resultValue == null) return; |
| | | |
| | | if (resultValue["code"] == 0) { |
| | | UserInfo user = UserInfo.fromJson(resultValue["data"]); |
| | | _loginSuccess(user); |
| | | } else { |
| | | ToastUtil.toast(value["msg"],context); |
| | | } |
| | | } else if (value["code"] == 700000) { |
| | | //Fluttertoast.showToast(msg: "取消登录了"); |
| | | } |
| | | } else { |
| | | ToastUtil.toast(value["msg"],context); |
| | | setState(() { |
| | | oneKeyLogin = false; |
| | | }); |
| | | } |
| | | } |
| | | |
| | | void qqLogin() async { |
| | | Map value = await UserUtil.loginQQ(); |
| | | } |
| | | |
| | | void _loginSuccess(UserInfo user) { |
| | | UserUtil.setUserInfo(user).then((value) { |
| | | print("登录成功"); |
| | | eventBus.fire(LoginEventBus(true)); |
| | | PushUtil.setAlias(user.id!.toString()).then((value) { |
| | | Navigator.pop(context); |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | @override |
| | | void dispose() { |
| | | if (timer != null) { |
| | | timer!.cancel(); |
| | | } |
| | | super.dispose(); |
| | | } |
| | | |
| | | BoxDecoration getItemDecoration(Color bgColor, Color shadowColor) { |
| | | return BoxDecoration( |
| | | borderRadius: const BorderRadius.all(Radius.elliptical(10, 10)), |
| | | color: bgColor, |
| | | boxShadow: [ |
| | | BoxShadow( |
| | | color: shadowColor, |
| | | blurRadius: 2.0, |
| | | offset: Offset(0.0, 5.0), //阴影y轴偏移量 |
| | | spreadRadius: 1 //阴影扩散程度 |
| | | ) |
| | | ]); |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return Scaffold( |
| | | resizeToAvoidBottomInset: false, |
| | | backgroundColor: Colors.white, |
| | | body: Stack( |
| | | children: [ |
| | | Column(children: [ |
| | | //登录内容区域 |
| | | Expanded( |
| | | child: SingleChildScrollView( |
| | | child: Padding( |
| | | padding: EdgeInsets.fromLTRB(40, 100, 40, 14), |
| | | child: Column( |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | children: [ |
| | | Image.asset( |
| | | "assets/imgs/login/ic_login_logo.png", |
| | | width: 131, |
| | | ), |
| | | Container( |
| | | height: 70, |
| | | ), |
| | | Container( |
| | | constraints: BoxConstraints(minHeight: 200), |
| | | child: getLoginContent()), |
| | | Container( |
| | | height: 30, |
| | | ), |
| | | Text( |
| | | "其他方式登录", |
| | | style: TextStyle( |
| | | color: Color(0xFF666666), fontSize: 14), |
| | | ), |
| | | Container( |
| | | height: 21, |
| | | ), |
| | | Row( |
| | | mainAxisAlignment: |
| | | MainAxisAlignment.spaceAround, |
| | | children: [ |
| | | // getThirdLoginItem("微信登录", |
| | | // "assets/images/login/ic_login_wx.png"), |
| | | // getThirdLoginItem("QQ登录", |
| | | // "assets/images/login/ic_login_qq.png"), |
| | | oneKeyLogin |
| | | ? getThirdLoginItem("手机号登录", |
| | | "assets/imgs/login/ic_login_phone.png") |
| | | : getThirdLoginItem("一键登录", |
| | | "assets/imgs/login/ic_login_onekey.png"), |
| | | ], |
| | | ) |
| | | ])))), |
| | | //用户协议与隐私政策 |
| | | Row(children: [ |
| | | Container( |
| | | width: 25, |
| | | ), |
| | | RoundCheckBox( |
| | | value: checked, |
| | | onChanged: (value) { |
| | | setState(() { |
| | | checked = value; |
| | | }); |
| | | }, |
| | | ), |
| | | Expanded( |
| | | child: Container( |
| | | child: HtmlWidget( |
| | | "<p>登录即表明同意 <a href='${Constant.PROTOCOL_URL}'>用户协议</a> 和 <a href='${Constant.PRIVACY_URL}'>隐私政策</a> </p>", |
| | | textStyle: |
| | | const TextStyle(color: Color(0xFF999999)), |
| | | onTapUrl: (url) { |
| | | String? title = ""; |
| | | if (url == Constant.PROTOCOL_URL) { |
| | | title = "用户协议"; |
| | | } else { |
| | | title = "隐私政策"; |
| | | } |
| | | |
| | | NavigatorUtil.navigateToNextPage( |
| | | context, |
| | | BrowserPage( |
| | | title: title, |
| | | url: url, |
| | | ), |
| | | (data) {}); |
| | | |
| | | return true; |
| | | }))), |
| | | ]) |
| | | ]), |
| | | //关闭按钮 |
| | | Positioned( |
| | | top: 30, |
| | | left: 20, |
| | | child: InkWell( |
| | | onTap: () { |
| | | popPage(context); |
| | | }, |
| | | child: Icon( |
| | | Icons.close, |
| | | size: 30, |
| | | ))) |
| | | ], |
| | | )); |
| | | } |
| | | |
| | | Widget getLoginContent() { |
| | | return oneKeyLogin |
| | | ? Container( |
| | | child: Column(children: [ |
| | | MyFillButton( |
| | | "本机号码一键登录", |
| | | 10, |
| | | height: 45, |
| | | fontSize: 17, |
| | | onClick: () { |
| | | aliyunOneKeyLogin(); |
| | | }, |
| | | ) |
| | | ])) |
| | | : Column( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | Container( |
| | | alignment: Alignment.centerLeft, |
| | | padding: EdgeInsets.fromLTRB(20, 0, 5, 0), |
| | | decoration: BoxDecoration( |
| | | color: const Color(0xFFF5F5F5), |
| | | borderRadius: BorderRadius.circular(10)), |
| | | child: Row( |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | children: [ |
| | | Image.asset( |
| | | "assets/imgs/login/icon_phone.png", |
| | | width: 14, |
| | | height: 20, |
| | | ), |
| | | Container(width: 14), |
| | | Expanded( |
| | | child: TextField( |
| | | style: TextStyle(color: Color(0xFF333333), fontSize: 17), |
| | | onChanged: (value) { |
| | | setState(() { |
| | | phone = value; |
| | | }); |
| | | }, |
| | | textAlign: TextAlign.start, |
| | | keyboardType: TextInputType.phone, |
| | | controller: phoneController, |
| | | maxLength: 11, |
| | | decoration: InputDecoration( |
| | | counterText: "", |
| | | hintText: "请输入手机号", |
| | | hintStyle: |
| | | TextStyle(color: Color(0xFFCCCCCC), fontSize: 17), |
| | | contentPadding: EdgeInsets.only(bottom: 3), |
| | | border: InputBorder.none, |
| | | focusedBorder: InputBorder.none, |
| | | ), |
| | | )), |
| | | ], |
| | | ), |
| | | ), |
| | | Container(height: 10), |
| | | Container( |
| | | alignment: Alignment.centerLeft, |
| | | padding: EdgeInsets.fromLTRB(20, 0, 7, 0), |
| | | decoration: BoxDecoration( |
| | | color: const Color(0xFFF5F5F5), |
| | | borderRadius: BorderRadius.circular(10)), |
| | | child: Row( |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | children: [ |
| | | Image.asset( |
| | | "assets/imgs/login/icon_code.png", |
| | | width: 16, |
| | | ), |
| | | Container(width: 14), |
| | | Expanded( |
| | | child: TextField( |
| | | style: TextStyle(color: Color(0xFF333333), fontSize: 17), |
| | | onChanged: (value) { |
| | | setState(() { |
| | | phone = value; |
| | | }); |
| | | }, |
| | | textAlign: TextAlign.start, |
| | | keyboardType: TextInputType.phone, |
| | | controller: codeController, |
| | | maxLength: 8, |
| | | decoration: const InputDecoration( |
| | | counterText: "", |
| | | hintText: "请输入验证码", |
| | | hintStyle: |
| | | TextStyle(color: Color(0xFFCCCCCC), fontSize: 17), |
| | | contentPadding: EdgeInsets.only(bottom: 3), |
| | | border: InputBorder.none, |
| | | focusedBorder: InputBorder.none, |
| | | ), |
| | | )), |
| | | MyFillButton( |
| | | (reSendSMSTimeLeft! > 0 |
| | | ? (reSendSMSTimeLeft.toString() + "S重新获取") |
| | | : reSendSMSTimeLeft == -1 |
| | | ? "获取验证码" |
| | | : "重新获取") |
| | | .toString(), |
| | | 10, |
| | | height: 34, |
| | | padding: const EdgeInsets.fromLTRB(10, 0, 10, 0), |
| | | enable: (reSendSMSTimeLeft! < 1 && |
| | | StringUtil.isMobile(phoneController!.value.text)), |
| | | onClick: () { |
| | | if (!(reSendSMSTimeLeft! < 1 && |
| | | StringUtil.isMobile(phoneController!.value.text))) { |
| | | return; |
| | | } |
| | | //发送验证码 |
| | | UserApiUtil.sendSMS( |
| | | context, phoneController!.value.text) |
| | | .then((value) { |
| | | if (value != null && value["code"] == 0) { |
| | | setState(() { |
| | | reSendSMSTimeLeft = 60; |
| | | //倒计时 |
| | | timer = Timer.periodic(const Duration(seconds: 1), |
| | | (timer) { |
| | | if (reSendSMSTimeLeft! > 0) { |
| | | setState(() { |
| | | reSendSMSTimeLeft = reSendSMSTimeLeft! - 1; |
| | | }); |
| | | } else { |
| | | timer.cancel(); |
| | | } |
| | | }); |
| | | }); |
| | | } else { |
| | | ToastUtil.toast(value!["msg"],context); |
| | | } |
| | | }); |
| | | }, |
| | | ), |
| | | ], |
| | | ), |
| | | ), |
| | | Container(height: 10), |
| | | const Text("未注册的手机号注册后系统会自动创建账户", |
| | | style: TextStyle(color: Color(0xFFA0A0A0), fontSize: 12)), |
| | | Container(height: 20), |
| | | MyFillButton( |
| | | "登录", |
| | | 10, |
| | | height: 45, |
| | | color: const Color(0xFFFF2B4B), |
| | | fontSize: 17, |
| | | enable: StringUtil.isMobile(phoneController!.value.text) && |
| | | codeController!.value.text.length >= 4, |
| | | onClick: () { |
| | | if (!(StringUtil.isMobile(phoneController!.value.text) && |
| | | codeController!.value.text.length >= 4)) { |
| | | return; |
| | | } |
| | | |
| | | if (!checked) { |
| | | ToastUtil.toast("请同意用户协议与隐私政策",context); |
| | | return; |
| | | } |
| | | |
| | | UserApiUtil.loginByPhone(context, phoneController!.value.text, |
| | | codeController!.value.text, "") |
| | | .then((value) { |
| | | print("结果: $value"); |
| | | if (value!["code"] == 0) { |
| | | UserInfo user = UserInfo.fromJson(value["data"]); |
| | | _loginSuccess(user); |
| | | } else { |
| | | ToastUtil.toast(value["msg"],context); |
| | | } |
| | | }); |
| | | }, |
| | | ), |
| | | ], |
| | | ); |
| | | } |
| | | |
| | | Widget getThirdLoginItem(String name, String iconAsset) { |
| | | return InkWell( |
| | | onTap: () { |
| | | if (name == "一键登录" || name == "手机号登录") { |
| | | setState(() { |
| | | oneKeyLogin = !oneKeyLogin; |
| | | }); |
| | | } else if (name == "QQ登录") { |
| | | qqLogin(); |
| | | } else if (name == "微信登录") { |
| | | UserUtil.loginWX(); |
| | | } |
| | | }, |
| | | child: Container( |
| | | constraints: BoxConstraints(minWidth: 80), |
| | | child: Column( |
| | | children: [ |
| | | Image.asset( |
| | | iconAsset, |
| | | height: 49, |
| | | ), |
| | | Container( |
| | | height: 8, |
| | | ), |
| | | Text( |
| | | name, |
| | | style: const TextStyle(color: Color(0xFF9DAAB3), fontSize: 12), |
| | | ) |
| | | ], |
| | | )), |
| | | ); |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:ui'; |
| | | import 'dart:io'; |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import '../../api/user_api.dart'; |
| | | import '../../model/user/user_info.dart'; |
| | | import '../../ui/widget/button.dart'; |
| | | import '../../ui/widget/video_item.dart'; |
| | | import '../../utils/image_util.dart'; |
| | | import '../../api/http.dart'; |
| | | import '../../ui/common/browser.dart'; |
| | | import '../../ui/widget/dialog.dart'; |
| | | import '../../ui/widget/nav.dart'; |
| | | import '../../utils/cache_util.dart'; |
| | | import '../../utils/config_util.dart'; |
| | | import '../../utils/event_bus_util.dart'; |
| | | import '../../utils/pageutils.dart'; |
| | | import '../../utils/push_util.dart'; |
| | | import '../../utils/setting_util.dart'; |
| | | import '../../utils/string_util.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | import '../../utils/ui_utils.dart'; |
| | | import '../../utils/user_util.dart'; |
| | | import 'package:package_info/package_info.dart'; |
| | | import 'package:image_picker/image_picker.dart'; |
| | | |
| | | void main() { |
| | | runApp(MyApp()); |
| | | } |
| | | |
| | | class MyApp extends StatelessWidget { |
| | | // This widget is the root of your application. |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return MaterialApp( |
| | | title: '个人信息', |
| | | theme: ThemeData(primaryColor: const Color(0xFFF5F5F5)), |
| | | home: PersonInfoPage(title: ''), |
| | | ); |
| | | } |
| | | } |
| | | |
| | | class PersonInfoPage extends StatefulWidget { |
| | | PersonInfoPage({Key? key, required this.title}) : super(key: key); |
| | | |
| | | // This widget is the home page of your application. It is stateful, meaning |
| | | // that it has a State object (defined below) that contains fields that affect |
| | | // how it looks. |
| | | |
| | | // This class is the configuration for the state. It holds the values (in this |
| | | // case the title) provided by the parent (in this case the App widget) and |
| | | // used by the build method of the State. Fields in a Widget subclass are |
| | | // always marked "final". |
| | | |
| | | final String title; |
| | | |
| | | @override |
| | | _PersonInfoPageState createState() => _PersonInfoPageState(); |
| | | } |
| | | |
| | | class _PersonInfoPageState extends State<PersonInfoPage> |
| | | with SingleTickerProviderStateMixin { |
| | | UserInfo? _user; |
| | | |
| | | String? editPortrait; |
| | | String? editNickName; |
| | | int? editSex; |
| | | DateTime? editBirthday; |
| | | String? editSign; |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | UserUtil.getUserInfo().then((value) { |
| | | setState(() { |
| | | _user = value; |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | void selectImg() async { |
| | | File? image = await ImageUtil.selectAndCropImage(); |
| | | if (image == null) { |
| | | return; |
| | | } |
| | | //image.path |
| | | setState(() { |
| | | editPortrait = image.path; |
| | | }); |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return Scaffold( |
| | | backgroundColor: const Color(0xFFDADADA), |
| | | body: _user == null |
| | | ? Container() |
| | | : Column( |
| | | children: [ |
| | | TopNavBar( |
| | | title: "个人信息", |
| | | rightText: canSave() ? "保存" : null, |
| | | rightClick: canSave() |
| | | ? () { |
| | | //保存 |
| | | UserApiUtil.updateUserInfo(context, |
| | | nickName: editNickName, |
| | | sex: editSex, |
| | | birthDay: editBirthday == null |
| | | ? null |
| | | : "${editBirthday!.year}/${editBirthday!.month}/${editBirthday!.day}", |
| | | sign: editSign, |
| | | portraitPath: editPortrait) |
| | | .then((value) { |
| | | if (value == null) { |
| | | return; |
| | | } |
| | | |
| | | if (value["IsPost"] == "true") { |
| | | ToastUtil.toast("修改成功", context); |
| | | popPage(context); |
| | | } else { |
| | | ToastUtil.toast(value["Error"], context); |
| | | } |
| | | }); |
| | | } |
| | | : null, |
| | | ), |
| | | Expanded( |
| | | child: SingleChildScrollView( |
| | | child: Column(children: [ |
| | | Container( |
| | | height: 10, |
| | | color: const Color(0xFFDADADA), |
| | | ), |
| | | getPortraitItemView(onClick: () { |
| | | //选择头像 |
| | | selectImg(); |
| | | }), |
| | | getCommonItemView( |
| | | title: "昵称", |
| | | content: editNickName ?? _user!.nickname!, |
| | | onClick: () { |
| | | showEditNickName(editNickName ?? _user!.nickname!); |
| | | }), |
| | | getCommonItemView( |
| | | title: "性别", |
| | | content: |
| | | _getSexDesc((editSex ?? int.parse(_user!.sex!))), |
| | | onClick: () { |
| | | showEditSex(editSex ?? int.parse(_user!.sex!)); |
| | | }), |
| | | getCommonItemView( |
| | | title: "生日", |
| | | content: _getBirthDay(), |
| | | onClick: () { |
| | | showEditBirthDay((editBirthday ?? |
| | | ((_user!.birthday == null || |
| | | _user!.birthday!.isEmpty) |
| | | ? null |
| | | : parseDate(_user!.birthday!)))); |
| | | }), |
| | | getCommonItemView( |
| | | title: "邮箱", |
| | | content: _user!.email!, |
| | | onClick: () {}, |
| | | canIn: false), |
| | | getCommonItemView( |
| | | title: "ID", |
| | | content: _user!.id!, |
| | | onClick: () {}, |
| | | canIn: false), |
| | | Container( |
| | | height: 10, |
| | | color: const Color(0xFFDADADA), |
| | | ), |
| | | getSignItemView(onClick: () { |
| | | showEditSign(editSign ?? _user!.sign); |
| | | }), |
| | | ]))), |
| | | ], |
| | | )); |
| | | } |
| | | |
| | | bool canSave() { |
| | | return editNickName != null || |
| | | editSex != null || |
| | | editBirthday != null || |
| | | editSign != null || |
| | | editPortrait != null; |
| | | } |
| | | |
| | | String _getSexDesc(int? sex) { |
| | | switch (sex) { |
| | | case 1: |
| | | return "男"; |
| | | case 2: |
| | | return "女"; |
| | | } |
| | | return "未知"; |
| | | } |
| | | |
| | | String _getBirthDay() { |
| | | DateTime? date = (editBirthday ?? |
| | | ((_user!.birthday == null || _user!.birthday!.isEmpty) |
| | | ? null |
| | | : parseDate(_user!.birthday!))); |
| | | if (date == null) { |
| | | return "请选择出生日期"; |
| | | } |
| | | |
| | | return "${date.year}/${date.month}/${date.day}"; |
| | | } |
| | | |
| | | DateTime parseDate(String date) { |
| | | List<String> sts = date.split("/"); |
| | | |
| | | return DateTime(int.parse(sts[0]), int.parse(sts[1]), int.parse(sts[2])); |
| | | } |
| | | |
| | | Widget getCommonItemView( |
| | | {required String title, |
| | | String content = "", |
| | | required GestureTapCallback onClick, |
| | | bool canIn = true}) { |
| | | return Container( |
| | | height: 53, |
| | | margin: const EdgeInsets.fromLTRB(0, 0, 0, 1), |
| | | color: Colors.white, |
| | | child: InkWell( |
| | | onTap: () { |
| | | onClick(); |
| | | }, |
| | | child: Container( |
| | | padding: const EdgeInsets.fromLTRB(20, 0, 20, 0), |
| | | child: Flex( |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | direction: Axis.horizontal, |
| | | children: [ |
| | | Text( |
| | | title, |
| | | style: |
| | | const TextStyle(fontSize: 16, color: Color(0xFF333333)), |
| | | ), |
| | | Expanded( |
| | | child: Flex( |
| | | direction: Axis.horizontal, |
| | | mainAxisAlignment: MainAxisAlignment.end, |
| | | children: [ |
| | | Container( |
| | | child: Text(content, |
| | | style: const TextStyle( |
| | | fontSize: 14, color: Color(0xFF959595))), |
| | | margin: const EdgeInsets.fromLTRB(0, 0, 13.5, 0), |
| | | ), |
| | | canIn |
| | | ? Image.asset( |
| | | "assets/imgs/icon_person_info_input.png", |
| | | height: 13.5, |
| | | ) |
| | | : Container() |
| | | ], |
| | | )) |
| | | ], |
| | | )), |
| | | ), |
| | | ); |
| | | } |
| | | |
| | | Widget getPortraitItemView({required GestureTapCallback onClick}) { |
| | | return Container( |
| | | height: 75, |
| | | margin: const EdgeInsets.fromLTRB(0, 0, 0, 1), |
| | | color: Colors.white, |
| | | child: InkWell( |
| | | onTap: () { |
| | | onClick(); |
| | | }, |
| | | child: Container( |
| | | padding: const EdgeInsets.fromLTRB(20, 0, 20, 0), |
| | | child: Flex( |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | direction: Axis.horizontal, |
| | | children: [ |
| | | const Text( |
| | | "头像", |
| | | style: TextStyle(fontSize: 16, color: Color(0xFF333333)), |
| | | ), |
| | | Expanded( |
| | | child: Flex( |
| | | direction: Axis.horizontal, |
| | | mainAxisAlignment: MainAxisAlignment.end, |
| | | children: [ |
| | | Container( |
| | | child: ClipRRect( |
| | | borderRadius: BorderRadius.circular(60), |
| | | child: editPortrait == null |
| | | ? CommonImage( |
| | | _user!.portrait!, |
| | | height: 57, |
| | | width: 57, |
| | | ) |
| | | : Image.file( |
| | | File(editPortrait!), |
| | | fit: BoxFit.cover, |
| | | height: 57, |
| | | width: 57, |
| | | )), |
| | | margin: const EdgeInsets.fromLTRB(0, 0, 13.5, 0), |
| | | ), |
| | | Image.asset( |
| | | "assets/imgs/icon_person_info_input.png", |
| | | height: 13.5, |
| | | ) |
| | | ], |
| | | )) |
| | | ], |
| | | )), |
| | | ), |
| | | ); |
| | | } |
| | | |
| | | Widget getSignItemView({required GestureTapCallback onClick}) { |
| | | return Container( |
| | | height: 200, |
| | | margin: const EdgeInsets.fromLTRB(0, 0, 0, 1), |
| | | color: Colors.white, |
| | | child: InkWell( |
| | | onTap: () { |
| | | onClick(); |
| | | }, |
| | | child: Container( |
| | | padding: const EdgeInsets.fromLTRB(20, 0, 20, 0), |
| | | child: |
| | | Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ |
| | | Container( |
| | | height: 18, |
| | | ), |
| | | Flex( |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | direction: Axis.horizontal, |
| | | children: [ |
| | | const Text( |
| | | "个性签名", |
| | | style: TextStyle(fontSize: 16, color: Color(0xFF333333)), |
| | | ), |
| | | Expanded( |
| | | child: Flex( |
| | | direction: Axis.horizontal, |
| | | mainAxisAlignment: MainAxisAlignment.end, |
| | | children: [ |
| | | Image.asset( |
| | | "assets/imgs/icon_person_info_input.png", |
| | | height: 13.5, |
| | | ) |
| | | ], |
| | | )) |
| | | ], |
| | | ), |
| | | Container( |
| | | height: 21, |
| | | ), |
| | | Text( |
| | | _getSign(), |
| | | textAlign: TextAlign.start, |
| | | style: const TextStyle(color: Color(0xFFBEBEBE), fontSize: 15), |
| | | ) |
| | | ]), |
| | | ), |
| | | )); |
| | | } |
| | | |
| | | _getSign() { |
| | | return editSign ?? |
| | | ((_user!.sign == null || _user!.sign!.isEmpty) |
| | | ? "这个人很懒,什么也没有留下" |
| | | : _user!.sign!); |
| | | } |
| | | |
| | | showAlertDilaog(String title, Widget content, VoidCallback sure, |
| | | {VoidCallback? cancel}) { |
| | | showCupertinoDialog( |
| | | context: context, |
| | | builder: (context) { |
| | | return AlertDialog( |
| | | title: Text(title), |
| | | content: content, |
| | | actions: <Widget>[ |
| | | TextButton( |
| | | child: Text('取消'), |
| | | onPressed: () { |
| | | Navigator.of(context).pop(); |
| | | }, |
| | | ), |
| | | TextButton( |
| | | child: Text('确定'), |
| | | onPressed: () { |
| | | sure(); |
| | | }, |
| | | ), |
| | | ], |
| | | ); |
| | | }); |
| | | } |
| | | |
| | | void showEditNickName(String nickName) { |
| | | TextEditingController controller = TextEditingController(); |
| | | controller.text = nickName; |
| | | showAlertDilaog( |
| | | "修改昵称", |
| | | TextField( |
| | | controller: controller, |
| | | ), () { |
| | | if (controller.text.isEmpty) { |
| | | return; |
| | | } |
| | | Navigator.of(context).pop(); |
| | | setState(() { |
| | | editNickName = controller.text; |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | void showEditSex(int sex) { |
| | | int? tempSex; |
| | | setState(() { |
| | | tempSex = sex; |
| | | }); |
| | | DialogUtil.showDialogBottom( |
| | | context, |
| | | Container( |
| | | margin: const EdgeInsets.all(10), |
| | | alignment: Alignment.bottomCenter, |
| | | child: Stack(alignment: Alignment.bottomCenter, children: [ |
| | | Container( |
| | | margin: const EdgeInsets.only(bottom: 50), |
| | | decoration: BoxDecoration( |
| | | color: Colors.white, |
| | | borderRadius: BorderRadius.circular(10)), |
| | | child: Container( |
| | | height: 100, |
| | | alignment: Alignment.center, |
| | | child: Column( |
| | | children: [ |
| | | InkWell( |
| | | onTap: () { |
| | | setState(() { |
| | | editSex = 1; |
| | | }); |
| | | Navigator.of(context).pop(); |
| | | }, |
| | | child: Container( |
| | | height: 50, |
| | | alignment: Alignment.center, |
| | | child: const Text( |
| | | "男", |
| | | style: TextStyle( |
| | | fontSize: 18, |
| | | color: ColorConstant.theme), |
| | | ))), |
| | | InkWell( |
| | | onTap: () { |
| | | setState(() { |
| | | editSex = 2; |
| | | }); |
| | | Navigator.of(context).pop(); |
| | | }, |
| | | child: Container( |
| | | height: 50, |
| | | alignment: Alignment.center, |
| | | child: const Text( |
| | | "女", |
| | | style: TextStyle( |
| | | fontSize: 18, |
| | | color: ColorConstant.theme), |
| | | ))), |
| | | ], |
| | | ))), |
| | | MyFillButton( |
| | | "取消", |
| | | 20, |
| | | height: 40, |
| | | color: Colors.grey, |
| | | fontSize: 14, |
| | | onClick: () { |
| | | popPage(context); |
| | | }, |
| | | ) |
| | | ]))); |
| | | } |
| | | |
| | | //编辑生日 |
| | | void showEditBirthDay(DateTime? dateTime) async { |
| | | dateTime ??= DateTime.now(); |
| | | Locale myLocale = Localizations.localeOf(context); |
| | | DateTime? date = await showDatePicker( |
| | | context: context, |
| | | initialDate: dateTime, |
| | | firstDate: DateTime(1900), |
| | | lastDate: DateTime(DateTime.now().year + 1), |
| | | locale: myLocale); |
| | | if (date != null) { |
| | | setState(() { |
| | | editBirthday = date; |
| | | }); |
| | | } |
| | | } |
| | | |
| | | void showEditSign(String? sign) { |
| | | sign ??= ""; |
| | | TextEditingController controller = TextEditingController(); |
| | | controller.text = sign; |
| | | showAlertDilaog( |
| | | "编辑签名", |
| | | TextField( |
| | | controller: controller, |
| | | maxLines: 5, |
| | | maxLength: 200, |
| | | decoration: const InputDecoration( |
| | | focusedBorder: InputBorder.none, |
| | | border: InputBorder.none, |
| | | counterStyle: TextStyle(color: Color(0xFF666666)), |
| | | hintText: "请输入签名内容", |
| | | hintStyle: TextStyle(fontSize: 15, color: Color(0xFF999999))), |
| | | ), () { |
| | | if (controller.text.isEmpty) { |
| | | return; |
| | | } |
| | | |
| | | Navigator.of(context).pop(); |
| | | setState(() { |
| | | editSign = controller.text; |
| | | }); |
| | | }); |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import '../../api/user_api.dart'; |
| | | import '../../utils/share_preference.dart'; |
| | | import '../../api/http.dart'; |
| | | import '../../ui/common/browser.dart'; |
| | | import '../../ui/widget/dialog.dart'; |
| | | import '../../ui/widget/nav.dart'; |
| | | import '../../utils/cache_util.dart'; |
| | | import '../../utils/config_util.dart'; |
| | | import '../../utils/event_bus_util.dart'; |
| | | import '../../utils/pageutils.dart'; |
| | | import '../../utils/push_util.dart'; |
| | | import '../../utils/setting_util.dart'; |
| | | import '../../utils/string_util.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | import '../../utils/ui_utils.dart'; |
| | | import '../../utils/user_util.dart'; |
| | | import 'package:package_info/package_info.dart'; |
| | | |
| | | void main() { |
| | | runApp(MyApp()); |
| | | } |
| | | |
| | | class MyApp extends StatelessWidget { |
| | | // This widget is the root of your application. |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return MaterialApp( |
| | | title: '设置', |
| | | theme: ThemeData(primaryColor: const Color(0xFFF5F5F5)), |
| | | home: SettingPage(title: ''), |
| | | ); |
| | | } |
| | | } |
| | | |
| | | class SettingPage extends StatefulWidget { |
| | | SettingPage({Key? key, required this.title}) : super(key: key); |
| | | |
| | | // This widget is the home page of your application. It is stateful, meaning |
| | | // that it has a State object (defined below) that contains fields that affect |
| | | // how it looks. |
| | | |
| | | // This class is the configuration for the state. It holds the values (in this |
| | | // case the title) provided by the parent (in this case the App widget) and |
| | | // used by the build method of the State. Fields in a Widget subclass are |
| | | // always marked "final". |
| | | |
| | | final String title; |
| | | |
| | | @override |
| | | _SettingPageState createState() => _SettingPageState(); |
| | | } |
| | | |
| | | class _SettingPageState extends State<SettingPage> |
| | | with SingleTickerProviderStateMixin { |
| | | bool msg = false; |
| | | bool ad = false; |
| | | bool login = false; |
| | | |
| | | String cacheSize = "0B"; |
| | | String version = ""; |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | UserUtil.isLogin().then((value) { |
| | | setState(() { |
| | | login = value; |
| | | }); |
| | | }); |
| | | |
| | | _getCacheSize(); |
| | | _getVersion(); |
| | | |
| | | SettingUtil.isEnablePush().then((value) { |
| | | setState(() { |
| | | msg = value; |
| | | }); |
| | | }); |
| | | |
| | | SettingUtil.isEnableRecommendAd().then((value) { |
| | | ad = value; |
| | | }); |
| | | } |
| | | |
| | | ///获取缓存大小 |
| | | void _getCacheSize() async { |
| | | int byteSize = await CacheUtil.total(); |
| | | String? desc; |
| | | if (byteSize < 1000) { |
| | | desc = byteSize.toString() + "KB"; |
| | | } else if (byteSize < 1000 * 1000) { |
| | | desc = (byteSize / 1000).toStringAsFixed(0) + "KB"; |
| | | } else if (byteSize < 1000 * 1000 * 1000) { |
| | | desc = (byteSize / (1000 * 1000)).toStringAsFixed(1) + "MB"; |
| | | } else { |
| | | desc = (byteSize / (1000 * 1000 * 1000)).toStringAsFixed(1) + "GB"; |
| | | } |
| | | |
| | | setState(() { |
| | | cacheSize = desc!; |
| | | }); |
| | | } |
| | | |
| | | void _getVersion() async { |
| | | PackageInfo packageInfo = await PackageInfo.fromPlatform(); |
| | | setState(() { |
| | | version = packageInfo.version; |
| | | }); |
| | | } |
| | | |
| | | Future logout() async { |
| | | Map<String, dynamic>? map = { |
| | | "code": 0 |
| | | }; //await UserApiUtil.logout(context, uid!); |
| | | if (map["code"] == 0) { |
| | | UserUtil.logout().then((value) { |
| | | setState(() { |
| | | login = false; |
| | | }); |
| | | ToastUtil.toast("退出成功", context); |
| | | UserUtil.getUid(); |
| | | // popPage(context); |
| | | }); |
| | | |
| | | } else { |
| | | ToastUtil.toast(map["msg"], context); |
| | | } |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return Scaffold( |
| | | backgroundColor: const Color(0xFFF5F5F5), |
| | | body: Flex( |
| | | direction: Axis.vertical, |
| | | children: [ |
| | | TopNavBar(title: "设置"), |
| | | getBigItemView( |
| | | title: "推送免打扰", |
| | | content: "关闭后,21:00-09:00不接受任何推送", |
| | | marginTop: 14, |
| | | marginBottom: 1, |
| | | checked: msg, |
| | | changed: (bool value) { |
| | | SettingUtil.setPush(value).then((value1) { |
| | | setState(() { |
| | | msg = value; |
| | | }); |
| | | }); |
| | | }), |
| | | getBigItemView( |
| | | title: "个性化广告推荐", |
| | | content: "关闭后,广告数量将不变,但内容相关度会降低", |
| | | marginTop: 0, |
| | | marginBottom: 16, |
| | | checked: ad, |
| | | changed: (bool value) { |
| | | SettingUtil.setRecommendAd(value).then((value1) { |
| | | setState(() { |
| | | ad = value; |
| | | }); |
| | | }); |
| | | }), |
| | | getCommonItemView( |
| | | title: "清理缓存", |
| | | content: cacheSize, |
| | | onClick: () { |
| | | showCupertinoDialog( |
| | | context: context, |
| | | builder: (context) { |
| | | return AlertDialog( |
| | | title: const Text("是否清楚缓存"), |
| | | actions: <Widget>[ |
| | | TextButton( |
| | | child: const Text('取消'), |
| | | onPressed: () { |
| | | Navigator.of(context).pop(); |
| | | }, |
| | | ), |
| | | TextButton( |
| | | child: const Text('确定'), |
| | | onPressed: () { |
| | | CacheUtil.clear().then((value) { |
| | | ToastUtil.toast("缓存清除成功", context); |
| | | _getCacheSize(); |
| | | }); |
| | | Navigator.of(context).pop(); |
| | | }, |
| | | ), |
| | | ], |
| | | ); |
| | | }); |
| | | }), |
| | | getCommonItemView( |
| | | title: "检查更新", |
| | | content: "版本号:$version", |
| | | onClick: () { |
| | | ToastUtil.toast("已经是最新版本", context); |
| | | }), |
| | | getCommonItemView( |
| | | title: "账户注销", |
| | | content: "", |
| | | onClick: () { |
| | | |
| | | MySharedPreferences.getInstance().getString("unRegisterLink").then((value) { |
| | | if (!StringUtil.isNullOrEmpty(value)) { |
| | | NavigatorUtil.navigateToNextPage(context, |
| | | BrowserPage(title: "账户注销", url: value!), (data) {}); |
| | | } |
| | | }); |
| | | }), |
| | | getCommonItemView( |
| | | title: "隐私投诉", |
| | | content: "", |
| | | onClick: () { |
| | | MySharedPreferences.getInstance().getString("feedBackLink").then((value) { |
| | | if (!StringUtil.isNullOrEmpty(value)) { |
| | | NavigatorUtil.navigateToNextPage(context, |
| | | BrowserPage(title: "隐私投诉", url: value!), (data) {}); |
| | | } |
| | | }); |
| | | }), |
| | | // getCommonItemView( |
| | | // title: "第三方SDK列表", |
| | | // content: "", |
| | | // onClick: () { |
| | | // |
| | | // ConfigUtil.getConfig(ConfigKey.sdkList).then((value) { |
| | | // if (!StringUtil.isNullOrEmpty(value)) { |
| | | // NavigatorUtil.navigateToNextPage( |
| | | // context, BrowserPage(title: "第三方SDK列表", url: value!), (data) {}); |
| | | // } |
| | | // }); |
| | | // }), |
| | | Expanded( |
| | | child: Container(), |
| | | ), |
| | | login |
| | | ? Container( |
| | | child: Stack( |
| | | children: [ |
| | | InkWell( |
| | | onTap: () { |
| | | print("退出登录"); |
| | | logout(); |
| | | }, |
| | | child: Container( |
| | | alignment: Alignment.center, |
| | | height: 48, |
| | | color: Colors.white, |
| | | child: const Text( |
| | | "退出登录", |
| | | style: TextStyle( |
| | | color: ColorConstant.theme, fontSize: 16), |
| | | ))) |
| | | ], |
| | | )) |
| | | : Container(), |
| | | ], |
| | | )); |
| | | } |
| | | |
| | | Widget getCommonItemView( |
| | | {required String title, |
| | | String content = "", |
| | | required GestureTapCallback onClick}) { |
| | | return Container( |
| | | height: 53, |
| | | margin: const EdgeInsets.fromLTRB(0, 0, 0, 1), |
| | | color: Colors.white, |
| | | child: InkWell( |
| | | onTap: () { |
| | | onClick(); |
| | | }, |
| | | child: Container( |
| | | padding: const EdgeInsets.fromLTRB(20, 0, 20, 0), |
| | | child: Flex( |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | direction: Axis.horizontal, |
| | | children: [ |
| | | Text( |
| | | title, |
| | | style: |
| | | const TextStyle(fontSize: 16, color: Color(0xFF333333)), |
| | | ), |
| | | Expanded( |
| | | child: Flex( |
| | | direction: Axis.horizontal, |
| | | mainAxisAlignment: MainAxisAlignment.end, |
| | | children: [ |
| | | Container( |
| | | child: Text(content, |
| | | style: const TextStyle( |
| | | fontSize: 14, color: Color(0xFF959595))), |
| | | margin: const EdgeInsets.fromLTRB(0, 0, 13.5, 0), |
| | | ), |
| | | Image.asset( |
| | | "assets/imgs/common/icon_array_right.png", |
| | | height: 13.5, |
| | | ) |
| | | ], |
| | | )) |
| | | ], |
| | | )), |
| | | ), |
| | | ); |
| | | } |
| | | |
| | | Widget getBigItemView( |
| | | {required String title, |
| | | String content = "", |
| | | bool checked = false, |
| | | required ValueChanged<bool> changed, |
| | | double marginTop = 0, |
| | | double marginBottom = 0}) { |
| | | return Container( |
| | | height: 68, |
| | | margin: EdgeInsets.fromLTRB(0, marginTop, 0, marginBottom), |
| | | color: Colors.white, |
| | | child: Container( |
| | | padding: const EdgeInsets.fromLTRB(20, 0, 20, 0), |
| | | child: Stack(children: [ |
| | | Flex( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | mainAxisAlignment: MainAxisAlignment.center, |
| | | direction: Axis.vertical, |
| | | children: [ |
| | | Text( |
| | | title, |
| | | style: TextStyle(fontSize: 16, color: Color(0xFF333333)), |
| | | ), |
| | | Container( |
| | | child: Text(content, |
| | | style: const TextStyle( |
| | | fontSize: 9, color: Color(0xFF959595))), |
| | | margin: const EdgeInsets.fromLTRB(0, 5, 0, 0), |
| | | ) |
| | | ], |
| | | ), |
| | | Container(), |
| | | Positioned( |
| | | right: 0, |
| | | top: 0, |
| | | bottom: 0, |
| | | child: InkWell( |
| | | onTap: () { |
| | | changed(!checked); |
| | | }, |
| | | child: Image.asset( |
| | | checked |
| | | ? "assets/imgs/common/icon_check_true.png" |
| | | : "assets/imgs/common/icon_check_false.png", |
| | | height: 30, |
| | | width: 60, |
| | | ))) |
| | | ])), |
| | | ); |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import '../../api/video_api.dart'; |
| | | import '../../ui/search/search_result.dart'; |
| | | import '../../ui/widget/ad_express.dart'; |
| | | import '../../ui/widget/search_widget.dart'; |
| | | import '../../utils/ad_util.dart'; |
| | | import '../../utils/video/search_record_util.dart'; |
| | | |
| | | import '../../utils/pageutils.dart'; |
| | | |
| | | class SearchPage extends StatefulWidget { |
| | | SearchPage({Key? key, required this.title}) : super(key: key); |
| | | |
| | | // This widget is the home page of your application. It is stateful, meaning |
| | | // that it has a State object (defined below) that contains fields that affect |
| | | // how it looks. |
| | | |
| | | // This class is the configuration for the state. It holds the values (in this |
| | | // case the title) provided by the parent (in this case the App widget) and |
| | | // used by the build method of the State. Fields in a Widget subclass are |
| | | // always marked "final". |
| | | |
| | | final String title; |
| | | |
| | | @override |
| | | _SearchPageState createState() => _SearchPageState(); |
| | | } |
| | | |
| | | class _SearchPageState extends State<SearchPage> |
| | | with AutomaticKeepAliveClientMixin { |
| | | final SugguestSearchController _sugguestSearchController = |
| | | SugguestSearchController(); |
| | | |
| | | bool showClose = false; |
| | | List<SearchKwModel> hotSearchList = []; |
| | | List<String> recordList = []; |
| | | AdType? adType; |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | AdUtil.getAdType(AdPosition.videoSearch).then((value) { |
| | | setState(() { |
| | | adType = value; |
| | | }); |
| | | }); |
| | | |
| | | getHotSearch(); |
| | | setData(); |
| | | } |
| | | |
| | | void fromBack() { |
| | | FocusScope.of(context).requestFocus(FocusNode()); |
| | | setData(); |
| | | } |
| | | |
| | | void setData() async { |
| | | List<String> tempList = await SearchRecordUtil.listRecord(); |
| | | setState(() { |
| | | recordList = tempList; |
| | | }); |
| | | } |
| | | |
| | | void getHotSearch() async { |
| | | Map<String, dynamic>? result = await SearchApiUtil.getHotSearch(context); |
| | | if (result == null) { |
| | | return; |
| | | } |
| | | if (result["IsPost"] == "true") { |
| | | List<dynamic> list = result["Data"]["data"]; |
| | | List<SearchKwModel> tempList = |
| | | List.filled(list.length, SearchKwModel(0, "")); |
| | | int row = |
| | | list.length % 2 == 0 ? (list.length ~/ 2) : ((list.length + 1) ~/ 2); |
| | | for (var i = 0; i < list.length; i++) { |
| | | if (i + 1 > row) { |
| | | tempList[(i + 1 - row) * 2 - 1] = SearchKwModel(i + 1, list[i]); |
| | | } else { |
| | | tempList[(i + 1) * 2 - 2] = SearchKwModel(i + 1, list[i]); |
| | | } |
| | | } |
| | | |
| | | // mList.sort((a, b) => a.index.compareTo(b.index)); |
| | | |
| | | setState(() { |
| | | hotSearchList = tempList; |
| | | }); |
| | | } |
| | | } |
| | | |
| | | void getSuggestSearch(String key) async { |
| | | Map<String, dynamic>? result = |
| | | await SearchApiUtil.getSuggestSearch(context, key); |
| | | if (result == null) { |
| | | return; |
| | | } |
| | | if (result["IsPost"] == "true") { |
| | | List<dynamic> list = result["Data"]["data"]; |
| | | |
| | | List<String> suggestList = []; |
| | | list.forEach((element) { |
| | | suggestList.add(element); |
| | | }); |
| | | |
| | | _sugguestSearchController.setData!(suggestList); |
| | | if (suggestList.isNotEmpty) { |
| | | _sugguestSearchController.setShow!(true); |
| | | } else { |
| | | _sugguestSearchController.setShow!(false); |
| | | } |
| | | } else { |
| | | _sugguestSearchController.setData!([]); |
| | | _sugguestSearchController.setShow!(false); |
| | | } |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return Scaffold( |
| | | backgroundColor: Colors.white, |
| | | body: Stack(children: [ |
| | | Column( |
| | | children: [ |
| | | Container( |
| | | height: MediaQuery.of(context).viewPadding.top, |
| | | ), |
| | | Container( |
| | | height: 5, |
| | | ), |
| | | //搜索栏 |
| | | SearchBar( |
| | | hint: widget.title, |
| | | onChange: (content) { |
| | | if (content.isEmpty) { |
| | | _sugguestSearchController.setData!([]); |
| | | _sugguestSearchController.setShow!(false); |
| | | } else { |
| | | getSuggestSearch(content); |
| | | } |
| | | }, |
| | | onSubmit: (content) { |
| | | NavigatorUtil.navigateToNextPage( |
| | | context, |
| | | SearchResultPage( |
| | | title: content.isNotEmpty ? content : widget.title), |
| | | (data) { |
| | | fromBack(); |
| | | }); |
| | | }, |
| | | ), |
| | | //内容 |
| | | Expanded( |
| | | child: SingleChildScrollView( |
| | | child: Column(children: [ |
| | | recordList.isNotEmpty |
| | | ? Container( |
| | | padding: const EdgeInsets.fromLTRB(12, 20, 12, 10), |
| | | child: Row( |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | children: [ |
| | | const Text("历史搜索", |
| | | style: TextStyle( |
| | | color: Color(0xFFA6A6A6), fontSize: 15)), |
| | | Expanded(child: Container()), |
| | | InkWell( |
| | | onTap: () { |
| | | SearchRecordUtil.clearRecord(); |
| | | setState(() { |
| | | recordList = []; |
| | | }); |
| | | }, |
| | | child: const Icon( |
| | | Icons.delete_outline, |
| | | color: Color(0xFFA6A6A6), |
| | | )) |
| | | ])) |
| | | : Container(), |
| | | Container( |
| | | padding: const EdgeInsets.fromLTRB(12, 7, 12, 10), |
| | | alignment: Alignment.topLeft, |
| | | child: Wrap( |
| | | spacing: 13.5, |
| | | runSpacing: 15.5, |
| | | alignment: WrapAlignment.start, |
| | | children: |
| | | recordList.map((e) => getRecordItem(e)).toList())), |
| | | |
| | | ///热门搜索 |
| | | Container( |
| | | margin: const EdgeInsets.fromLTRB(12, 20, 12, 10), |
| | | alignment: Alignment.center, |
| | | padding: const EdgeInsets.fromLTRB(15, 15, 15, 25), |
| | | decoration: BoxDecoration( |
| | | borderRadius: BorderRadius.circular(15), |
| | | border: Border.all(color: Color(0xFFFFD7E4), width: 0.5), |
| | | gradient: const LinearGradient( |
| | | begin: Alignment.topCenter, |
| | | end: Alignment.bottomCenter, |
| | | colors: [ |
| | | Color(0xFFFFCEDE), |
| | | Color(0xFFFFFFFF), |
| | | Color(0xFFFFFFFF) |
| | | ])), |
| | | child: Column( |
| | | children: [ |
| | | const Text( |
| | | "热门搜索", |
| | | style: TextStyle(color: Color(0xFFFF558D)), |
| | | ), |
| | | Container( |
| | | height: 23, |
| | | ), |
| | | Wrap( |
| | | alignment: WrapAlignment.spaceBetween, |
| | | runSpacing: 11, |
| | | spacing: 10, |
| | | children: hotSearchList |
| | | .map((e) => getHotItem(e.index, e.kw)) |
| | | .toList()) |
| | | ], |
| | | ), |
| | | ), |
| | | |
| | | ///广告 |
| | | adType == null |
| | | ? Container() |
| | | : Container( |
| | | alignment: Alignment.center, |
| | | height: MediaQuery.of(context).size.width * 0.8, |
| | | child: _nativeAdView()) |
| | | ]))) |
| | | ], |
| | | ), |
| | | SugguestSearchView( |
| | | contentList: [], |
| | | sugguestSearchController: _sugguestSearchController, |
| | | onCancel: (value) { |
| | | _sugguestSearchController.setShow!(false); |
| | | }, |
| | | onItemClick: (value) { |
| | | _sugguestSearchController.setShow!(false); |
| | | NavigatorUtil.navigateToNextPage( |
| | | context, SearchResultPage(title: value), (data) { |
| | | fromBack(); |
| | | }); |
| | | }, |
| | | ) |
| | | ])); |
| | | } |
| | | |
| | | Widget _nativeAdView() { |
| | | return adType == AdType.csj |
| | | ? CSJEXpressAd( |
| | | CSJADConstant.PID_VIDEO_SEARCH, |
| | | MediaQuery.of(context).size.width - 20, |
| | | (MediaQuery.of(context).size.width - 20) * 0.8, |
| | | close: () { |
| | | setState(() { |
| | | adType = null; |
| | | }); |
| | | }, |
| | | loadFail: () { |
| | | setState(() { |
| | | adType = null; |
| | | }); |
| | | }, |
| | | ) |
| | | : GDTEXpressAd( |
| | | GDTADConstant.PID_VIDEO_SEARCH, |
| | | MediaQuery.of(context).size.width - 20, |
| | | (MediaQuery.of(context).size.width - 20) * 0.8, |
| | | close: () { |
| | | setState(() { |
| | | adType = null; |
| | | }); |
| | | }, |
| | | loadFail: () { |
| | | setState(() { |
| | | adType = null; |
| | | }); |
| | | }, |
| | | ); |
| | | } |
| | | |
| | | @override |
| | | bool get wantKeepAlive => true; |
| | | |
| | | Widget getRecordItem(String text) { |
| | | return InkWell( |
| | | onTap: () { |
| | | NavigatorUtil.navigateToNextPage( |
| | | context, SearchResultPage(title: text), (data) { |
| | | fromBack(); |
| | | }); |
| | | }, |
| | | child: Container( |
| | | padding: const EdgeInsets.fromLTRB(23, 8, 23, 8), |
| | | decoration: BoxDecoration( |
| | | borderRadius: BorderRadius.circular(8), color: Color(0xFFF7F7F7)), |
| | | child: Text( |
| | | text, |
| | | maxLines: 1, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: const TextStyle(color: Colors.black, fontSize: 12), |
| | | ), |
| | | )); |
| | | } |
| | | |
| | | Widget getHotItem(int index, String text) { |
| | | Color indexColor = const Color(0xFFD5D5D5); |
| | | switch (index) { |
| | | case 1: |
| | | indexColor = const Color(0xFFFF558D); |
| | | break; |
| | | case 2: |
| | | indexColor = const Color(0xFFFED73C); |
| | | break; |
| | | case 3: |
| | | indexColor = const Color(0xFF3CD7FE); |
| | | break; |
| | | } |
| | | return InkWell( |
| | | onTap: () { |
| | | NavigatorUtil.navigateToNextPage( |
| | | context, SearchResultPage(title: text), (data) { |
| | | fromBack(); |
| | | }); |
| | | }, |
| | | child: SizedBox( |
| | | width: (MediaQuery.of(context).size.width - 70) / 2, |
| | | child: Row( |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | children: [ |
| | | Container( |
| | | width: 17, |
| | | height: 17, |
| | | alignment: Alignment.center, |
| | | decoration: BoxDecoration( |
| | | borderRadius: BorderRadius.circular(6), color: indexColor), |
| | | child: Text( |
| | | "$index", |
| | | style: const TextStyle(color: Colors.white, fontSize: 11), |
| | | ), |
| | | ), |
| | | Container( |
| | | width: 12.5, |
| | | ), |
| | | Expanded( |
| | | child: Text( |
| | | text, |
| | | maxLines: 1, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: TextStyle(color: const Color(0xFF4A4A4A), fontSize: 12), |
| | | )) |
| | | ], |
| | | ), |
| | | )); |
| | | } |
| | | } |
| | | |
| | | class SearchKwModel { |
| | | int index; |
| | | String kw; |
| | | |
| | | SearchKwModel(this.index, this.kw); |
| | | } |
New file |
| | |
| | | import 'dart:convert'; |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import '../../api/video_api.dart'; |
| | | import '../../model/video/video_model.dart'; |
| | | import '../../ui/widget/dialog.dart'; |
| | | import '../../ui/widget/refresh_listview.dart'; |
| | | import '../../ui/widget/search_widget.dart'; |
| | | import '../../ui/widget/video_item.dart'; |
| | | import '../../utils/video/search_record_util.dart'; |
| | | import '../../ui/mine/settings.dart'; |
| | | import '../../ui/mine/login.dart'; |
| | | import '../../ui/mine/advice.dart'; |
| | | import '../../api/http.dart'; |
| | | import '../../model/user/user_info.dart'; |
| | | import '../../ui/common/browser.dart'; |
| | | import '../../utils/config_util.dart'; |
| | | import '../../utils/pageutils.dart'; |
| | | import '../../utils/string_util.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | import '../../utils/ui_utils.dart'; |
| | | import '../../utils/user_util.dart'; |
| | | |
| | | class SearchResultPage extends StatefulWidget { |
| | | SearchResultPage({Key? key, required this.title}) : super(key: key); |
| | | final String title; |
| | | |
| | | @override |
| | | _SearchResultPageState createState() => _SearchResultPageState(); |
| | | } |
| | | |
| | | class _SearchResultPageState extends State<SearchResultPage> |
| | | with TickerProviderStateMixin { |
| | | final SugguestSearchController _sugguestSearchController = |
| | | SugguestSearchController(); |
| | | final SearchController _searchController=SearchController(); |
| | | final MyRefreshController _refreshController = MyRefreshController(); |
| | | bool showClose = false; |
| | | int page = 1; |
| | | bool hasMore = true; |
| | | int? type; |
| | | List<VideoType> typeList = []; |
| | | String? kw; |
| | | List<VideoInfoModel> videoList = []; |
| | | |
| | | TabController? _tabController; |
| | | |
| | | VoidCallback? listener; |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | kw = widget.title; |
| | | |
| | | listener = () { |
| | | if(type!=null){ |
| | | if(type==typeList[_tabController!.index].id){ |
| | | return; |
| | | } |
| | | } |
| | | setState(() { |
| | | type = typeList[_tabController!.index].id; |
| | | }); |
| | | page = 1; |
| | | search(kw!, page); |
| | | }; |
| | | |
| | | _tabController = TabController(length: typeList.length, vsync: this); |
| | | } |
| | | |
| | | void initTab() { |
| | | if (_tabController != null) { |
| | | _tabController!.removeListener(listener!); |
| | | } |
| | | setState(() { |
| | | _tabController = TabController(length: typeList.length, vsync: this); |
| | | }); |
| | | _tabController!.addListener(listener!); |
| | | } |
| | | |
| | | void search(String content, int _page) async { |
| | | setState(() { |
| | | kw = content; |
| | | }); |
| | | |
| | | SearchRecordUtil.addRecord(content); |
| | | page = _page; |
| | | if (page == 1) { |
| | | setState(() { |
| | | videoList = []; |
| | | }); |
| | | } |
| | | Map<String, dynamic>? result = |
| | | await SearchApiUtil.search(context, content, _page, type); |
| | | //请求失败了 |
| | | if (result == null) { |
| | | if (page > 1) { |
| | | page = page - 1; |
| | | } |
| | | return; |
| | | } |
| | | |
| | | if (result["IsPost"] == "true") { |
| | | if (type == null && page == 1) { |
| | | List<dynamic> list = result["Data"]["typeList"]; |
| | | List<VideoType> tempVideoTypeTypeList = []; |
| | | list.forEach((element) { |
| | | tempVideoTypeTypeList.add(VideoType.fromJson(element)); |
| | | }); |
| | | setState(() { |
| | | typeList = tempVideoTypeTypeList; |
| | | }); |
| | | |
| | | initTab(); |
| | | } |
| | | |
| | | List<dynamic> list = result["Data"]["data"]; |
| | | List<VideoInfoModel> tempList = []; |
| | | for (var element in list) { |
| | | VideoInfoModel videoInfo = VideoInfoModel.fromJson(element); |
| | | //处理中间剧集 |
| | | List<VideoDetailInfo> detailList = videoInfo.videoDetailList!; |
| | | if (detailList.length > 5) { |
| | | detailList = [ |
| | | detailList[0], |
| | | detailList[1], |
| | | VideoDetailInfo(tag: "..."), |
| | | detailList[detailList.length - 2], |
| | | detailList[detailList.length - 1] |
| | | ]; |
| | | } |
| | | videoInfo.videoDetailList = detailList; |
| | | tempList.add(videoInfo); |
| | | } |
| | | hasMore = tempList.isNotEmpty; |
| | | setState(() { |
| | | if (_page == 1) { |
| | | videoList = tempList; |
| | | } else { |
| | | if (tempList.isNotEmpty) { |
| | | videoList.addAll(tempList); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | if (_page == 1) { |
| | | hasMore = true; |
| | | } |
| | | |
| | | _refreshController.refreshCompleted(); |
| | | |
| | | if (!hasMore) { |
| | | _refreshController.loadNoData(); |
| | | } else { |
| | | _refreshController.loadComplete(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | void getSuggestSearch(String key) async { |
| | | Map<String, dynamic>? result = |
| | | await SearchApiUtil.getSuggestSearch(context, key); |
| | | if (result == null) { |
| | | return; |
| | | } |
| | | if (result["IsPost"] == "true") { |
| | | List<dynamic> list = result["Data"]["data"]; |
| | | |
| | | List<String> suggestList = []; |
| | | list.forEach((element) { |
| | | suggestList.add(element); |
| | | }); |
| | | |
| | | _sugguestSearchController.setData!(suggestList); |
| | | if (suggestList.isNotEmpty) { |
| | | _sugguestSearchController.setShow!(true); |
| | | } else { |
| | | _sugguestSearchController.setShow!(false); |
| | | } |
| | | } else { |
| | | _sugguestSearchController.setData!([]); |
| | | _sugguestSearchController.setShow!(false); |
| | | } |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return Scaffold( |
| | | backgroundColor: Colors.white, |
| | | body: Stack(children: [ |
| | | Column( |
| | | children: [ |
| | | Container( |
| | | height: MediaQuery.of(context).viewPadding.top, |
| | | ), |
| | | Container( |
| | | height: 5, |
| | | ), |
| | | //搜索栏 |
| | | SearchBar( |
| | | text: kw, |
| | | onChange: (content) { |
| | | if (content.isEmpty) { |
| | | _sugguestSearchController.setData!([]); |
| | | _sugguestSearchController.setShow!(false); |
| | | } else { |
| | | getSuggestSearch(content); |
| | | } |
| | | }, |
| | | onSubmit: (content) { |
| | | type = null; |
| | | search(content, 1); |
| | | }, |
| | | searchController: _searchController, |
| | | ), |
| | | Container( |
| | | height: 5, |
| | | ), |
| | | |
| | | ///内容 |
| | | /// |
| | | Container( |
| | | alignment: Alignment.centerLeft, |
| | | child: TabBar( |
| | | padding: EdgeInsets.only(top: 5, bottom: 15, left: 2), |
| | | isScrollable: true, |
| | | indicatorSize: TabBarIndicatorSize.label, |
| | | labelColor: ColorConstant.theme, |
| | | labelStyle: const TextStyle(fontSize: 16), |
| | | labelPadding: EdgeInsets.only(left: 8, right: 8), |
| | | unselectedLabelColor: const Color(0xFF666666), |
| | | indicatorColor: ColorConstant.theme, |
| | | tabs: typeList.map((e) => Text(e.name!)).toList(), |
| | | controller: _tabController, |
| | | )), |
| | | |
| | | Expanded( |
| | | child: RefreshListView( |
| | | content: ListView.builder( |
| | | itemBuilder: (BuildContext context, int index) { |
| | | VideoInfoModel video = videoList[index]; |
| | | if (video.showType == 1) { |
| | | return VideoListUIUtil.getSearchVideoAlbum( |
| | | context, video); |
| | | } else { |
| | | return VideoListUIUtil.getSearchVideoCommon( |
| | | context, video); |
| | | } |
| | | }, |
| | | padding: const EdgeInsets.only(left: 0, right: 0, top: 0), |
| | | itemCount: videoList.length, |
| | | ), |
| | | refreshController: _refreshController, |
| | | refresh: () { |
| | | page = 1; |
| | | search(kw!, page); |
| | | }, |
| | | loadMore: () { |
| | | search(kw!, page + 1); |
| | | }, |
| | | ), |
| | | ) |
| | | ], |
| | | ), |
| | | SugguestSearchView( |
| | | contentList: [], |
| | | sugguestSearchController: _sugguestSearchController, |
| | | onCancel: (value) { |
| | | _sugguestSearchController.setShow!(false); |
| | | }, |
| | | onItemClick: (value) { |
| | | FocusScope.of(context).requestFocus(FocusNode()); |
| | | _searchController.setData!(value); |
| | | _sugguestSearchController.setShow!(false); |
| | | type = null; |
| | | search(value, 1); |
| | | }, |
| | | ) |
| | | ])); |
| | | } |
| | | |
| | | @override |
| | | bool get wantKeepAlive => true; |
| | | } |
New file |
| | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | |
| | | class SplashPage extends StatefulWidget { |
| | | SplashPage({Key? key, required this.title}) : super(key: key); |
| | | |
| | | final String title; |
| | | |
| | | @override |
| | | _SplashPageState createState() => _SplashPageState(); |
| | | } |
| | | |
| | | class _SplashPageState extends State<SplashPage> |
| | | with SingleTickerProviderStateMixin { |
| | | int index = 1; |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return MaterialApp(home: WillPopScope( |
| | | onWillPop: () async { |
| | | return false; |
| | | }, |
| | | child: Scaffold( |
| | | backgroundColor: Colors.red, |
| | | body: Column( |
| | | mainAxisAlignment: MainAxisAlignment.center, |
| | | children: [ |
| | | Text("$index"), |
| | | InkWell( |
| | | onTap: () { |
| | | setState(() { |
| | | index += 1; |
| | | }); |
| | | }, |
| | | child: const Text("点击事件"), |
| | | ) |
| | | ], |
| | | )))); |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import '../../api/video_api.dart'; |
| | | import '../../model/video/video_model.dart'; |
| | | import '../../model/video/watch_record_model.dart'; |
| | | import '../../ui/widget/button.dart'; |
| | | import '../../ui/widget/refresh_listview.dart'; |
| | | import '../../ui/widget/video_item.dart'; |
| | | import '../../utils/db_manager.dart'; |
| | | import '../../utils/video/video_util.dart'; |
| | | import '../../api/http.dart'; |
| | | import '../../ui/common/browser.dart'; |
| | | import '../../ui/widget/dialog.dart'; |
| | | import '../../ui/widget/nav.dart'; |
| | | import '../../utils/cache_util.dart'; |
| | | import '../../utils/config_util.dart'; |
| | | import '../../utils/event_bus_util.dart'; |
| | | import '../../utils/pageutils.dart'; |
| | | import '../../utils/push_util.dart'; |
| | | import '../../utils/setting_util.dart'; |
| | | import '../../utils/string_util.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | import '../../utils/ui_utils.dart'; |
| | | import '../../utils/user_util.dart'; |
| | | import 'package:package_info/package_info.dart'; |
| | | |
| | | class VideoAttentionPage extends StatefulWidget { |
| | | VideoAttentionPage({Key? key, required this.title}) : super(key: key); |
| | | |
| | | // This widget is the home page of your application. It is stateful, meaning |
| | | // that it has a State object (defined below) that contains fields that affect |
| | | // how it looks. |
| | | |
| | | // This class is the configuration for the state. It holds the values (in this |
| | | // case the title) provided by the parent (in this case the App widget) and |
| | | // used by the build method of the State. Fields in a Widget subclass are |
| | | // always marked "final". |
| | | |
| | | final String title; |
| | | |
| | | @override |
| | | _VideoAttentionPageState createState() => _VideoAttentionPageState(); |
| | | } |
| | | |
| | | class _VideoAttentionPageState extends State<VideoAttentionPage> |
| | | with SingleTickerProviderStateMixin { |
| | | final MyRefreshController _refreshController = MyRefreshController(); |
| | | List<VideoInfoModel> videoList = []; |
| | | |
| | | int page = 1; |
| | | int toalCount = 0; |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | } |
| | | |
| | | void loadVideoList(int _page) async { |
| | | page = _page; |
| | | Map<String, dynamic>? result = |
| | | await VideoApiUtil.getAttentionVideoList(context, page); |
| | | if (result == null) { |
| | | if (page == 1) { |
| | | _refreshController.apiError!(); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | if (result["IsPost"] != "true") { |
| | | return; |
| | | } |
| | | |
| | | List<dynamic> list = result["Data"]["data"]; |
| | | List<VideoInfoModel> temList = []; |
| | | list.forEach((element) { |
| | | temList.add(VideoInfoModel.fromJson(element["VideoInfo"])); |
| | | }); |
| | | |
| | | int count = int.parse(result["Data"]["count"]); |
| | | setState(() { |
| | | toalCount = count; |
| | | }); |
| | | if (page == 1) { |
| | | setState(() { |
| | | videoList = temList; |
| | | }); |
| | | } else { |
| | | setState(() { |
| | | videoList.addAll(temList); |
| | | }); |
| | | } |
| | | |
| | | _refreshController.refreshCompleted(); |
| | | if (count >= videoList.length) { |
| | | _refreshController.loadNoData(); |
| | | } else { |
| | | _refreshController.loadComplete(); |
| | | } |
| | | if (videoList.isEmpty) { |
| | | _refreshController.dataEmpty!(); |
| | | } |
| | | } |
| | | |
| | | void deleteVideo(index) async { |
| | | Map<String, dynamic>? result = |
| | | await VideoApiUtil.cancelAttentionVideo(context, videoList[index].id!); |
| | | if (result == null) { |
| | | return; |
| | | } |
| | | if (result["IsPost"] == "true") { |
| | | setState(() { |
| | | videoList.removeAt(index); |
| | | }); |
| | | ToastUtil.toast("删除成功", context); |
| | | if (videoList.isEmpty) { |
| | | loadVideoList(1); |
| | | } |
| | | } else { |
| | | ToastUtil.toast(result["Error"], context); |
| | | } |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return Scaffold( |
| | | backgroundColor: Colors.white, |
| | | body: Column( |
| | | children: [ |
| | | TopNavBar( |
| | | title: "我的追剧", |
| | | ), |
| | | Container( |
| | | height: DimenUtil.getOnePixel(context), |
| | | color: const Color(0xFFDBDBDB), |
| | | ), |
| | | Expanded( |
| | | child: RefreshListView( |
| | | content: ListView.builder( |
| | | padding: const EdgeInsets.only(top: 10), |
| | | itemBuilder: (BuildContext context, int index) { |
| | | return getItem(index); |
| | | }, |
| | | itemCount: videoList.length, |
| | | ), |
| | | refreshController: _refreshController, |
| | | refresh: () { |
| | | loadVideoList(1); |
| | | }, |
| | | loadMore: () { |
| | | loadVideoList(page + 1); |
| | | }, |
| | | )), |
| | | ], |
| | | )); |
| | | } |
| | | |
| | | Widget getItem(index) { |
| | | return Container( |
| | | alignment: Alignment.centerLeft, |
| | | margin: const EdgeInsets.fromLTRB(10, 8, 10, 8), |
| | | height: 80, |
| | | child: Stack( |
| | | children: [ |
| | | InkWell( |
| | | onTap: () { |
| | | jumpVideoDetail(context, videoList[index], false); |
| | | }, |
| | | child: Row( |
| | | children: [ |
| | | ClipRRect( |
| | | borderRadius: BorderRadius.circular(6), |
| | | child: VideoImage( |
| | | VideoUtil.getHPicture(videoList[index]), |
| | | fit: BoxFit.cover, |
| | | height: 80, |
| | | width: 80 * 1.68, |
| | | )), |
| | | Container( |
| | | width: 10, |
| | | ), |
| | | Expanded( |
| | | child: Column( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | Text( |
| | | videoList[index].name!, |
| | | maxLines: 2, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: const TextStyle( |
| | | color: Color(0xFF232323), fontSize: 15), |
| | | ), |
| | | Text( |
| | | videoList[index].tag!, |
| | | maxLines: 1, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: const TextStyle( |
| | | color: Color(0xFFB8AFB5), fontSize: 12), |
| | | ), |
| | | Expanded(child: Container()), |
| | | const Text( |
| | | "", |
| | | style: |
| | | TextStyle(color: Color(0xFFB8AFB5), fontSize: 12), |
| | | ) |
| | | ], |
| | | )) |
| | | ], |
| | | )), |
| | | Positioned( |
| | | width: 60, |
| | | right: 10, |
| | | top: 0, |
| | | bottom: 0, |
| | | child: Container( |
| | | alignment: Alignment.centerRight, |
| | | child: MyFillButton( |
| | | "取消追剧", |
| | | 5, |
| | | onClick: () { |
| | | deleteVideo(index); |
| | | }, |
| | | ))) |
| | | ], |
| | | )); |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import '../../api/video_api.dart'; |
| | | import '../../model/video/video_model.dart'; |
| | | import '../../ui/widget/button.dart'; |
| | | import '../../ui/widget/refresh_listview.dart'; |
| | | import '../../ui/widget/video_item.dart'; |
| | | import '../../utils/video/video_util.dart'; |
| | | |
| | | import '../../ui/widget/nav.dart'; |
| | | import '../../utils/ui_utils.dart'; |
| | | |
| | | class VideoCollectedPage extends StatefulWidget { |
| | | VideoCollectedPage({Key? key, required this.title}) : super(key: key); |
| | | |
| | | // This widget is the home page of your application. It is stateful, meaning |
| | | // that it has a State object (defined below) that contains fields that affect |
| | | // how it looks. |
| | | |
| | | // This class is the configuration for the state. It holds the values (in this |
| | | // case the title) provided by the parent (in this case the App widget) and |
| | | // used by the build method of the State. Fields in a Widget subclass are |
| | | // always marked "final". |
| | | |
| | | final String title; |
| | | |
| | | @override |
| | | _VideoCollectedPageState createState() => _VideoCollectedPageState(); |
| | | } |
| | | |
| | | class _VideoCollectedPageState extends State<VideoCollectedPage> |
| | | with SingleTickerProviderStateMixin { |
| | | bool editMode = false; |
| | | Set<String> selectedSet = {}; |
| | | |
| | | final MyRefreshController _refreshController = MyRefreshController(initialRefresh: false); |
| | | List<VideoInfoModel> videoList = []; |
| | | |
| | | int page = 1; |
| | | int toalCount = 0; |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | loadVideoList(1); |
| | | } |
| | | |
| | | |
| | | void loadVideoList(int _page) async { |
| | | page = _page; |
| | | Map<String, dynamic>? result = |
| | | await VideoApiUtil.getCollelctedVideos(context, page); |
| | | if (result == null) { |
| | | if (page == 1) { |
| | | _refreshController.apiError!(); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | if (result["IsPost"] != "true") { |
| | | return; |
| | | } |
| | | |
| | | List<dynamic> list = result["Data"]["data"]; |
| | | List<VideoInfoModel> temList = []; |
| | | list.forEach((element) { |
| | | temList.add(VideoInfoModel.fromJson(element)); |
| | | }); |
| | | |
| | | int count = int.parse(result["Data"]["count"]); |
| | | setState(() { |
| | | toalCount = count; |
| | | }); |
| | | if (page == 1) { |
| | | setState(() { |
| | | videoList = temList; |
| | | }); |
| | | } else { |
| | | setState(() { |
| | | videoList.addAll(temList); |
| | | }); |
| | | } |
| | | |
| | | _refreshController.refreshCompleted(); |
| | | if (count >= videoList.length) { |
| | | _refreshController.loadNoData(); |
| | | } else { |
| | | _refreshController.loadComplete(); |
| | | } |
| | | if (videoList.isEmpty) { |
| | | _refreshController.dataEmpty!(); |
| | | } else { |
| | | //正常的状态 |
| | | _refreshController.dataNormal!(); |
| | | } |
| | | } |
| | | |
| | | void deleteVideo() async { |
| | | if (selectedSet.isEmpty) { |
| | | return; |
| | | } |
| | | String videoIds = selectedSet.join(","); |
| | | |
| | | Map<String, dynamic>? result = |
| | | await VideoApiUtil.collelctVideo(context, videoIds, false); |
| | | if (result == null) { |
| | | return; |
| | | } |
| | | if (result["IsPost"] == "true") { |
| | | ToastUtil.toast("删除成功", context); |
| | | setState(() { |
| | | selectedSet.clear(); |
| | | }); |
| | | loadVideoList(1); |
| | | } else { |
| | | ToastUtil.toast(result["Error"], context); |
| | | } |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return Scaffold( |
| | | backgroundColor: Colors.white, |
| | | body: Column( |
| | | children: [ |
| | | TopNavBar( |
| | | title: "我的收藏", |
| | | rightIcon: videoList.isNotEmpty |
| | | ? const Icon( |
| | | Icons.format_list_bulleted, |
| | | size: 25, |
| | | color: Color(0xFF202020), |
| | | ) |
| | | : null, |
| | | rightClick: () { |
| | | setState(() { |
| | | editMode = !editMode; |
| | | if (!editMode) { |
| | | selectedSet.clear(); |
| | | } |
| | | _refreshController.setEditMode!(editMode); |
| | | }); |
| | | }, |
| | | ), |
| | | Container( |
| | | height: DimenUtil.getOnePixel(context), |
| | | color: const Color(0xFFDBDBDB), |
| | | ), |
| | | Expanded( |
| | | child: RefreshListView( |
| | | content: ListView.builder( |
| | | padding: const EdgeInsets.only(top: 10), |
| | | itemBuilder: (BuildContext context, int index) { |
| | | return getItem(index); |
| | | }, |
| | | itemCount: videoList.length, |
| | | ), |
| | | refreshController: _refreshController, |
| | | refresh: () { |
| | | loadVideoList(1); |
| | | }, |
| | | loadMore: () { |
| | | loadVideoList(page + 1); |
| | | }, |
| | | )), |
| | | editMode && videoList.isNotEmpty |
| | | ? Container( |
| | | padding: const EdgeInsets.fromLTRB(28, 10, 28, 10), |
| | | decoration: const BoxDecoration( |
| | | color: Colors.white, |
| | | border: Border( |
| | | top: BorderSide( |
| | | color: Color(0xFFDBDBDB), width: 0.5))), |
| | | child: Row( |
| | | children: [ |
| | | Expanded( |
| | | child: MyOutlineButton( |
| | | videoList.length == selectedSet.length ? "反选" : "全选", |
| | | 8, |
| | | height: 34, |
| | | fontSize: 16, |
| | | color: const Color(0xFFdbdbdb), |
| | | textColor: const Color(0xFF202020), |
| | | onClick: () { |
| | | if (videoList.length == selectedSet.length) { |
| | | setState(() { |
| | | selectedSet.clear(); |
| | | }); |
| | | } else { |
| | | Set<String> list = |
| | | videoList.map((e) => e.id.toString()).toSet(); |
| | | setState(() { |
| | | selectedSet = list; |
| | | }); |
| | | } |
| | | }, |
| | | )), |
| | | Container( |
| | | width: 20, |
| | | ), |
| | | Expanded( |
| | | child: MyFillButton( |
| | | selectedSet.isNotEmpty |
| | | ? ("删除(${selectedSet.length})") |
| | | : "删除", |
| | | 8, |
| | | height: 34, |
| | | fontSize: 16, |
| | | onClick: () { |
| | | deleteVideo(); |
| | | }, |
| | | )), |
| | | ], |
| | | ), |
| | | ) |
| | | : Container() |
| | | ], |
| | | )); |
| | | } |
| | | |
| | | Widget getItem(index) { |
| | | return Container( |
| | | alignment: Alignment.centerLeft, |
| | | margin: const EdgeInsets.fromLTRB(10, 8, 10, 8), |
| | | height: 80, |
| | | child: InkWell( |
| | | child: Row( |
| | | children: [ |
| | | Stack( |
| | | alignment: Alignment.center, |
| | | children: [ |
| | | ClipRRect( |
| | | borderRadius: BorderRadius.circular(6), |
| | | child: VideoImage( |
| | | VideoUtil.getHPicture(videoList[index]), |
| | | fit: BoxFit.cover, |
| | | height: 80, |
| | | width: 80 * 1.68, |
| | | )), |
| | | (editMode |
| | | ? Image.asset( |
| | | selectedSet.contains("${videoList[index].id}") |
| | | ? "assets/imgs/video/icon_check_true.png" |
| | | : "assets/imgs/video/icon_check_false.png", |
| | | width: 35, |
| | | ) |
| | | : Container()) |
| | | ], |
| | | ), |
| | | Container( |
| | | width: 10, |
| | | ), |
| | | Expanded( |
| | | child: Column( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | Text( |
| | | videoList[index].name!, |
| | | maxLines: 2, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: |
| | | const TextStyle(color: Color(0xFF232323), fontSize: 15), |
| | | ), |
| | | Text( |
| | | videoList[index].tag!, |
| | | maxLines: 1, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: |
| | | TextStyle(color: const Color(0xFFB8AFB5), fontSize: 12), |
| | | ), |
| | | Expanded(child: Container()), |
| | | const Text( |
| | | "", |
| | | style: TextStyle(color: Color(0xFFB8AFB5), fontSize: 12), |
| | | ) |
| | | ], |
| | | )) |
| | | ], |
| | | ), |
| | | onTap: () { |
| | | if (!editMode) { |
| | | return; |
| | | } |
| | | setState(() { |
| | | if (selectedSet.contains("${videoList[index].id}")) { |
| | | selectedSet.remove("${videoList[index].id}"); |
| | | } else { |
| | | selectedSet.add("${videoList[index].id}"); |
| | | } |
| | | }); |
| | | }, |
| | | )); |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:io'; |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import 'package:flutter_spinkit/flutter_spinkit.dart'; |
| | | import '../../api/video_api.dart'; |
| | | import '../../model/video/video_attention_model.dart'; |
| | | import '../../model/video/video_model.dart'; |
| | | import '../../model/video/video_play_url_model.dart'; |
| | | import '../../ui/mine/email_login.dart'; |
| | | import '../../ui/video/video_player_browser.dart'; |
| | | import '../../ui/widget/button.dart'; |
| | | import '../../ui/widget/refresh_listview.dart'; |
| | | import '../../ui/widget/video_item.dart'; |
| | | import '../../utils/ad_util.dart'; |
| | | import '../../utils/db_manager.dart'; |
| | | import '../../utils/pageutils.dart'; |
| | | import '../../utils/share_preference.dart'; |
| | | import '../../utils/string_util.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | import '../../utils/ui_utils.dart'; |
| | | import '../../utils/user_util.dart'; |
| | | import 'package:share_plus/share_plus.dart'; |
| | | import 'package:webview_flutter/webview_flutter.dart'; |
| | | |
| | | import '../../ui/widget/nav.dart'; |
| | | |
| | | class VideoDetailPage extends StatefulWidget { |
| | | VideoDetailPage( |
| | | {Key? key, this.videoId, this.videoInfo, required this.position}) |
| | | : super(key: key); |
| | | final String? videoId; |
| | | final VideoInfoModel? videoInfo; |
| | | final int position; |
| | | |
| | | @override |
| | | _VideoDetailPageState createState() => _VideoDetailPageState(); |
| | | } |
| | | |
| | | class _VideoDetailPageState extends State<VideoDetailPage> |
| | | with SingleTickerProviderStateMixin { |
| | | TabController? _tabController; |
| | | List tabs = [" 剧集 ", " 简介 "]; |
| | | int _playPosition = 0; |
| | | VideoDetailStatus _status = VideoDetailStatus.going; |
| | | |
| | | VideoResource? _selectedVideoResource; |
| | | |
| | | VideoInfoModel? _videoInfoModel; |
| | | |
| | | VideoInfoModel? _simpleVideo; |
| | | |
| | | List<VideoInfoModel> relativeVideoList = []; |
| | | |
| | | VideoAttentionModel? videoAttention; |
| | | |
| | | //是否已经收藏 |
| | | bool collected = false; |
| | | |
| | | //是否展开剧集 |
| | | bool episodeExpand = false; |
| | | |
| | | bool adShown = false; |
| | | |
| | | final MyRefreshController _refreshController = |
| | | MyRefreshController(initialRefresh: false); |
| | | final ScrollController _scrollController = ScrollController(); |
| | | |
| | | int episodePage = 1; |
| | | bool episodeHasMore = true; |
| | | |
| | | String applink = ""; |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | if (Platform.isAndroid) { |
| | | WebView.platform = SurfaceAndroidWebView(); |
| | | } |
| | | _playPosition = widget.position; |
| | | _simpleVideo = widget.videoInfo; |
| | | _tabController = TabController(length: tabs.length, vsync: this); |
| | | if (Platform.isIOS) { |
| | | MySharedPreferences.getInstance().getString("appLink").then((value) { |
| | | setState(() { |
| | | applink = value!; |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | getVideoDetail(_simpleVideo!.id); |
| | | } |
| | | |
| | | void getVideoDetail(videoId) async { |
| | | setState(() { |
| | | _status = VideoDetailStatus.going; |
| | | //初始化状态 |
| | | episodePage = 1; |
| | | episodeHasMore = true; |
| | | episodeExpand = false; |
| | | }); |
| | | |
| | | Map<String, dynamic>? result = await VideoApiUtil.getVideoDetail( |
| | | context, |
| | | videoId, |
| | | _selectedVideoResource == null ? null : _selectedVideoResource!.id); |
| | | |
| | | if (result == null) { |
| | | setState(() { |
| | | _status = VideoDetailStatus.fail; |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | if (result["IsPost"] == "true") { |
| | | VideoInfoModel temp = VideoInfoModel.fromJson(result["Data"]["Video"]); |
| | | if (temp.videoDetailList!.length <= _playPosition) { |
| | | setState(() { |
| | | _playPosition = 0; |
| | | }); |
| | | } |
| | | |
| | | setState(() { |
| | | _videoInfoModel = temp; |
| | | _status = VideoDetailStatus.success; |
| | | }); |
| | | _videoInfoModel!.resourceList!.forEach((element) { |
| | | if (element.checked!) { |
| | | setState(() { |
| | | _selectedVideoResource = element; |
| | | }); |
| | | } |
| | | }); |
| | | if (_selectedVideoResource == null) { |
| | | setState(() { |
| | | _selectedVideoResource = _videoInfoModel!.resourceList![0]; |
| | | }); |
| | | } |
| | | //解析追剧数据 |
| | | if (result["Data"]["Attention"] != null) { |
| | | VideoAttentionModel temp = |
| | | VideoAttentionModel.fromJson(result["Data"]["Attention"]); |
| | | setState(() { |
| | | videoAttention = temp; |
| | | }); |
| | | } else { |
| | | setState(() { |
| | | videoAttention = null; |
| | | }); |
| | | } |
| | | |
| | | if (result["Data"]["AdInfo"] != null) { |
| | | if (result["Data"]["AdInfo"]["FullVideo"] && !adShown) { |
| | | AdUtil.loadFullScreenAd( |
| | | AdType.csj, CSJADConstant.PID_VIDEO_DETAIL_FULLSCREEN); |
| | | adShown = true; |
| | | } |
| | | } |
| | | |
| | | getRelativeVideos(videoId); |
| | | _isCollectedVideo(); |
| | | } else { |
| | | ToastUtil.toast(result["Error"], context); |
| | | popPage(context); |
| | | } |
| | | } |
| | | |
| | | void getEpisodeList(videoId, _page) async { |
| | | episodePage = _page; |
| | | Map<String, dynamic>? result = await VideoApiUtil.getVideoEpisodeList( |
| | | context, |
| | | videoId, |
| | | _selectedVideoResource == null ? null : _selectedVideoResource!.id, |
| | | _page); |
| | | if (result == null || result["IsPost"] != "true") { |
| | | //回退页码 |
| | | if (episodePage > 1) { |
| | | episodePage--; |
| | | } |
| | | return; |
| | | } |
| | | List<dynamic> list = result["Data"]["list"]; |
| | | episodeHasMore = result["Data"]["hasMore"]; |
| | | List<VideoDetailInfo> tempList = []; |
| | | list.forEach((element) { |
| | | tempList.add(VideoDetailInfo.fromJson(element)); |
| | | }); |
| | | setState(() { |
| | | _videoInfoModel!.videoDetailList!.addAll(tempList); |
| | | }); |
| | | |
| | | if (!episodeHasMore) { |
| | | _refreshController.loadNoData(); |
| | | } else { |
| | | _refreshController.loadComplete(); |
| | | } |
| | | } |
| | | |
| | | void getRelativeVideos(videoId) async { |
| | | Map<String, dynamic>? result = |
| | | await VideoApiUtil.getRelativeVideos(context, videoId); |
| | | if (result == null) { |
| | | return; |
| | | } |
| | | |
| | | if (result["IsPost"] == "true") { |
| | | List<dynamic> list = result["Data"]["data"]; |
| | | |
| | | List<VideoInfoModel> tempList = []; |
| | | |
| | | list.forEach((element) { |
| | | tempList.add(VideoInfoModel.fromJson(element)); |
| | | }); |
| | | setState(() { |
| | | relativeVideoList = tempList; |
| | | }); |
| | | } |
| | | } |
| | | |
| | | void getPlayUrl(position) async { |
| | | setState(() { |
| | | _playPosition = position; |
| | | }); |
| | | Map<String, dynamic>? result = await VideoApiUtil.getPlayUrl( |
| | | context, |
| | | _videoInfoModel!.id!, |
| | | _videoInfoModel!.videoDetailList![position].id!, |
| | | _selectedVideoResource!.id!); |
| | | |
| | | if (result == null) { |
| | | return; |
| | | } |
| | | if (result["IsPost"] == "true") { |
| | | //添加观看记录 |
| | | _videoInfoModel!.tag = _simpleVideo!.tag; |
| | | DBManager.addWatchRecord(_videoInfoModel!, |
| | | _videoInfoModel!.videoDetailList![_playPosition], _playPosition); |
| | | |
| | | VideoPlayUrlModel temp = VideoPlayUrlModel.fromJson(result["Data"]); |
| | | if (temp.playType == 1) { |
| | | NavigatorUtil.navigateToNextPage( |
| | | context, |
| | | VideoPlayerWebPage( |
| | | title: _videoInfoModel!.videoDetailList![_playPosition].name!, |
| | | url: temp.url!), |
| | | (data) {}); |
| | | } |
| | | } |
| | | } |
| | | |
| | | String getPosterImg() { |
| | | if (_videoInfoModel == null) { |
| | | if (!StringUtil.isNullOrEmpty(widget.videoInfo!.hpicture)) { |
| | | return widget.videoInfo!.hpicture!; |
| | | } else { |
| | | return widget.videoInfo!.picture!; |
| | | } |
| | | } else { |
| | | if (!StringUtil.isNullOrEmpty(_videoInfoModel!.playPicture)) { |
| | | return _videoInfoModel!.playPicture!; |
| | | } |
| | | if (!StringUtil.isNullOrEmpty(_videoInfoModel!.hpicture)) { |
| | | return _videoInfoModel!.hpicture!; |
| | | } |
| | | return _videoInfoModel!.picture!; |
| | | } |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return Scaffold( |
| | | backgroundColor: Colors.white, |
| | | body: Column( |
| | | children: [ |
| | | Container( |
| | | height: MediaQuery.of(context).viewPadding.top, |
| | | ), |
| | | //封面 |
| | | AspectRatio( |
| | | aspectRatio: 1.7778, |
| | | child: Stack( |
| | | alignment: Alignment.center, |
| | | children: [ |
| | | Positioned( |
| | | left: 0, |
| | | right: 0, |
| | | top: 0, |
| | | bottom: 0, |
| | | child: VideoImage(getPosterImg()), |
| | | ), |
| | | // Positioned( |
| | | // left: 0, |
| | | // right: 0, |
| | | // top: 0, |
| | | // bottom: 0, |
| | | // child: _getPlayer(), |
| | | // ), |
| | | Positioned( |
| | | top: 10, |
| | | left: 10, |
| | | child: InkWell( |
| | | onTap: () { |
| | | popPage(context); |
| | | }, |
| | | child: Image.asset( |
| | | "assets/imgs/video/ic_play_back.png", |
| | | width: 29, |
| | | height: 29, |
| | | ))), |
| | | Positioned( |
| | | width: 60, |
| | | height: 60, |
| | | child: InkWell( |
| | | onTap: () { |
| | | getPlayUrl(_playPosition); |
| | | }, |
| | | child: |
| | | Image.asset("assets/imgs/video/ic_play.png"))) |
| | | ], |
| | | )), |
| | | |
| | | _status == VideoDetailStatus.success |
| | | ? Expanded( |
| | | child: Stack(children: [ |
| | | Column( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | Stack(alignment: Alignment.bottomLeft, children: [ |
| | | Container( |
| | | color: const Color(0xFFEEEEEE), |
| | | height: 2, |
| | | ), |
| | | Container( |
| | | margin: const EdgeInsets.only(left: 34), |
| | | alignment: Alignment.topLeft, |
| | | child: TabBar( |
| | | isScrollable: true, |
| | | labelPadding: |
| | | const EdgeInsets.fromLTRB(10, 10, 10, 10), |
| | | indicatorColor: const Color(0xFFFF558D), |
| | | labelColor: const Color(0xFFFF558D), |
| | | labelStyle: const TextStyle(fontSize: 15), |
| | | unselectedLabelColor: |
| | | const Color(0xFFF999999), |
| | | indicatorWeight: 4, |
| | | indicatorSize: TabBarIndicatorSize.label, |
| | | controller: _tabController, |
| | | tabs: tabs.map((e) => Text(e)).toList(), |
| | | )) |
| | | ]), |
| | | Expanded( |
| | | child: TabBarView( |
| | | controller: _tabController, |
| | | children: [ |
| | | SingleChildScrollView( |
| | | child: _getVideoEpisodeView(), |
| | | ), |
| | | _getVideoDescView() |
| | | ])) |
| | | ]), |
| | | //选集浮层 |
| | | _videoInfoModel != null && episodeExpand |
| | | ? Container( |
| | | color: Colors.white, |
| | | child: Stack(children: [ |
| | | RefreshListView( |
| | | enablePullDown: false, |
| | | refreshController: _refreshController, |
| | | loadMore: () { |
| | | getEpisodeList( |
| | | _simpleVideo!.id, episodePage + 1); |
| | | }, |
| | | content: GridView.builder( |
| | | padding: const EdgeInsets.only( |
| | | top: 45, left: 10, right: 10, bottom: 10), |
| | | itemCount: |
| | | _videoInfoModel!.videoDetailList!.length, |
| | | itemBuilder: |
| | | (BuildContext context, int index) { |
| | | return _getEpisodeItem( |
| | | _videoInfoModel! |
| | | .videoDetailList![index], |
| | | index); |
| | | }, |
| | | gridDelegate: |
| | | SliverGridDelegateWithFixedCrossAxisCount( |
| | | //横轴元素个数 |
| | | crossAxisCount: |
| | | _videoInfoModel!.showType == 2 |
| | | ? 5 |
| | | : 2, |
| | | //纵轴间距 |
| | | mainAxisSpacing: 10.0, |
| | | //横轴间距 |
| | | crossAxisSpacing: 10.0, |
| | | //子组件宽高长度比例 |
| | | childAspectRatio: |
| | | _videoInfoModel!.showType == 2 |
| | | ? 1.6 |
| | | : 2), |
| | | ), |
| | | ), |
| | | Positioned( |
| | | right: 0, |
| | | top: 0, |
| | | left: 0, |
| | | child: Container( |
| | | height: 40, |
| | | color: Colors.white, |
| | | child: Stack( |
| | | alignment: Alignment.center, |
| | | children: [ |
| | | Text( |
| | | "选集", |
| | | style: TextStyle(fontSize: 16), |
| | | ), |
| | | Positioned( |
| | | right: 0, |
| | | child: InkWell( |
| | | onTap: () { |
| | | setState(() { |
| | | episodeExpand = false; |
| | | }); |
| | | }, |
| | | child: const Icon( |
| | | Icons.close, |
| | | color: Colors.black54, |
| | | ))) |
| | | ]))) |
| | | ]), |
| | | ) |
| | | : Container() |
| | | ])) |
| | | : Expanded(child: getStatusView()) |
| | | ], |
| | | )); |
| | | } |
| | | |
| | | Widget getStatusView() { |
| | | if (_status == VideoDetailStatus.going) { |
| | | return const SpinKitCircle( |
| | | color: ColorConstant.theme, |
| | | size: 60.0, |
| | | ); |
| | | } else if (_status == VideoDetailStatus.fail) { |
| | | return InkWell( |
| | | onTap: () { |
| | | getVideoDetail(_simpleVideo!.id); |
| | | }, |
| | | child: Container( |
| | | alignment: Alignment.center, |
| | | child: const Text( |
| | | "加载失败,点击重试", |
| | | textAlign: TextAlign.center, |
| | | style: TextStyle(color: Colors.grey, fontSize: 18), |
| | | ))); |
| | | } |
| | | return Container(); |
| | | } |
| | | |
| | | void _collectVideo(bool collect) async { |
| | | if (!await UserUtil.isLogin()) { |
| | | NavigatorUtil.navigateToNextPage( |
| | | context, EmailLoginPage(title: ""), (data) {}); |
| | | return; |
| | | } |
| | | |
| | | Map<String, dynamic>? result = |
| | | await VideoApiUtil.collelctVideo(context, _simpleVideo!.id!, collect); |
| | | if (result == null) { |
| | | return; |
| | | } |
| | | if (result["IsPost"] == "true") { |
| | | ToastUtil.toast(collect ? "收藏成功" : "取消收藏成功", context); |
| | | } |
| | | setState(() { |
| | | collected = collect; |
| | | }); |
| | | } |
| | | |
| | | void _isCollectedVideo() async { |
| | | if (!await UserUtil.isLogin()) { |
| | | return; |
| | | } |
| | | |
| | | Map<String, dynamic>? result = |
| | | await VideoApiUtil.isCollelctedVideo(context, _simpleVideo!.id!); |
| | | if (result == null) { |
| | | return; |
| | | } |
| | | if (result["IsPost"] == "true") { |
| | | setState(() { |
| | | collected = true; |
| | | }); |
| | | } else { |
| | | collected = false; |
| | | } |
| | | } |
| | | |
| | | void attentionClick() async { |
| | | bool login = await UserUtil.isLogin(); |
| | | if (!login) { |
| | | NavigatorUtil.navigateToNextPage( |
| | | context, EmailLoginPage(title: ""), (data) {}); |
| | | return; |
| | | } |
| | | |
| | | if (videoAttention!.attention!) { |
| | | //取消关注 |
| | | Map<String, dynamic>? result = |
| | | await VideoApiUtil.cancelAttentionVideo(context, _simpleVideo!.id!); |
| | | if (result == null) { |
| | | return; |
| | | } |
| | | if (result["IsPost"] == "true") { |
| | | ToastUtil.toast("取消追剧成功", context); |
| | | setState(() { |
| | | videoAttention!.attention = false; |
| | | }); |
| | | } else { |
| | | ToastUtil.toast(result["Error"], context); |
| | | } |
| | | } else { |
| | | //关注 |
| | | Map<String, dynamic>? result = |
| | | await VideoApiUtil.addAttentionVideo(context, _simpleVideo!.id!); |
| | | if (result == null) { |
| | | return; |
| | | } |
| | | if (result["IsPost"] == "true") { |
| | | ToastUtil.toast("追剧成功", context); |
| | | setState(() { |
| | | videoAttention!.attention = true; |
| | | }); |
| | | } else { |
| | | ToastUtil.toast(result["Error"], context); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | Widget _getPlayer() { |
| | | return WebView( |
| | | //http://192.168.3.122:8848/test/JsTest.html |
| | | initialUrl: "https://jx.parwix.com:4433/player/?url=https://v.youku.com/v_show/id_XNTg0ODEyODc0NA==.html", |
| | | navigationDelegate: (NavigationRequest request) { |
| | | if (!request.url.startsWith("http")) { |
| | | return NavigationDecision.prevent; |
| | | } |
| | | return NavigationDecision.navigate; |
| | | }, |
| | | ); |
| | | } |
| | | |
| | | //剧集 |
| | | Widget _getVideoEpisodeView() { |
| | | return Column( |
| | | children: [ |
| | | Container( |
| | | padding: const EdgeInsets.fromLTRB(12, 20, 12, 10), |
| | | child: |
| | | Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ |
| | | Row( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | Expanded( |
| | | child: Text( |
| | | _videoInfoModel!.name!, |
| | | style: const TextStyle(color: Colors.black, fontSize: 18), |
| | | )), |
| | | Container( |
| | | width: 10, |
| | | ), |
| | | _selectedVideoResource != null |
| | | ? InkWell( |
| | | onTap: () { |
| | | if (_videoInfoModel == null) { |
| | | return; |
| | | } |
| | | |
| | | DialogUtil.showDialogBottom( |
| | | context, |
| | | //资源选择弹框 |
| | | _getResourceListDialog(), |
| | | borderRadius: const BorderRadius.only( |
| | | topRight: Radius.circular(10), |
| | | topLeft: Radius.circular(10))); |
| | | }, |
| | | child: Container( |
| | | padding: |
| | | const EdgeInsets.fromLTRB(9.5, 10, 8.5, 10), |
| | | decoration: BoxDecoration( |
| | | borderRadius: BorderRadius.circular(10), |
| | | border: Border.all( |
| | | color: Color(0xFFBBBBBB), |
| | | width: DimenUtil.getOnePixel(context))), |
| | | child: Row(children: [ |
| | | Text.rich( |
| | | TextSpan( |
| | | text: "来源:", |
| | | style: const TextStyle( |
| | | color: Color(0xFF999999), fontSize: 9), |
| | | children: [ |
| | | TextSpan( |
| | | text: _selectedVideoResource!.name, |
| | | style: const TextStyle( |
| | | color: Color(0xFF666666), |
| | | fontSize: 11)) |
| | | ]), |
| | | ), |
| | | const Icon( |
| | | Icons.keyboard_arrow_down, |
| | | color: Color(0xFFE0E0E0), |
| | | ), |
| | | ]), |
| | | )) |
| | | : Container() |
| | | ], |
| | | ), |
| | | _videoInfoModel!.score != null && |
| | | _videoInfoModel!.score!.isNotEmpty |
| | | ? Text.rich( |
| | | TextSpan( |
| | | text: "评分:", |
| | | style: const TextStyle( |
| | | color: Color(0xFF999999), fontSize: 14), |
| | | children: [ |
| | | TextSpan( |
| | | text: _videoInfoModel!.score!, |
| | | style: const TextStyle( |
| | | color: Color(0xFFFB9F00), |
| | | fontSize: 14, |
| | | fontWeight: FontWeight.bold), |
| | | ) |
| | | ]), |
| | | ) |
| | | : Container(), |
| | | Container( |
| | | height: 30, |
| | | ), |
| | | Row( |
| | | mainAxisAlignment: MainAxisAlignment.spaceAround, |
| | | children: [ |
| | | InkWell( |
| | | onTap: () { |
| | | _collectVideo(!collected); |
| | | }, |
| | | child: Column(children: [ |
| | | Image.asset( |
| | | collected |
| | | ? "assets/imgs/video/icon_collected.png" |
| | | : "assets/imgs/video/icon_uncollected.png", |
| | | width: 22, |
| | | ), |
| | | Container( |
| | | height: 5, |
| | | ), |
| | | const Text( |
| | | "收藏", |
| | | style: |
| | | TextStyle(color: Color(0xFF9D9D9D), fontSize: 10), |
| | | ) |
| | | ])), |
| | | InkWell( |
| | | onTap: () { |
| | | ToastUtil.toast("暂不支持下载", context); |
| | | }, |
| | | child: Column(children: [ |
| | | Image.asset( |
| | | "assets/imgs/video/icon_download.png", |
| | | width: 21, |
| | | ), |
| | | Container( |
| | | height: 5, |
| | | ), |
| | | const Text( |
| | | "下载", |
| | | style: |
| | | TextStyle(color: Color(0xFF9D9D9D), fontSize: 10), |
| | | ) |
| | | ])), |
| | | InkWell( |
| | | onTap: () { |
| | | Share.share( |
| | | "我正在看《${_simpleVideo!.name!}》,APP链接地址为:$applink"); |
| | | }, |
| | | child: Column(children: [ |
| | | Image.asset( |
| | | "assets/imgs/video/icon_share.png", |
| | | width: 21, |
| | | ), |
| | | Container( |
| | | height: 5, |
| | | ), |
| | | const Text( |
| | | "分享", |
| | | style: |
| | | TextStyle(color: Color(0xFF9D9D9D), fontSize: 10), |
| | | ) |
| | | ])), |
| | | ], |
| | | ), |
| | | ])), |
| | | //追剧 |
| | | videoAttention != null |
| | | ? Container( |
| | | height: 60, |
| | | margin: const EdgeInsets.fromLTRB(10, 10, 10, 0), |
| | | padding: const EdgeInsets.fromLTRB(5, 10, 5, 10), |
| | | decoration: BoxDecoration( |
| | | border: Border.all( |
| | | color: const Color(0xFF999999), |
| | | width: DimenUtil.getOnePixel(context)), |
| | | borderRadius: BorderRadius.circular(8)), |
| | | child: Row( |
| | | children: [ |
| | | ClipRRect( |
| | | borderRadius: BorderRadius.circular(20), |
| | | child: CommonImage( |
| | | videoAttention!.picture!, |
| | | width: 40, |
| | | height: 40, |
| | | )), |
| | | const SizedBox( |
| | | width: 5, |
| | | ), |
| | | Expanded( |
| | | child: Column( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | Text( |
| | | videoAttention!.name!, |
| | | maxLines: 1, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: const TextStyle(fontSize: 14), |
| | | ), |
| | | Expanded(child: Container()), |
| | | Text( |
| | | videoAttention!.updateInfo!, |
| | | style: const TextStyle( |
| | | fontSize: 10, color: Color(0xFF9D9D9D)), |
| | | ) |
| | | ], |
| | | )), |
| | | const SizedBox( |
| | | width: 5, |
| | | ), |
| | | MyFillButton( |
| | | videoAttention!.attention! ? "取消追剧" : "追剧", |
| | | 6, |
| | | width: 60, |
| | | color: ColorConstant.theme, |
| | | onClick: () { |
| | | attentionClick(); |
| | | }, |
| | | ) |
| | | ], |
| | | ), |
| | | ) |
| | | : Container(), |
| | | |
| | | Container( |
| | | padding: const EdgeInsets.fromLTRB(12, 16, 12, 0), |
| | | child: Row( |
| | | mainAxisAlignment: MainAxisAlignment.spaceBetween, |
| | | children: [ |
| | | const Text( |
| | | "选集", |
| | | style: TextStyle(color: Color(0xFF999999)), |
| | | ), |
| | | InkWell( |
| | | onTap: () { |
| | | setState(() { |
| | | episodeExpand = true; |
| | | }); |
| | | }, |
| | | child: Row(children: [ |
| | | Text( |
| | | _videoInfoModel!.tag ?? "", |
| | | style: const TextStyle(color: Color(0xFF999999)), |
| | | ), |
| | | const Icon( |
| | | Icons.arrow_forward_ios, |
| | | color: Color(0xFF999999), |
| | | size: 15, |
| | | ) |
| | | ])) |
| | | ])), |
| | | Container( |
| | | margin: const EdgeInsets.only(top: 12), |
| | | alignment: Alignment.topLeft, |
| | | height: _videoInfoModel!.showType == 2 ? 40 : 90, |
| | | child: ListView.separated( |
| | | padding: const EdgeInsets.only(left: 12, right: 12), |
| | | scrollDirection: Axis.horizontal, |
| | | controller: _scrollController, |
| | | shrinkWrap: true, |
| | | itemCount: _videoInfoModel!.videoDetailList!.length, |
| | | itemBuilder: (BuildContext context, int index) { |
| | | VideoDetailInfo detailInfo = |
| | | _videoInfoModel!.videoDetailList![index]; |
| | | return _getEpisodeItem(detailInfo, index); |
| | | }, |
| | | separatorBuilder: (BuildContext context, int index) { |
| | | return Container( |
| | | width: 8.5, |
| | | ); |
| | | }, |
| | | )), |
| | | relativeVideoList.isNotEmpty |
| | | ? Container( |
| | | margin: const EdgeInsets.fromLTRB(12, 30, 12, 20), |
| | | alignment: Alignment.topLeft, |
| | | child: Column( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | const Text( |
| | | "相关推荐", |
| | | style: TextStyle(color: Colors.black, fontSize: 16.41), |
| | | ), |
| | | ListView.separated( |
| | | physics: const NeverScrollableScrollPhysics(), |
| | | shrinkWrap: true, |
| | | itemBuilder: (BuildContext context, int index) { |
| | | return InkWell( |
| | | onTap: () { |
| | | setState(() { |
| | | _playPosition = 0; |
| | | _selectedVideoResource = null; |
| | | _simpleVideo = relativeVideoList[index]; |
| | | }); |
| | | getVideoDetail(_simpleVideo!.id); |
| | | }, |
| | | child: VideoListUIUtil.getRecommendVideo( |
| | | context, relativeVideoList[index])); |
| | | }, |
| | | separatorBuilder: (BuildContext context, int index) { |
| | | return Container( |
| | | height: 14, |
| | | ); |
| | | }, |
| | | itemCount: relativeVideoList.length) |
| | | ]), |
| | | ) |
| | | : Container() |
| | | ], |
| | | ); |
| | | } |
| | | |
| | | Widget _getEpisodeItem(VideoDetailInfo detailInfo, int index) { |
| | | //滑到指定位置 |
| | | |
| | | if (_videoInfoModel!.showType == 2) { |
| | | return VideoListUIUtil.getTVEpisodeItem(detailInfo.tag, 60, 30, context, |
| | | checked: _playPosition == index, onClick: () { |
| | | _scrollController.animateTo(60.0 * index + 8.5 * index, |
| | | duration: const Duration(milliseconds: 200), curve: Curves.ease); |
| | | setState(() { |
| | | _playPosition = index; |
| | | episodeExpand = false; |
| | | }); |
| | | getPlayUrl(_playPosition); |
| | | }); |
| | | } else { |
| | | return VideoListUIUtil.getShowEpisodeItem(detailInfo.tag, context, |
| | | checked: _playPosition == index, onClick: () { |
| | | _scrollController.animateTo(200.0 * index + 8.5 * index, |
| | | duration: const Duration(milliseconds: 200), curve: Curves.ease); |
| | | setState(() { |
| | | _playPosition = index; |
| | | episodeExpand = false; |
| | | }); |
| | | getPlayUrl(_playPosition); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | Widget _getVideoDescView() { |
| | | return Container( |
| | | alignment: Alignment.topLeft, |
| | | padding: const EdgeInsets.all(10), |
| | | child: SingleChildScrollView( |
| | | child: Text( |
| | | _videoInfoModel!.introduction ?? "", |
| | | style: const TextStyle(fontSize: 16), |
| | | ))); |
| | | } |
| | | |
| | | Widget _getResourceListDialog() { |
| | | return Container( |
| | | margin: const EdgeInsets.all(10), |
| | | alignment: Alignment.bottomCenter, |
| | | child: Stack(alignment: Alignment.bottomCenter, children: [ |
| | | Container( |
| | | margin: const EdgeInsets.only(bottom: 50), |
| | | decoration: BoxDecoration( |
| | | color: Colors.white, borderRadius: BorderRadius.circular(10)), |
| | | child: GridView.count( |
| | | childAspectRatio: 3, |
| | | shrinkWrap: true, |
| | | crossAxisCount: 2, |
| | | children: _videoInfoModel!.resourceList!.map((e) { |
| | | return InkWell( |
| | | onTap: () { |
| | | setState(() { |
| | | _selectedVideoResource = e; |
| | | }); |
| | | popPage(context); |
| | | getVideoDetail(_simpleVideo!.id); |
| | | }, |
| | | child: Container( |
| | | alignment: Alignment.center, |
| | | child: Text( |
| | | e.name!, |
| | | style: TextStyle(fontSize: 18), |
| | | ))); |
| | | }).toList(), |
| | | )), |
| | | MyFillButton( |
| | | "取消", |
| | | 20, |
| | | height: 40, |
| | | color: Colors.grey, |
| | | fontSize: 14, |
| | | onClick: () { |
| | | popPage(context); |
| | | }, |
| | | ) |
| | | ])); |
| | | } |
| | | } |
| | | |
| | | enum VideoDetailStatus { success, fail, going } |
New file |
| | |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import '../../model/video/watch_record_model.dart'; |
| | | import '../../ui/widget/button.dart'; |
| | | import '../../ui/widget/refresh_listview.dart'; |
| | | import '../../ui/widget/video_item.dart'; |
| | | import '../../utils/db_manager.dart'; |
| | | import '../../utils/video/video_util.dart'; |
| | | import '../../api/http.dart'; |
| | | import '../../ui/common/browser.dart'; |
| | | import '../../ui/widget/dialog.dart'; |
| | | import '../../ui/widget/nav.dart'; |
| | | import '../../utils/cache_util.dart'; |
| | | import '../../utils/config_util.dart'; |
| | | import '../../utils/event_bus_util.dart'; |
| | | import '../../utils/pageutils.dart'; |
| | | import '../../utils/push_util.dart'; |
| | | import '../../utils/setting_util.dart'; |
| | | import '../../utils/string_util.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | import '../../utils/ui_utils.dart'; |
| | | import '../../utils/user_util.dart'; |
| | | import 'package:package_info/package_info.dart'; |
| | | |
| | | void main() { |
| | | runApp(MyApp()); |
| | | } |
| | | |
| | | class MyApp extends StatelessWidget { |
| | | // This widget is the root of your application. |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return MaterialApp( |
| | | title: '视频下载', |
| | | theme: ThemeData(primaryColor: const Color(0xFFF5F5F5)), |
| | | home: VideoDownloadPage(title: ''), |
| | | ); |
| | | } |
| | | } |
| | | |
| | | class VideoDownloadPage extends StatefulWidget { |
| | | VideoDownloadPage({Key? key, required this.title}) : super(key: key); |
| | | |
| | | // This widget is the home page of your application. It is stateful, meaning |
| | | // that it has a State object (defined below) that contains fields that affect |
| | | // how it looks. |
| | | |
| | | // This class is the configuration for the state. It holds the values (in this |
| | | // case the title) provided by the parent (in this case the App widget) and |
| | | // used by the build method of the State. Fields in a Widget subclass are |
| | | // always marked "final". |
| | | |
| | | final String title; |
| | | |
| | | @override |
| | | _VideoDownloadPageState createState() => _VideoDownloadPageState(); |
| | | } |
| | | |
| | | class _VideoDownloadPageState extends State<VideoDownloadPage> |
| | | with SingleTickerProviderStateMixin { |
| | | bool editMode = false; |
| | | Set<String> selectedSet = {}; |
| | | |
| | | final MyRefreshController _refreshController = MyRefreshController(); |
| | | List<WatchRecordModel> recordList = []; |
| | | |
| | | int page = 1; |
| | | int toalCount = 0; |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | } |
| | | |
| | | void loadWatchRecord(int _page) async { |
| | | page = _page; |
| | | List<WatchRecordModel> temp = []; |
| | | int count = 0; |
| | | setState(() { |
| | | toalCount = count; |
| | | }); |
| | | if (page == 1) { |
| | | setState(() { |
| | | recordList = temp; |
| | | }); |
| | | } else { |
| | | setState(() { |
| | | recordList.addAll(temp); |
| | | }); |
| | | } |
| | | |
| | | _refreshController.refreshCompleted(); |
| | | if (count >= recordList.length) { |
| | | _refreshController.loadNoData(); |
| | | } else { |
| | | _refreshController.loadComplete(); |
| | | } |
| | | if (toalCount == 0) { |
| | | _refreshController.dataEmpty!(); |
| | | } |
| | | } |
| | | |
| | | void deleteRecord() async{ |
| | | if (selectedSet.isEmpty) { |
| | | return; |
| | | } |
| | | await DBManager.deleteWatchRecord(selectedSet.toList()); |
| | | setState(() { |
| | | selectedSet.clear(); |
| | | }); |
| | | loadWatchRecord(1); |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return Scaffold( |
| | | backgroundColor: Colors.white, |
| | | body: Column( |
| | | children: [ |
| | | TopNavBar( |
| | | title: "我的下载", |
| | | rightIcon: recordList.isNotEmpty? const Icon( |
| | | Icons.format_list_bulleted, |
| | | size: 25, |
| | | color: Color(0xFF202020), |
| | | ):null, |
| | | rightClick: () { |
| | | setState(() { |
| | | editMode = !editMode; |
| | | if (!editMode) { |
| | | selectedSet.clear(); |
| | | _refreshController.setPullDownEnable!(true); |
| | | _refreshController.setPullUpEnable!(true); |
| | | } else { |
| | | _refreshController.setPullDownEnable!(false); |
| | | _refreshController.setPullUpEnable!(false); |
| | | } |
| | | }); |
| | | }, |
| | | ), |
| | | Container( |
| | | height: DimenUtil.getOnePixel(context), |
| | | color: const Color(0xFFDBDBDB), |
| | | ), |
| | | Expanded( |
| | | child: RefreshListView( |
| | | content: ListView.builder( |
| | | padding: const EdgeInsets.only(top: 10), |
| | | itemBuilder: (BuildContext context, int index) { |
| | | return getItem(index); |
| | | }, |
| | | itemCount: recordList.length, |
| | | ), |
| | | refreshController: _refreshController, |
| | | refresh: () { |
| | | loadWatchRecord(1); |
| | | }, |
| | | loadMore: () { |
| | | loadWatchRecord(page + 1); |
| | | }, |
| | | )), |
| | | editMode |
| | | ? Container( |
| | | padding: const EdgeInsets.fromLTRB(28, 10, 28, 10), |
| | | decoration: const BoxDecoration( |
| | | color: Colors.white, |
| | | border: Border( |
| | | top: BorderSide( |
| | | color: Color(0xFFDBDBDB), width: 0.5))), |
| | | child: Row( |
| | | children: [ |
| | | Expanded( |
| | | child: MyOutlineButton( |
| | | recordList.length == selectedSet.length ? "反选" : "全选", |
| | | 8, |
| | | height: 34, |
| | | fontSize: 16, |
| | | color: const Color(0xFFdbdbdb), |
| | | textColor: const Color(0xFF202020), |
| | | onClick: () { |
| | | if (recordList.length == selectedSet.length) { |
| | | setState(() { |
| | | selectedSet.clear(); |
| | | }); |
| | | } else { |
| | | Set<String> list = recordList |
| | | .map((e) => e.id.toString()) |
| | | .toSet(); |
| | | setState(() { |
| | | selectedSet = list; |
| | | }); |
| | | } |
| | | }, |
| | | )), |
| | | Container( |
| | | width: 20, |
| | | ), |
| | | Expanded( |
| | | child: MyFillButton( |
| | | selectedSet.isNotEmpty |
| | | ? ("删除(${selectedSet.length})") |
| | | : "删除", |
| | | 8, |
| | | height: 34, |
| | | fontSize: 16, |
| | | onClick: () { |
| | | deleteRecord(); |
| | | }, |
| | | )), |
| | | ], |
| | | ), |
| | | ) |
| | | : Container() |
| | | ], |
| | | )); |
| | | } |
| | | |
| | | Widget getItem(index) { |
| | | return Container( |
| | | alignment: Alignment.centerLeft, |
| | | margin: const EdgeInsets.fromLTRB(10, 8, 10, 8), |
| | | height: 80, |
| | | child: InkWell( |
| | | child: Row( |
| | | children: [ |
| | | Stack( |
| | | alignment: Alignment.center, |
| | | children: [ |
| | | ClipRRect( |
| | | borderRadius: BorderRadius.circular(6), |
| | | child: VideoImage( |
| | | VideoUtil.getHPicture(recordList[index].video!), |
| | | height: 80, |
| | | width: 80 * 1.68, |
| | | )), |
| | | (editMode |
| | | ? Image.asset( |
| | | selectedSet.contains("${recordList[index].id}") |
| | | ? "assets/imgs/video/icon_check_true.png" |
| | | : "assets/imgs/video/icon_check_false.png", |
| | | width: 35, |
| | | ) |
| | | : Container()) |
| | | ], |
| | | ), |
| | | Container( |
| | | width: 10, |
| | | ), |
| | | Expanded( |
| | | child: Column( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | Text( |
| | | recordList[index].video!.name!, |
| | | maxLines: 2, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: |
| | | const TextStyle(color: Color(0xFF232323), fontSize: 15), |
| | | ), |
| | | Text( |
| | | recordList[index].video!.tag ?? "", |
| | | maxLines: 1, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: |
| | | const TextStyle(color: Color(0xFFB8AFB5), fontSize: 12), |
| | | ), |
| | | Expanded(child: Container()), |
| | | Text( |
| | | _getPositionDesc(recordList[index]), |
| | | style: |
| | | const TextStyle(color: Color(0xFFB8AFB5), fontSize: 12), |
| | | ) |
| | | ], |
| | | )) |
| | | ], |
| | | ), |
| | | onTap: () { |
| | | if (!editMode) { |
| | | return; |
| | | } |
| | | print("check: index-$index"); |
| | | setState(() { |
| | | if (selectedSet.contains("${recordList[index].id}")) { |
| | | selectedSet.remove("${recordList[index].id}"); |
| | | } else { |
| | | selectedSet.add("${recordList[index].id}"); |
| | | } |
| | | }); |
| | | }, |
| | | )); |
| | | } |
| | | |
| | | String _getPositionDesc(WatchRecordModel record) { |
| | | if (record.video!.tag != null && record.video!.tag!.contains("集")) { |
| | | return "观看至${record.position! + 1}集"; |
| | | } else { |
| | | return ""; |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import '../../api/video_api.dart'; |
| | | import '../../model/video/home_type_model.dart'; |
| | | import '../../model/video/video_model.dart'; |
| | | import '../../ui/search/search.dart'; |
| | | import '../../ui/widget/video_item.dart'; |
| | | import '../../utils/pageutils.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | import '../../utils/ui_utils.dart'; |
| | | import '../widget/button.dart'; |
| | | |
| | | import '../../ui/widget/nav.dart'; |
| | | import '../widget/refresh_listview.dart'; |
| | | |
| | | class VideoListPage extends StatefulWidget { |
| | | final String kw; |
| | | |
| | | VideoListPage({Key? key, required this.title, required this.kw}) |
| | | : super(key: key); |
| | | |
| | | // This widget is the home page of your application. It is stateful, meaning |
| | | // that it has a State object (defined below) that contains fields that affect |
| | | // how it looks. |
| | | |
| | | // This class is the configuration for the state. It holds the values (in this |
| | | // case the title) provided by the parent (in this case the App widget) and |
| | | // used by the build method of the State. Fields in a Widget subclass are |
| | | // always marked "final". |
| | | |
| | | final String title; |
| | | |
| | | @override |
| | | _VideoListPageState createState() => _VideoListPageState(); |
| | | } |
| | | |
| | | class _VideoListPageState extends State<VideoListPage> |
| | | with SingleTickerProviderStateMixin { |
| | | int page = 1; |
| | | List<VideoInfoModel> videoList = []; |
| | | int column = 2; |
| | | bool hasNextPage = true; |
| | | |
| | | final MyRefreshController _refreshController = MyRefreshController(); |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | getVideoList(1); |
| | | } |
| | | |
| | | void getVideoList(int _page) async { |
| | | page = _page; |
| | | Map<String, dynamic>? result = |
| | | await VideoApiUtil.getSearchSpecialVideoList(context, widget.kw, _page); |
| | | _refreshController.refreshCompleted(resetFooterState: true); |
| | | //请求失败了 |
| | | if (result == null) { |
| | | if (page > 1) { |
| | | page = page - 1; |
| | | } |
| | | if (videoList.isEmpty) { |
| | | _refreshController.apiError!(); |
| | | } |
| | | return; |
| | | } |
| | | if (result["IsPost"] == "true") { |
| | | List<dynamic> list = result["Data"]["list"]; |
| | | hasNextPage = result["Data"]["hasNextPage"]; |
| | | column = result["Data"]["column"]; |
| | | List<VideoInfoModel> tempList = []; |
| | | for (var element in list) { |
| | | tempList.add(VideoInfoModel.fromJson(element)); |
| | | } |
| | | setState(() { |
| | | if (_page == 1) { |
| | | videoList = tempList; |
| | | } else { |
| | | if (tempList.isNotEmpty) { |
| | | videoList.addAll(tempList); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | if (!hasNextPage) { |
| | | _refreshController.loadNoData(); |
| | | } else { |
| | | if (_page > 1) { |
| | | _refreshController.loadComplete(); |
| | | } |
| | | } |
| | | |
| | | if (videoList.isEmpty) { |
| | | _refreshController.dataEmpty!(); |
| | | } else { |
| | | //正常的状态 |
| | | _refreshController.dataNormal!(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return Scaffold( |
| | | backgroundColor: Colors.white, |
| | | body: Column( |
| | | children: [ |
| | | TopNavBar( |
| | | title: "", |
| | | leftText: widget.title, |
| | | rightIcon: const Icon( |
| | | Icons.search, |
| | | size: 40, |
| | | color: ColorConstant.theme, |
| | | ), |
| | | rightClick: () { |
| | | NavigatorUtil.navigateToNextPage( |
| | | context, SearchPage(title: ""), (data) {}); |
| | | }, |
| | | ), |
| | | Container( |
| | | height: DimenUtil.getOnePixel(context), |
| | | color: const Color(0xFFDBDBDB), |
| | | ), |
| | | Expanded( |
| | | child: RefreshListView( |
| | | content: GridView.builder( |
| | | padding: const EdgeInsets.fromLTRB(10, 10, 10, 10), |
| | | itemCount: videoList.length, |
| | | itemBuilder: (BuildContext context, int index) { |
| | | if (column == 2) { |
| | | return VideoListUIUtil.getVideoItemColumn_2( |
| | | MediaQuery.of(context).size.width - 10 * 2, |
| | | HomeVideoModel(video: videoList[index]), |
| | | context); |
| | | } else { |
| | | return VideoListUIUtil.getVideoItemColumn_3( |
| | | MediaQuery.of(context).size.width - 10 * 2, |
| | | HomeVideoModel(video: videoList[index]), |
| | | context); |
| | | } |
| | | }, |
| | | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( |
| | | //横轴元素个数 |
| | | crossAxisCount: column, |
| | | //纵轴间距 |
| | | mainAxisSpacing: 10.0, |
| | | //横轴间距 |
| | | crossAxisSpacing: 10.0, |
| | | //子组件宽高长度比例 |
| | | childAspectRatio: column == 2 ? 1.25 : 0.55), |
| | | ), |
| | | refreshController: _refreshController, |
| | | refresh: () { |
| | | getVideoList(1); |
| | | }, |
| | | loadMore: () { |
| | | getVideoList(page + 1); |
| | | }, |
| | | )), |
| | | ], |
| | | )); |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:io'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import 'package:flutter/services.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | import '../../utils/ui_utils.dart'; |
| | | import 'package:url_launcher/url_launcher.dart'; |
| | | import 'package:webview_flutter/webview_flutter.dart'; |
| | | |
| | | import '../../ui/widget/nav.dart'; |
| | | import '../../utils/jsinterface.dart'; |
| | | import 'package:url_launcher/url_launcher.dart'; |
| | | |
| | | class VideoPlayerWebPage extends StatefulWidget { |
| | | VideoPlayerWebPage({Key? key, required this.title, required this.url}) |
| | | : super(key: key); |
| | | final String title; |
| | | final String url; |
| | | |
| | | @override |
| | | _VideoPlayerWebPageState createState() => |
| | | _VideoPlayerWebPageState(title, url); |
| | | } |
| | | |
| | | class _VideoPlayerWebPageState extends State<VideoPlayerWebPage> |
| | | with SingleTickerProviderStateMixin { |
| | | String title = ""; |
| | | String? url; |
| | | double progress = 0; |
| | | |
| | | _VideoPlayerWebPageState(this.title, this.url); |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | if (Platform.isAndroid) { |
| | | WebView.platform = SurfaceAndroidWebView(); |
| | | } |
| | | } |
| | | |
| | | WebViewController? _webViewController; |
| | | |
| | | _back() { |
| | | if (_webViewController == null) { |
| | | popPage(context); |
| | | } else { |
| | | _webViewController!.canGoBack().then((value) { |
| | | if (value) { |
| | | _webViewController!.goBack(); |
| | | } else { |
| | | popPage(context); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | |
| | | Future<void> _launchInBrowser(String url) async { |
| | | if (!await launch(url)) throw 'Could not launch $url'; |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return WillPopScope( |
| | | onWillPop: () async { |
| | | _back(); |
| | | return false; |
| | | }, |
| | | child: Scaffold( |
| | | backgroundColor: Colors.white, |
| | | body: Flex( |
| | | direction: Axis.vertical, |
| | | children: [ |
| | | Flex(direction: Axis.vertical, children: [ |
| | | Container( |
| | | height: MediaQuery.of(context).viewPadding.top, |
| | | color: Colors.white, |
| | | ), |
| | | Container( |
| | | color: Colors.white, |
| | | height: 48, |
| | | child: Stack( |
| | | alignment: Alignment.centerLeft, |
| | | children: [ |
| | | Positioned( |
| | | child: Container( |
| | | alignment: Alignment.center, |
| | | child: Flex( |
| | | direction: Axis.horizontal, |
| | | mainAxisAlignment: MainAxisAlignment.center, |
| | | children: [ |
| | | Container( |
| | | width: 70, |
| | | ), |
| | | Expanded( |
| | | child: Column( |
| | | crossAxisAlignment: |
| | | CrossAxisAlignment.start, |
| | | mainAxisAlignment: |
| | | MainAxisAlignment.center, |
| | | children: [ |
| | | Text( |
| | | title, |
| | | maxLines: 1, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: const TextStyle( |
| | | fontSize: 18, |
| | | color: Colors.black), |
| | | ), |
| | | Text( |
| | | url!, |
| | | maxLines: 1, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: const TextStyle( |
| | | fontSize: 10, |
| | | color: Color(0xFF999999)), |
| | | ) |
| | | ])), |
| | | Container( |
| | | width: 50, |
| | | ) |
| | | ], |
| | | ))), |
| | | Positioned( |
| | | right: 0, |
| | | top: 0, |
| | | bottom: 0, |
| | | child: InkWell( |
| | | onTap: () {}, |
| | | child: Container( |
| | | alignment: Alignment.center, |
| | | padding: const EdgeInsets.only(right: 10), |
| | | child: PopupMenuButton( |
| | | offset: const Offset(0, 48), |
| | | icon: const Icon( |
| | | Icons.more_horiz, |
| | | size: 38, |
| | | color: Color(0xFF666666), |
| | | ), |
| | | itemBuilder: (BuildContext context) { |
| | | return [ |
| | | PopupMenuItem( |
| | | textStyle: const TextStyle( |
| | | fontSize: 15, |
| | | color: Color(0xFF666666)), |
| | | child: Row( |
| | | mainAxisSize: MainAxisSize.min, |
| | | children: [ |
| | | Image.asset( |
| | | "assets/imgs/common/icon_browser_refresh.png", |
| | | width: 17, |
| | | ), |
| | | Container( |
| | | width: 9, |
| | | ), |
| | | const Text("刷新") |
| | | ]), |
| | | onTap: () { |
| | | if (_webViewController == null) { |
| | | return; |
| | | } |
| | | _webViewController!.reload(); |
| | | }, |
| | | ), |
| | | PopupMenuItem( |
| | | textStyle: const TextStyle( |
| | | fontSize: 15, |
| | | color: Color(0xFF666666)), |
| | | child: Row( |
| | | mainAxisSize: MainAxisSize.min, |
| | | children: [ |
| | | Image.asset( |
| | | "assets/imgs/common/icon_browser_link.png", |
| | | width: 17, |
| | | ), |
| | | Container( |
| | | width: 9, |
| | | ), |
| | | const Text("复制链接") |
| | | ]), |
| | | onTap: () { |
| | | if (url == null) { |
| | | return; |
| | | } |
| | | Clipboard.setData( |
| | | ClipboardData(text: url)); |
| | | ToastUtil.toast("复制成功", context); |
| | | }, |
| | | ), |
| | | PopupMenuItem( |
| | | textStyle: const TextStyle( |
| | | fontSize: 15, |
| | | color: Color(0xFF666666)), |
| | | child: Row( |
| | | mainAxisSize: MainAxisSize.min, |
| | | children: [ |
| | | Image.asset( |
| | | "assets/imgs/common/icon_browser_out.png", |
| | | width: 17, |
| | | ), |
| | | Container( |
| | | width: 9, |
| | | ), |
| | | const Text("外部浏览器打开") |
| | | ]), |
| | | onTap: () { |
| | | if (url == null) { |
| | | return; |
| | | } |
| | | _launchInBrowser(url!); |
| | | }, |
| | | ), |
| | | ]; |
| | | }, |
| | | ), |
| | | ))), |
| | | Positioned( |
| | | left: 30, |
| | | child: InkWell( |
| | | onTap: () { |
| | | popPage(context); |
| | | }, |
| | | child: const Icon( |
| | | Icons.clear, |
| | | color: Color(0xFF666666), |
| | | size: 35, |
| | | )), |
| | | ), |
| | | InkWell( |
| | | onTap: () { |
| | | _webViewController!.canGoBack().then((value) { |
| | | if (value) { |
| | | _webViewController!.goBack(); |
| | | } else { |
| | | popPage(context); |
| | | } |
| | | }); |
| | | }, |
| | | child: Container( |
| | | padding: const EdgeInsets.only(left: 10), |
| | | height: 48, |
| | | child: Row( |
| | | mainAxisSize: MainAxisSize.min, |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | children: const [ |
| | | Icon( |
| | | Icons.arrow_back_ios, |
| | | color: Color(0xFF666666), |
| | | size: 30, |
| | | ), |
| | | ]), |
| | | )) |
| | | ], |
| | | ), |
| | | ) |
| | | ]), |
| | | SizedBox( |
| | | height: 1, |
| | | child: LinearProgressIndicator( |
| | | backgroundColor: Colors.white, |
| | | valueColor: |
| | | const AlwaysStoppedAnimation(ColorConstant.theme), |
| | | value: progress, |
| | | ), |
| | | ), |
| | | Expanded( |
| | | child: WebView( |
| | | //http://192.168.3.122:8848/test/JsTest.html |
| | | initialUrl: url, |
| | | onWebViewCreated: (WebViewController webViewController) { |
| | | _webViewController = webViewController; |
| | | }, |
| | | javascriptMode: JavascriptMode.unrestricted, |
| | | javascriptChannels: |
| | | JavascriptInterface(context, _webViewController) |
| | | .getInterfaces(), |
| | | navigationDelegate: (NavigationRequest request) { |
| | | print("链接:${request.url}"); |
| | | if (!request.url.startsWith("http")) { |
| | | launch(request.url); |
| | | return NavigationDecision.prevent; |
| | | } |
| | | return NavigationDecision.navigate; |
| | | }, |
| | | onPageStarted: (url) { |
| | | print("process:onPageStarted-$url"); |
| | | }, |
| | | onPageFinished: (url) { |
| | | print("process:onPageFinished-$url"); |
| | | _webViewController!.getTitle().then((value) { |
| | | if (value != null) { |
| | | setState(() { |
| | | title = value; |
| | | }); |
| | | } |
| | | }); |
| | | }, |
| | | onProgress: (int process) { |
| | | print("process:$process"); |
| | | if (progress == 0) { |
| | | if (_webViewController != null) { |
| | | _webViewController!.currentUrl().then((value) { |
| | | setState(() { |
| | | url = value; |
| | | }); |
| | | }); |
| | | } |
| | | } |
| | | setState(() { |
| | | if (process == 100) { |
| | | progress = 0; |
| | | } else { |
| | | progress = process / 100.0; |
| | | } |
| | | }); |
| | | }, |
| | | )) |
| | | ], |
| | | ))); |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import '../../model/video/watch_record_model.dart'; |
| | | import '../../ui/video/video_detail.dart'; |
| | | import '../../ui/widget/button.dart'; |
| | | import '../../ui/widget/refresh_listview.dart'; |
| | | import '../../ui/widget/video_item.dart'; |
| | | import '../../utils/db_manager.dart'; |
| | | import '../../utils/video/video_util.dart'; |
| | | import '../../api/http.dart'; |
| | | import '../../ui/common/browser.dart'; |
| | | import '../../ui/widget/dialog.dart'; |
| | | import '../../ui/widget/nav.dart'; |
| | | import '../../utils/cache_util.dart'; |
| | | import '../../utils/config_util.dart'; |
| | | import '../../utils/event_bus_util.dart'; |
| | | import '../../utils/pageutils.dart'; |
| | | import '../../utils/push_util.dart'; |
| | | import '../../utils/setting_util.dart'; |
| | | import '../../utils/string_util.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | import '../../utils/ui_utils.dart'; |
| | | import '../../utils/user_util.dart'; |
| | | import 'package:package_info/package_info.dart'; |
| | | |
| | | void main() { |
| | | runApp(MyApp()); |
| | | } |
| | | |
| | | class MyApp extends StatelessWidget { |
| | | // This widget is the root of your application. |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return MaterialApp( |
| | | title: '视频收藏', |
| | | theme: ThemeData(primaryColor: const Color(0xFFF5F5F5)), |
| | | home: VideoScanRecordPage(title: ''), |
| | | ); |
| | | } |
| | | } |
| | | |
| | | class VideoScanRecordPage extends StatefulWidget { |
| | | VideoScanRecordPage({Key? key, required this.title}) : super(key: key); |
| | | |
| | | // This widget is the home page of your application. It is stateful, meaning |
| | | // that it has a State object (defined below) that contains fields that affect |
| | | // how it looks. |
| | | |
| | | // This class is the configuration for the state. It holds the values (in this |
| | | // case the title) provided by the parent (in this case the App widget) and |
| | | // used by the build method of the State. Fields in a Widget subclass are |
| | | // always marked "final". |
| | | |
| | | final String title; |
| | | |
| | | @override |
| | | _VideoScanRecordPageState createState() => _VideoScanRecordPageState(); |
| | | } |
| | | |
| | | class _VideoScanRecordPageState extends State<VideoScanRecordPage> |
| | | with SingleTickerProviderStateMixin { |
| | | bool editMode = false; |
| | | Set<String> selectedSet = {}; |
| | | |
| | | final MyRefreshController _refreshController = |
| | | MyRefreshController(initialRefresh: false); |
| | | List<WatchRecordModel> recordList = []; |
| | | |
| | | int page = 1; |
| | | int toalCount = 0; |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | loadWatchRecord(1); |
| | | } |
| | | |
| | | Future loadWatchRecord(int _page) async { |
| | | page = _page; |
| | | List<WatchRecordModel> temp = await DBManager.listWatchRecord(page, 20); |
| | | int count = await DBManager.countWatchRecord(); |
| | | setState(() { |
| | | toalCount = count; |
| | | }); |
| | | if (page == 1) { |
| | | setState(() { |
| | | recordList = temp; |
| | | }); |
| | | } else { |
| | | setState(() { |
| | | recordList.addAll(temp); |
| | | }); |
| | | } |
| | | |
| | | _refreshController.refreshCompleted(); |
| | | if (count >= recordList.length) { |
| | | _refreshController.loadNoData(); |
| | | } else { |
| | | _refreshController.loadComplete(); |
| | | } |
| | | if (toalCount == 0) { |
| | | _refreshController.dataEmpty!(); |
| | | } |
| | | } |
| | | |
| | | void deleteRecord() async { |
| | | if (selectedSet.isEmpty) { |
| | | return; |
| | | } |
| | | await DBManager.deleteWatchRecord(selectedSet.toList()); |
| | | setState(() { |
| | | selectedSet.clear(); |
| | | }); |
| | | loadWatchRecord(1).then((value) { |
| | | if (recordList.isEmpty) { |
| | | setState(() { |
| | | editMode = false; |
| | | }); |
| | | _refreshController.setEditMode!(false); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | Widget getListView() { |
| | | return ListView.builder( |
| | | padding: const EdgeInsets.only(top: 10), |
| | | itemBuilder: (BuildContext context, int index) { |
| | | return getItem(index); |
| | | }, |
| | | itemCount: recordList.length, |
| | | ); |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return Scaffold( |
| | | backgroundColor: Colors.white, |
| | | body: Column( |
| | | children: [ |
| | | TopNavBar( |
| | | title: "观看记录", |
| | | rightIcon: recordList.isNotEmpty |
| | | ? const Icon( |
| | | Icons.format_list_bulleted, |
| | | size: 25, |
| | | color: Color(0xFF202020), |
| | | ) |
| | | : null, |
| | | rightClick: () { |
| | | setState(() { |
| | | editMode = !editMode; |
| | | _refreshController.setEditMode!(editMode); |
| | | if (!editMode) { |
| | | selectedSet.clear(); |
| | | } else {} |
| | | }); |
| | | }, |
| | | ), |
| | | Container( |
| | | height: DimenUtil.getOnePixel(context), |
| | | color: const Color(0xFFDBDBDB), |
| | | ), |
| | | Expanded( |
| | | child: RefreshListView( |
| | | content: getListView(), |
| | | refreshController: _refreshController, |
| | | refresh: () { |
| | | loadWatchRecord(1); |
| | | }, |
| | | loadMore: () { |
| | | loadWatchRecord(page + 1); |
| | | }, |
| | | )), |
| | | editMode && recordList.isNotEmpty |
| | | ? Container( |
| | | padding: const EdgeInsets.fromLTRB(28, 10, 28, 10), |
| | | decoration: const BoxDecoration( |
| | | color: Colors.white, |
| | | border: Border( |
| | | top: BorderSide( |
| | | color: Color(0xFFDBDBDB), width: 0.5))), |
| | | child: Row( |
| | | children: [ |
| | | Expanded( |
| | | child: MyOutlineButton( |
| | | recordList.length == selectedSet.length ? "反选" : "全选", |
| | | 8, |
| | | height: 34, |
| | | fontSize: 16, |
| | | color: const Color(0xFFdbdbdb), |
| | | textColor: const Color(0xFF202020), |
| | | onClick: () { |
| | | if (recordList.length == selectedSet.length) { |
| | | setState(() { |
| | | selectedSet.clear(); |
| | | }); |
| | | } else { |
| | | Set<String> list = recordList |
| | | .map((e) => e.id.toString()) |
| | | .toSet(); |
| | | setState(() { |
| | | selectedSet = list; |
| | | }); |
| | | } |
| | | }, |
| | | )), |
| | | Container( |
| | | width: 20, |
| | | ), |
| | | Expanded( |
| | | child: MyFillButton( |
| | | selectedSet.isNotEmpty |
| | | ? ("删除(${selectedSet.length})") |
| | | : "删除", |
| | | 8, |
| | | height: 34, |
| | | fontSize: 16, |
| | | onClick: () { |
| | | deleteRecord(); |
| | | }, |
| | | )), |
| | | ], |
| | | ), |
| | | ) |
| | | : Container() |
| | | ], |
| | | )); |
| | | } |
| | | |
| | | Widget getItem(index) { |
| | | return Container( |
| | | alignment: Alignment.centerLeft, |
| | | margin: const EdgeInsets.fromLTRB(10, 8, 10, 8), |
| | | height: 80, |
| | | child: InkWell( |
| | | child: Row( |
| | | children: [ |
| | | Stack( |
| | | alignment: Alignment.center, |
| | | children: [ |
| | | ClipRRect( |
| | | borderRadius: BorderRadius.circular(6), |
| | | child: VideoImage( |
| | | VideoUtil.getHPicture(recordList[index].video!), |
| | | height: 80, |
| | | width: 80 * 1.68, |
| | | )), |
| | | (editMode |
| | | ? Image.asset( |
| | | selectedSet.contains("${recordList[index].id}") |
| | | ? "assets/imgs/video/icon_check_true.png" |
| | | : "assets/imgs/video/icon_check_false.png", |
| | | width: 35, |
| | | ) |
| | | : Container()) |
| | | ], |
| | | ), |
| | | Container( |
| | | width: 10, |
| | | ), |
| | | Expanded( |
| | | child: Column( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | Text( |
| | | recordList[index].video!.name!, |
| | | maxLines: 2, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: |
| | | const TextStyle(color: Color(0xFF232323), fontSize: 15), |
| | | ), |
| | | Text( |
| | | recordList[index].video!.tag ?? "", |
| | | maxLines: 1, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: |
| | | const TextStyle(color: Color(0xFFB8AFB5), fontSize: 12), |
| | | ), |
| | | Expanded(child: Container()), |
| | | Text( |
| | | _getPositionDesc(recordList[index]), |
| | | style: |
| | | const TextStyle(color: Color(0xFFB8AFB5), fontSize: 12), |
| | | ) |
| | | ], |
| | | )) |
| | | ], |
| | | ), |
| | | onTap: () { |
| | | if (!editMode) { |
| | | //跳转详情 |
| | | NavigatorUtil.navigateToNextPage(context, VideoDetailPage(position:recordList[index].position! ,videoInfo:recordList[index].video ,), (data) { |
| | | |
| | | |
| | | }); |
| | | |
| | | |
| | | return; |
| | | } |
| | | print("check: index-$index"); |
| | | setState(() { |
| | | if (selectedSet.contains("${recordList[index].id}")) { |
| | | selectedSet.remove("${recordList[index].id}"); |
| | | } else { |
| | | selectedSet.add("${recordList[index].id}"); |
| | | } |
| | | }); |
| | | }, |
| | | )); |
| | | } |
| | | |
| | | String _getPositionDesc(WatchRecordModel record) { |
| | | if (record.video!.tag != null && record.video!.tag!.contains("集")) { |
| | | return "观看至${record.position! + 1}集"; |
| | | } else { |
| | | return ""; |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:io'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/services.dart'; |
| | | |
| | | import 'common_ui.dart'; |
| | | |
| | | class ExpressAdController { |
| | | VoidCallback? refresh; |
| | | } |
| | | |
| | | //TODO 兼容android |
| | | class CSJEXpressAd extends StatefulWidget { |
| | | final String pid; |
| | | final double width; |
| | | final double height; |
| | | final ExpressAdController? controller; |
| | | final VoidCallback? close; |
| | | final VoidCallback? loadFail; |
| | | |
| | | CSJEXpressAd(this.pid, this.width, this.height, |
| | | {this.controller, this.close, this.loadFail}); |
| | | |
| | | @override |
| | | State<StatefulWidget> createState() => _CSJEXpressAdState(); |
| | | } |
| | | |
| | | class _CSJEXpressAdState extends State<CSJEXpressAd> { |
| | | MethodChannel? _expressAdChannel; |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | if (widget.controller != null) { |
| | | widget.controller!.refresh = () { |
| | | if (_expressAdChannel != null) { |
| | | _expressAdChannel!.invokeMethod("refresh"); |
| | | } |
| | | }; |
| | | } |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return KeepAliveWrapper( |
| | | child: Platform.isAndroid |
| | | ? AndroidView( |
| | | viewType: "ad-csj-express-view", |
| | | layoutDirection: TextDirection.ltr, |
| | | onPlatformViewCreated: onPlatformViewCreated, |
| | | creationParams: <String, dynamic>{ |
| | | "width": widget.width, |
| | | "height": widget.height, |
| | | "pid": widget.pid |
| | | }, |
| | | creationParamsCodec: const StandardMessageCodec(), |
| | | ) |
| | | : UiKitView( |
| | | viewType: "ad-csj-express-view", |
| | | layoutDirection: TextDirection.ltr, |
| | | onPlatformViewCreated: onPlatformViewCreated, |
| | | creationParams: <String, dynamic>{ |
| | | "width": widget.width, |
| | | "height": widget.height, |
| | | "pid": widget.pid |
| | | }, |
| | | creationParamsCodec: const StandardMessageCodec(), |
| | | )); |
| | | } |
| | | |
| | | Future<void> onPlatformViewCreated(id) async { |
| | | _expressAdChannel = MethodChannel("ad-csj-express-view-$id"); |
| | | _expressAdChannel!.setMethodCallHandler((call) { |
| | | if ("close" == call.method) { |
| | | if (widget.close != null) { |
| | | widget.close!(); |
| | | } |
| | | } else if ("loadFail" == call.method) { |
| | | if (widget.loadFail != null) { |
| | | widget.loadFail!(); |
| | | } |
| | | } |
| | | return Future.value(""); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | class GDTEXpressAd extends StatefulWidget { |
| | | final String pid; |
| | | final double width; |
| | | final double height; |
| | | final ExpressAdController? controller; |
| | | final VoidCallback? close; |
| | | final VoidCallback? loadFail; |
| | | |
| | | GDTEXpressAd(this.pid, this.width, this.height, |
| | | {this.controller, this.close, this.loadFail}); |
| | | |
| | | @override |
| | | State<StatefulWidget> createState() => _GDTEXpressAdState(); |
| | | } |
| | | |
| | | class _GDTEXpressAdState extends State<GDTEXpressAd> { |
| | | MethodChannel? _expressAdChannel; |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | if (widget.controller != null) { |
| | | widget.controller!.refresh = () { |
| | | if (_expressAdChannel != null) { |
| | | _expressAdChannel!.invokeMethod("refresh"); |
| | | } |
| | | }; |
| | | } |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return KeepAliveWrapper( |
| | | child: UiKitView( |
| | | viewType: "ad-gdt-express-view", |
| | | layoutDirection: TextDirection.ltr, |
| | | onPlatformViewCreated: onPlatformViewCreated, |
| | | creationParams: <String, dynamic>{ |
| | | "width": widget.width, |
| | | "height": widget.height, |
| | | "pid": widget.pid |
| | | }, |
| | | creationParamsCodec: const StandardMessageCodec(), |
| | | )); |
| | | } |
| | | |
| | | Future<void> onPlatformViewCreated(id) async { |
| | | _expressAdChannel = MethodChannel("ad-gdt-express-view-$id"); |
| | | _expressAdChannel!.setMethodCallHandler((call) { |
| | | if ("close" == call.method) { |
| | | if (widget.close != null) { |
| | | widget.close!(); |
| | | } |
| | | } else if ("loadFail" == call.method) { |
| | | if (widget.loadFail != null) { |
| | | widget.loadFail!(); |
| | | } |
| | | } |
| | | return Future.value(""); |
| | | }); |
| | | } |
| | | } |
New file |
| | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:pull_to_refresh/pull_to_refresh.dart'; |
New file |
| | |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/material.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | |
| | | class MyOutlineButton extends StatelessWidget { |
| | | final String text; |
| | | final double radius; |
| | | final double height; |
| | | final double? width; |
| | | final Color color; |
| | | Color? textColor; |
| | | final double fontSize; |
| | | final GestureTapCallback? onClick; |
| | | final EdgeInsets? padding; |
| | | |
| | | MyOutlineButton(this.text, this.radius, |
| | | {GestureTapCallback? this.onClick, |
| | | Color this.color = ColorConstant.theme, |
| | | Color? textColor, |
| | | double this.height = 26, |
| | | double this.fontSize = 12, |
| | | double? this.width = null, |
| | | EdgeInsets? this.padding = null}) { |
| | | textColor ??= this.color; |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | Container child = Container( |
| | | alignment: Alignment.center, |
| | | height: height, |
| | | width: width, |
| | | padding: padding, |
| | | decoration: BoxDecoration( |
| | | color: Colors.transparent, |
| | | border: Border.all(color: color, width: 1), |
| | | borderRadius: BorderRadius.circular(radius), |
| | | ), |
| | | child: Text( |
| | | text, |
| | | style: TextStyle(color: textColor, fontSize: fontSize), |
| | | ), |
| | | ); |
| | | |
| | | return InkWell( |
| | | onTap: () { |
| | | onClick!(); |
| | | }, |
| | | child: child); |
| | | } |
| | | } |
| | | |
| | | class MyFillButton extends StatelessWidget { |
| | | final String text; |
| | | final Color textColor; |
| | | final double radius; |
| | | final double height; |
| | | final double? width; |
| | | final Color color; |
| | | final double fontSize; |
| | | final GestureTapCallback? onClick; |
| | | final EdgeInsets? padding; |
| | | final bool? enable; |
| | | |
| | | MyFillButton(this.text, this.radius, |
| | | {GestureTapCallback? this.onClick, |
| | | Color this.color = ColorConstant.theme, |
| | | Color this.textColor = Colors.white, |
| | | double this.height = 26, |
| | | double this.fontSize = 12, |
| | | double? this.width = null, |
| | | EdgeInsets? this.padding = null, |
| | | bool? this.enable = true}); |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | Container child = Container( |
| | | alignment: Alignment.center, |
| | | height: height, |
| | | width: width, |
| | | padding: padding, |
| | | decoration: BoxDecoration( |
| | | color: enable! ? color : const Color(0xFFCBCBCB), |
| | | borderRadius: BorderRadius.circular(radius), |
| | | ), |
| | | child: Text( |
| | | text, |
| | | style: TextStyle(color: textColor, fontSize: fontSize), |
| | | ), |
| | | ); |
| | | |
| | | return InkWell( |
| | | onTap: () { |
| | | onClick!(); |
| | | }, |
| | | child: child); |
| | | } |
| | | } |
| | | |
| | | class RoundCheckBox extends StatefulWidget { |
| | | var value = false; |
| | | |
| | | Function(bool) onChanged; |
| | | |
| | | RoundCheckBox({Key? key, required this.value, required this.onChanged}) |
| | | : super(key: key); |
| | | |
| | | @override |
| | | _RoundCheckBoxState createState() => _RoundCheckBoxState(); |
| | | } |
| | | |
| | | class _RoundCheckBoxState extends State<RoundCheckBox> { |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return Center( |
| | | child: GestureDetector( |
| | | onTap: () { |
| | | widget.value = !widget.value; |
| | | widget.onChanged(widget.value); |
| | | }, |
| | | child: Padding( |
| | | padding: const EdgeInsets.all(10.0), |
| | | child: widget.value |
| | | ? const Icon( |
| | | Icons.check_circle, |
| | | size: 19, |
| | | color: Colors.green, |
| | | ) |
| | | : const Icon( |
| | | Icons.panorama_fish_eye, |
| | | size: 19, |
| | | color: Colors.black12, |
| | | ), |
| | | )), |
| | | ); |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:convert'; |
| | | import 'dart:io'; |
| | | import 'dart:typed_data'; |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import 'package:flutter/rendering.dart'; |
| | | |
| | | ///对widget截图 |
| | | |
| | | class CaptureWidget extends StatelessWidget { |
| | | //截图组件 |
| | | GlobalKey rootWidgetKey = GlobalKey(); |
| | | final Widget widget; |
| | | final CaptureController captureController; |
| | | |
| | | CaptureWidget({required this.widget, required this.captureController}) { |
| | | captureController.setGlobalKey(rootWidgetKey); |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return RepaintBoundary(key: rootWidgetKey, child: widget); |
| | | } |
| | | } |
| | | |
| | | class CaptureController { |
| | | GlobalKey? _globalKey; |
| | | |
| | | setGlobalKey(GlobalKey globalKey) { |
| | | _globalKey = globalKey; |
| | | } |
| | | |
| | | Future<String> capturePng() async { |
| | | try { |
| | | RenderRepaintBoundary? boundary = _globalKey!.currentContext! |
| | | .findRenderObject() as RenderRepaintBoundary; |
| | | var image = await boundary.toImage(pixelRatio: 3.0); |
| | | ByteData? byteData = await image.toByteData(format: ImageByteFormat.png); |
| | | Uint8List pngBytes = byteData!.buffer.asUint8List(); |
| | | String str = base64.encode(pngBytes); |
| | | return str; |
| | | } catch (e) {} |
| | | return ""; |
| | | } |
| | | } |
New file |
| | |
| | | import 'package:flutter/widgets.dart'; |
| | | |
| | | class KeepAliveWrapper extends StatefulWidget { |
| | | const KeepAliveWrapper({ |
| | | Key? key, |
| | | this.keepAlive = true, |
| | | required this.child, |
| | | }) : super(key: key); |
| | | final bool keepAlive; |
| | | final Widget child; |
| | | |
| | | @override |
| | | _KeepAliveWrapperState createState() => _KeepAliveWrapperState(); |
| | | } |
| | | |
| | | class _KeepAliveWrapperState extends State<KeepAliveWrapper> |
| | | with AutomaticKeepAliveClientMixin { |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | super.build(context); |
| | | return widget.child; |
| | | } |
| | | |
| | | @override |
| | | void didUpdateWidget(covariant KeepAliveWrapper oldWidget) { |
| | | if (oldWidget.keepAlive != widget.keepAlive) { |
| | | // keepAlive 状态需要更新,实现在 AutomaticKeepAliveClientMixin 中 |
| | | updateKeepAlive(); |
| | | } |
| | | super.didUpdateWidget(oldWidget); |
| | | } |
| | | |
| | | @override |
| | | bool get wantKeepAlive => widget.keepAlive; |
| | | } |
New file |
| | |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/material.dart'; |
| | | import 'package:flutter_spinkit/flutter_spinkit.dart'; |
| | | import '../../ui/search/search.dart'; |
| | | import 'package:html/dom.dart' as dom; |
| | | import '../../ui/common/browser.dart'; |
| | | import '../../utils/pageutils.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart'; |
| | | import '../widget/nav.dart'; |
| | | |
| | | //通用弹框 |
| | | class NotifyDialog extends Dialog { |
| | | BuildContext? context; |
| | | final String title; |
| | | final String content; |
| | | final GestureTapCallback onCancel; |
| | | final GestureTapCallback onSure; |
| | | final bool richText; |
| | | final double fontSize; |
| | | final double height; |
| | | final Color contentColor; |
| | | bool touchOutCancel = false; |
| | | final String cancelName; |
| | | final String sureName; |
| | | |
| | | NotifyDialog(this.title, this.content, this.onCancel, this.onSure, |
| | | {this.fontSize = 16.0, |
| | | this.richText = false, |
| | | this.height = 240, |
| | | this.contentColor = const Color(0xFF333333), |
| | | this.cancelName = "取消", |
| | | this.sureName = "确定"}); |
| | | |
| | | Widget getContent(BuildContext context) { |
| | | if (richText) { |
| | | return SingleChildScrollView( |
| | | child: HtmlWidget(content, onTapUrl: (String url) { |
| | | NavigatorUtil.navigateToNextPage( |
| | | context, |
| | | BrowserPage( |
| | | title: "", |
| | | url: url, |
| | | ), |
| | | (data) {}); |
| | | |
| | | return true; |
| | | })); |
| | | } else { |
| | | return Text( |
| | | content, |
| | | style: TextStyle(color: contentColor, fontSize: fontSize), |
| | | ); |
| | | } |
| | | } |
| | | |
| | | Offset? offset; |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | this.context = context; |
| | | double width = MediaQuery.of(context).size.width; |
| | | double dialogWidth = width * 4 / 5; |
| | | print("屏幕宽:$width"); |
| | | |
| | | //关闭弹框 |
| | | // Navigator.pop(context); |
| | | return WillPopScope( |
| | | onWillPop: () async { |
| | | return false; |
| | | }, |
| | | child: Material( |
| | | type: MaterialType.transparency, |
| | | child: Align( |
| | | alignment: Alignment.center, |
| | | child: Container( |
| | | decoration: const BoxDecoration( |
| | | borderRadius: BorderRadius.all(Radius.circular(15)), |
| | | color: Colors.white), |
| | | alignment: Alignment.topCenter, |
| | | height: height, |
| | | width: dialogWidth, |
| | | child: Flex( |
| | | mainAxisAlignment: MainAxisAlignment.start, |
| | | direction: Axis.vertical, |
| | | children: [ |
| | | //-------标题区域-------- |
| | | Container( |
| | | alignment: Alignment.center, |
| | | height: 60, |
| | | child: Text( |
| | | title, |
| | | style: TextStyle(fontSize: 18, color: Colors.white), |
| | | ), |
| | | decoration: const BoxDecoration( |
| | | color: ColorConstant.theme, |
| | | borderRadius: BorderRadius.only( |
| | | topLeft: Radius.circular(15), |
| | | topRight: Radius.circular(15)), |
| | | )), |
| | | //-------内容区域-------- |
| | | Expanded( |
| | | child: Container( |
| | | alignment: Alignment.center, |
| | | padding: const EdgeInsets.fromLTRB(15, 5, 15, 5), |
| | | child: getContent(context), |
| | | )), |
| | | |
| | | //------按钮区域-------- |
| | | Flex( |
| | | direction: Axis.horizontal, |
| | | children: [ |
| | | Expanded( |
| | | child: InkWell( |
| | | onTap: () { |
| | | Navigator.pop(context); |
| | | onCancel(); |
| | | }, |
| | | child: Container( |
| | | margin: EdgeInsets.fromLTRB(15, 0, 6, 15), |
| | | alignment: Alignment.center, |
| | | height: 44, |
| | | decoration: BoxDecoration( |
| | | border: Border.all(color: ColorConstant.theme), |
| | | borderRadius: BorderRadius.circular(10)), |
| | | child: Text( |
| | | cancelName, |
| | | style: const TextStyle( |
| | | color: ColorConstant.theme, fontSize: 18), |
| | | ), |
| | | ), |
| | | )), |
| | | Expanded( |
| | | child: InkWell( |
| | | onTap: () { |
| | | Navigator.pop(context); |
| | | onSure(); |
| | | }, |
| | | child: Container( |
| | | margin: const EdgeInsets.fromLTRB(6, 0, 15, 15), |
| | | alignment: Alignment.center, |
| | | height: 44, |
| | | decoration: BoxDecoration( |
| | | color: ColorConstant.theme, |
| | | borderRadius: BorderRadius.circular(10)), |
| | | child: Text( |
| | | sureName, |
| | | style: const TextStyle( |
| | | color: Colors.white, fontSize: 18), |
| | | ), |
| | | ), |
| | | )) |
| | | ], |
| | | ) |
| | | ], |
| | | ), |
| | | )))); |
| | | } |
| | | } |
| | | |
| | | ///权限弹框 |
| | | class PermissionNotifyDialog extends Dialog { |
| | | final GestureTapCallback onOpen; |
| | | |
| | | PermissionNotifyDialog(this.onOpen); |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | double width = MediaQuery.of(context).size.width; |
| | | double dialogWidth = width * 4 / 5; |
| | | print("屏幕宽:$width"); |
| | | //关闭弹框 |
| | | // Navigator.pop(context); |
| | | return WillPopScope( |
| | | onWillPop: () async { |
| | | return false; |
| | | }, |
| | | child: Material( |
| | | type: MaterialType.transparency, |
| | | child: Align( |
| | | alignment: Alignment.center, |
| | | child: Container( |
| | | width: dialogWidth, |
| | | child: Flex( |
| | | mainAxisAlignment: MainAxisAlignment.center, |
| | | direction: Axis.vertical, |
| | | children: [ |
| | | Image.asset( |
| | | "assets/imgs/common/ic_permission_notify_top.png"), |
| | | Container( |
| | | padding: EdgeInsets.all(16), |
| | | decoration: BoxDecoration( |
| | | borderRadius: BorderRadius.only( |
| | | bottomLeft: Radius.circular(15), |
| | | bottomRight: Radius.circular(15)), |
| | | color: Colors.white), |
| | | child: Flex( |
| | | direction: Axis.vertical, |
| | | children: [ |
| | | getPermissionItem( |
| | | title: "手机", |
| | | content: "校验手机识别码,防止账号被盗", |
| | | icon: Image.asset( |
| | | "assets/imgs/common/icon_permission_notify_phone.png", |
| | | width: 21, |
| | | )), |
| | | Container( |
| | | height: 30, |
| | | ), |
| | | getPermissionItem( |
| | | title: "存储", |
| | | content: "缓存图片和视频,降低流量消耗", |
| | | icon: Image.asset( |
| | | "assets/imgs/common/icon_permission_notify_save.png", |
| | | width: 26, |
| | | )), |
| | | Container( |
| | | height: 30, |
| | | ), |
| | | getPermissionItem( |
| | | title: "位置", |
| | | content: "定位用户", |
| | | icon: Image.asset( |
| | | "assets/imgs/common/icon_permission_notify_location.png", |
| | | width: 26, |
| | | )), |
| | | Container( |
| | | height: 36, |
| | | ), |
| | | InkWell( |
| | | onTap: () { |
| | | onOpen(); |
| | | }, |
| | | child: Container( |
| | | height: 44, |
| | | alignment: Alignment.center, |
| | | decoration: BoxDecoration( |
| | | borderRadius: BorderRadius.circular(10), |
| | | color: const Color(0xFF0E96FF)), |
| | | child: const Text( |
| | | "立即开启", |
| | | style: TextStyle( |
| | | fontSize: 18, color: Colors.white), |
| | | ), |
| | | ), |
| | | ) |
| | | ], |
| | | ), |
| | | ), |
| | | ], |
| | | ))))); |
| | | } |
| | | |
| | | //权限项 |
| | | Widget getPermissionItem( |
| | | {required Image icon, required String title, required String content}) { |
| | | return Flex( |
| | | direction: Axis.horizontal, |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | children: [ |
| | | Container( |
| | | width: 34, |
| | | ), |
| | | icon, |
| | | Container( |
| | | width: 4, |
| | | ), |
| | | Expanded( |
| | | child: Flex( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | direction: Axis.vertical, |
| | | children: [ |
| | | Text(title, |
| | | style: TextStyle(fontSize: 17, color: Color(0xFF6B6B6B))), |
| | | Text(content, |
| | | style: TextStyle(fontSize: 12, color: Color(0xFFA0A0A0)), |
| | | softWrap: false, |
| | | overflow: TextOverflow.ellipsis) |
| | | ], |
| | | )) |
| | | ], |
| | | ); |
| | | } |
| | | } |
| | | |
| | | ///ListView弹框 |
| | | class ListViewDialog extends Dialog { |
| | | BuildContext? context; |
| | | final ListView listView; |
| | | final GestureTapCallback onClose; |
| | | final double maxHeight; |
| | | final double? dwidth; |
| | | |
| | | ListViewDialog(this.listView, this.onClose, |
| | | {this.maxHeight = 420, this.dwidth}); |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | this.context = context; |
| | | double swidth = MediaQuery.of(context).size.width; |
| | | |
| | | double dialogWidth = swidth - 20; |
| | | print("屏幕宽:$swidth"); |
| | | if (dwidth != null) { |
| | | dialogWidth = dwidth!; |
| | | } |
| | | //关闭弹框 |
| | | // Navigator.pop(context); |
| | | return WillPopScope( |
| | | onWillPop: () async { |
| | | return false; |
| | | }, |
| | | child: Material( |
| | | type: MaterialType.transparency, |
| | | child: Align( |
| | | alignment: Alignment.center, |
| | | child: Column( |
| | | mainAxisAlignment: MainAxisAlignment.center, |
| | | mainAxisSize: MainAxisSize.min, |
| | | children: [ |
| | | ClipRRect( |
| | | borderRadius: BorderRadius.circular(10), |
| | | child: Container( |
| | | constraints: BoxConstraints(maxHeight: maxHeight), |
| | | color: Colors.white, |
| | | alignment: Alignment.topCenter, |
| | | width: dialogWidth, |
| | | child: listView, |
| | | )), |
| | | Container( |
| | | height: 20, |
| | | ), |
| | | InkWell( |
| | | child: Image.asset( |
| | | "assets/imgs/common/icon_dialog_close.png", |
| | | height: 32, |
| | | width: 32, |
| | | ), |
| | | onTap: () { |
| | | onClose(); |
| | | }, |
| | | ), |
| | | ])))); |
| | | } |
| | | } |
| | | |
| | | ///ListView弹框 |
| | | class LoadingDialog extends Dialog { |
| | | BuildContext? context; |
| | | final String? text; |
| | | |
| | | LoadingDialog(this.text); |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | this.context = context; |
| | | |
| | | //关闭弹框 |
| | | // Navigator.pop(context); |
| | | return WillPopScope( |
| | | onWillPop: () async { |
| | | return false; |
| | | }, |
| | | child: const Material( |
| | | type: MaterialType.transparency, |
| | | child: Align( |
| | | alignment: Alignment.center, |
| | | child: SpinKitCircle( |
| | | color: ColorConstant.theme, |
| | | size: 80.0, |
| | | )))); |
| | | } |
| | | } |
| | | |
| | | class CustomDialog extends Dialog { |
| | | BuildContext? context; |
| | | final Widget contentWidget; |
| | | |
| | | CustomDialog(this.contentWidget); |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | this.context = context; |
| | | //关闭弹框 |
| | | // Navigator.pop(context); |
| | | return WillPopScope( |
| | | onWillPop: () async { |
| | | return false; |
| | | }, |
| | | child: Material( |
| | | type: MaterialType.transparency, |
| | | child: Align( |
| | | alignment: Alignment.center, |
| | | child: Column( |
| | | mainAxisSize: MainAxisSize.min, |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | children: [ |
| | | contentWidget, |
| | | Container( |
| | | height: 20, |
| | | ), |
| | | InkWell( |
| | | onTap: () { |
| | | popPage(context); |
| | | }, |
| | | child: Image.asset( |
| | | "assets/imgs/common/icon_dialog_close.png", |
| | | height: 32, |
| | | width: 32, |
| | | )) |
| | | ], |
| | | )))); |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import '../../utils/ui_utils.dart'; |
| | | import 'package:flutter_boost/flutter_boost.dart'; |
| | | |
| | | void popPage(BuildContext context) { |
| | | if (Navigator.canPop(context)) { |
| | | Navigator.of(context).pop(); |
| | | } else { |
| | | //uiMethodChannel.invokeMethod("popPage", ""); |
| | | print("popPage"); |
| | | BoostNavigator.instance.pop(""); |
| | | } |
| | | } |
| | | |
| | | class TopNavBar extends StatelessWidget { |
| | | final String title; |
| | | GestureTapCallback? back; |
| | | String? rightText; |
| | | String? leftText; |
| | | Icon? rightIcon; |
| | | GestureTapCallback? rightClick; |
| | | final Color textColor; |
| | | final Color backGround; |
| | | final Image backIcon; |
| | | |
| | | TopNavBar( |
| | | {required this.title, |
| | | this.back, |
| | | this.rightText, |
| | | this.rightIcon, |
| | | this.leftText = "", |
| | | this.rightClick, |
| | | this.textColor = const Color(0xFF333333), |
| | | this.backGround = Colors.white, |
| | | this.backIcon = const Image( |
| | | image: AssetImage( |
| | | "assets/imgs/common/icon_back.png", |
| | | ), |
| | | height: 19)}); |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return Flex(direction: Axis.vertical, children: [ |
| | | Container( |
| | | height: MediaQuery.of(context).viewPadding.top, |
| | | color: backGround, |
| | | ), |
| | | Container( |
| | | color: backGround, |
| | | height: 48, |
| | | child: Stack( |
| | | alignment: Alignment.centerLeft, |
| | | children: [ |
| | | Positioned( |
| | | child: Container( |
| | | alignment: Alignment.center, |
| | | child: Flex( |
| | | direction: Axis.horizontal, |
| | | mainAxisAlignment: MainAxisAlignment.center, |
| | | children: [ |
| | | Container( |
| | | width: 50, |
| | | ), |
| | | Expanded( |
| | | child: Center( |
| | | child: Text( |
| | | title, |
| | | maxLines: 1, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: TextStyle(fontSize: 18, color: textColor), |
| | | ))), |
| | | Container( |
| | | width: 50, |
| | | ) |
| | | ], |
| | | ))), |
| | | ((rightText != null && rightText!.isNotEmpty) || rightIcon != null) |
| | | ? Positioned( |
| | | right: 0, |
| | | top: 0, |
| | | bottom: 0, |
| | | child: InkWell( |
| | | onTap: () { |
| | | rightClick!(); |
| | | }, |
| | | child: Container( |
| | | alignment: Alignment.center, |
| | | padding: const EdgeInsets.only(right: 10), |
| | | child: rightIcon ?? |
| | | Text( |
| | | rightText!, |
| | | style: |
| | | TextStyle(fontSize: 15, color: textColor), |
| | | ), |
| | | ))) |
| | | : Container(), |
| | | InkWell( |
| | | onTap: () { |
| | | if (back != null) { |
| | | back!(); |
| | | } else { |
| | | popPage(context); |
| | | } |
| | | }, |
| | | child: Container( |
| | | padding: const EdgeInsets.only(left: 10), |
| | | height: 48, |
| | | child: Row(mainAxisSize: MainAxisSize.min,crossAxisAlignment: CrossAxisAlignment.center, children: [ |
| | | Icon( |
| | | Icons.arrow_back_ios, |
| | | color: textColor, |
| | | ), |
| | | leftText!.isNotEmpty |
| | | ? Text( |
| | | leftText!, |
| | | style: const TextStyle(fontSize: 16), |
| | | ) |
| | | : Container() |
| | | ]), |
| | | )) |
| | | ], |
| | | ), |
| | | ) |
| | | ]); |
| | | } |
| | | } |
New file |
| | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import 'package:pull_to_refresh/pull_to_refresh.dart'; |
| | | |
| | | typedef OnRefresh = void Function(); |
| | | typedef OnLoadMore = void Function(); |
| | | typedef BoolCallback = void Function(bool b); |
| | | typedef GetViewState = RefreshListViewState Function(); |
| | | |
| | | class MyRefreshController extends RefreshController { |
| | | MyRefreshController({ |
| | | bool initialRefresh = true, |
| | | }) : super( |
| | | initialRefresh: initialRefresh, initialLoadStatus: LoadStatus.idle); |
| | | |
| | | //api错误 |
| | | VoidCallback? apiError; |
| | | |
| | | //空数据 |
| | | VoidCallback? dataEmpty; |
| | | |
| | | //正常数据 |
| | | VoidCallback? dataNormal; |
| | | |
| | | //下拉刷新 |
| | | BoolCallback? setPullDownEnable; |
| | | |
| | | //上拉加载 |
| | | BoolCallback? setPullUpEnable; |
| | | |
| | | //编辑模式,编辑模式下不允许下拉刷新与上拉加载 |
| | | BoolCallback? setEditMode; |
| | | |
| | | //界面的状态 |
| | | GetViewState? viewState; |
| | | |
| | | void dispose() { |
| | | apiError = null; |
| | | dataEmpty = null; |
| | | dataNormal=null; |
| | | setPullDownEnable = null; |
| | | setPullUpEnable = null; |
| | | setEditMode = null; |
| | | } |
| | | } |
| | | |
| | | class RefreshListView extends StatefulWidget { |
| | | final MyRefreshController refreshController; |
| | | final Widget content; |
| | | final OnRefresh? refresh; |
| | | final OnLoadMore? loadMore; |
| | | bool? enablePullDown; |
| | | |
| | | bool? enablePullUp; |
| | | |
| | | RefreshListView( |
| | | {required this.refreshController, |
| | | required this.content, |
| | | this.refresh, |
| | | this.loadMore, |
| | | this.enablePullDown = true, |
| | | this.enablePullUp = true}); |
| | | |
| | | @override |
| | | State<StatefulWidget> createState() => _RefreshListViewState(); |
| | | } |
| | | |
| | | class _RefreshListViewState extends State<RefreshListView> { |
| | | bool editMode = false; |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | bindController(); |
| | | } |
| | | |
| | | bindController() { |
| | | widget.refreshController.apiError = _apiError; |
| | | widget.refreshController.dataEmpty = _dataEmpty; |
| | | widget.refreshController.dataNormal =_dataNormal; |
| | | widget.refreshController.setPullDownEnable = _pullDownEnable; |
| | | widget.refreshController.setPullUpEnable = _pullUpEnable; |
| | | widget.refreshController.viewState = () { |
| | | return _viewState; |
| | | }; |
| | | widget.refreshController.setEditMode = (bool enable) { |
| | | setState(() { |
| | | editMode = enable; |
| | | }); |
| | | }; |
| | | |
| | | } |
| | | |
| | | //视图状态 0-正常 1-空视图 2-网络请求错误 |
| | | RefreshListViewState _viewState = RefreshListViewState.normal; |
| | | |
| | | _apiError() { |
| | | setState(() { |
| | | _viewState = RefreshListViewState.error; |
| | | }); |
| | | } |
| | | |
| | | _dataEmpty() { |
| | | setState(() { |
| | | _viewState = RefreshListViewState.empty; |
| | | }); |
| | | } |
| | | |
| | | _dataNormal() { |
| | | setState(() { |
| | | _viewState = RefreshListViewState.normal; |
| | | }); |
| | | } |
| | | |
| | | _pullDownEnable(bool enable) { |
| | | setState(() { |
| | | widget.enablePullDown = enable; |
| | | }); |
| | | } |
| | | |
| | | _pullUpEnable(bool enable) { |
| | | setState(() { |
| | | widget.enablePullUp = enable; |
| | | }); |
| | | } |
| | | |
| | | void _onRefresh() async { |
| | | if (widget.refresh != null) { |
| | | widget.refresh!(); |
| | | } |
| | | } |
| | | |
| | | void _onLoading() async { |
| | | if (widget.loadMore != null) { |
| | | widget.loadMore!(); |
| | | } |
| | | } |
| | | |
| | | Widget getView() { |
| | | switch (_viewState) { |
| | | case RefreshListViewState.normal: |
| | | return contentView(); |
| | | case RefreshListViewState.empty: |
| | | return emptyView(); |
| | | case RefreshListViewState.error: |
| | | return errorView(); |
| | | } |
| | | } |
| | | |
| | | Widget contentView() { |
| | | return editMode |
| | | ? widget.content |
| | | : SmartRefresher( |
| | | enablePullDown: widget.enablePullDown!, |
| | | enablePullUp: widget.enablePullUp!, |
| | | header: const WaterDropHeader( |
| | | complete: Text( |
| | | "刷新完成", |
| | | style: TextStyle(color: Color(0xFFB8AFB5)), |
| | | ), |
| | | ), |
| | | footer: CustomFooter( |
| | | builder: (BuildContext context, LoadStatus? mode) { |
| | | Widget body; |
| | | if (mode == LoadStatus.idle) { |
| | | body = const Text("", |
| | | style: TextStyle(color: Color(0xFFB8AFB5))); |
| | | } else if (mode == LoadStatus.loading) { |
| | | body = const CupertinoActivityIndicator(); |
| | | } else if (mode == LoadStatus.failed) { |
| | | body = const Text("加载失败!点击重试!", |
| | | style: TextStyle(color: Color(0xFFB8AFB5))); |
| | | } else if (mode == LoadStatus.canLoading) { |
| | | body = const Text("松手,加载更多!", |
| | | style: TextStyle(color: Color(0xFFB8AFB5))); |
| | | } else { |
| | | body = const Text("没有更多数据了!", |
| | | style: TextStyle(color: Color(0xFFB8AFB5))); |
| | | } |
| | | return SizedBox( |
| | | height: 55.0, |
| | | child: Center(child: body), |
| | | ); |
| | | }, |
| | | ), |
| | | controller: widget.refreshController, |
| | | onRefresh: _onRefresh, |
| | | onLoading: _onLoading, |
| | | child: widget.content, |
| | | ); |
| | | } |
| | | |
| | | Widget emptyView() { |
| | | return Container( |
| | | width: MediaQuery.of(context).size.width * 2 / 3, |
| | | alignment: Alignment.center, |
| | | // color: Colors.yellow, |
| | | child: Stack( |
| | | alignment: Alignment.bottomCenter, |
| | | children: [ |
| | | Image.asset( |
| | | "assets/imgs/common/ic_empty.png", |
| | | ), |
| | | const Text( |
| | | "暂无数据 ", |
| | | style: TextStyle(color: Color(0xFFF698C9), fontSize: 18), |
| | | ) |
| | | ], |
| | | ), |
| | | ); |
| | | } |
| | | |
| | | Widget errorView() { |
| | | return InkWell( |
| | | onTap: () { |
| | | _onRefresh(); |
| | | }, |
| | | child: Container( |
| | | alignment: Alignment.center, |
| | | // color: Colors.yellow, |
| | | child: Stack( |
| | | alignment: Alignment.bottomCenter, |
| | | children: [ |
| | | Image.asset("assets/imgs/common/ic_network_error.png", |
| | | width: MediaQuery.of(context).size.width * 2 / 5), |
| | | const Text( |
| | | "网络异常,点击重新加载", |
| | | style: TextStyle(color: Color(0xFFF698C9), fontSize: 18), |
| | | ) |
| | | ], |
| | | ), |
| | | )); |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return Stack(alignment: Alignment.center, children: [ |
| | | getView() |
| | | //网络错误页面 |
| | | ]); |
| | | } |
| | | } |
| | | |
| | | enum RefreshListViewState { normal, empty, error } |
New file |
| | |
| | | import 'package:flutter/material.dart'; |
| | | import '../../ui/widget/nav.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | import '../../utils/ui_utils.dart'; |
| | | |
| | | class SearchBar extends StatefulWidget { |
| | | final String? hint; |
| | | final String? text; |
| | | final ValueChanged<String>? onSubmit; |
| | | final ValueChanged<String>? onChange; |
| | | |
| | | final SearchController? searchController; |
| | | |
| | | SearchBar( |
| | | {Key? key, |
| | | this.hint, |
| | | this.text, |
| | | this.onSubmit, |
| | | this.onChange, |
| | | this.searchController}) |
| | | : super(key: key); |
| | | |
| | | @override |
| | | _SearchBarState createState() => _SearchBarState(); |
| | | } |
| | | |
| | | class _SearchBarState extends State<SearchBar> { |
| | | final TextEditingController _searchKeyController = TextEditingController(); |
| | | final FocusNode _focusNode = FocusNode(); |
| | | bool _showClose = false; |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | _focusNode.addListener(() { |
| | | if (!_focusNode.hasFocus) { |
| | | // print('失去焦点'); |
| | | setState(() { |
| | | _showClose = false; |
| | | }); |
| | | } else { |
| | | // print('得到焦点'); |
| | | if (_searchKeyController.text.isNotEmpty) { |
| | | setState(() { |
| | | _showClose = true; |
| | | }); |
| | | } |
| | | } |
| | | }); |
| | | if (widget.text != null) { |
| | | _searchKeyController.text = widget.text!; |
| | | } |
| | | |
| | | if (widget.searchController != null) { |
| | | widget.searchController!.setData = (content) { |
| | | _searchKeyController.text = content; |
| | | _searchKeyController.selection=TextSelection.fromPosition(TextPosition( |
| | | affinity: TextAffinity.downstream, |
| | | offset: content.length)); |
| | | }; |
| | | } |
| | | } |
| | | |
| | | @override |
| | | void dispose() { |
| | | _focusNode.unfocus(); |
| | | super.dispose(); |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return Row( |
| | | children: [ |
| | | Container( |
| | | width: 11, |
| | | ), |
| | | Expanded( |
| | | child: Stack(children: [ |
| | | Container( |
| | | height: 34, |
| | | decoration: BoxDecoration( |
| | | color: Color(0xFFEEEDED), |
| | | borderRadius: BorderRadius.circular(17)), |
| | | child: Row( |
| | | crossAxisAlignment: CrossAxisAlignment.center, |
| | | children: [ |
| | | Container( |
| | | width: 13, |
| | | ), |
| | | Image.asset( |
| | | "assets/imgs/icon_search_home.png", |
| | | height: 17, |
| | | ), |
| | | Expanded( |
| | | child: TextField( |
| | | cursorRadius: const Radius.circular(1), |
| | | cursorColor: ColorConstant.theme, |
| | | maxLines: 1, |
| | | textInputAction: TextInputAction.search, |
| | | style: TextStyle( |
| | | color: Color(0xFF787878), |
| | | ), |
| | | decoration: InputDecoration( |
| | | hintText: widget.hint, |
| | | hintStyle: TextStyle( |
| | | color: Color(0xFF787878), |
| | | ), |
| | | border: InputBorder.none, |
| | | enabledBorder: InputBorder.none, |
| | | disabledBorder: InputBorder.none, |
| | | focusedBorder: InputBorder.none, |
| | | contentPadding: EdgeInsets.fromLTRB(10, 0, 10, 12)), |
| | | controller: _searchKeyController, |
| | | focusNode: _focusNode, |
| | | onSubmitted: (content) { |
| | | if (widget.onSubmit != null) { |
| | | widget.onSubmit!(content); |
| | | } |
| | | }, |
| | | onChanged: (content) { |
| | | if (content.isEmpty) { |
| | | setState(() { |
| | | _showClose = false; |
| | | }); |
| | | } else { |
| | | setState(() { |
| | | _showClose = true; |
| | | }); |
| | | } |
| | | |
| | | if (widget.onChange != null) { |
| | | widget.onChange!(content); |
| | | } |
| | | }, |
| | | )), |
| | | Container( |
| | | width: 13, |
| | | ), |
| | | ], |
| | | ), |
| | | ), |
| | | _showClose |
| | | ? Positioned( |
| | | right: 5, |
| | | top: 0, |
| | | bottom: 0, |
| | | child: InkWell( |
| | | onTap: () { |
| | | _searchKeyController.text = ""; |
| | | setState(() { |
| | | _showClose = false; |
| | | }); |
| | | }, |
| | | child: Icon( |
| | | Icons.highlight_off, |
| | | color: Color(0xFF787878), |
| | | ))) |
| | | : Container() |
| | | ])), |
| | | InkWell( |
| | | onTap: () { |
| | | popPage(context); |
| | | }, |
| | | child: Container( |
| | | height: 34, |
| | | alignment: Alignment.center, |
| | | padding: const EdgeInsets.only(left: 20, right: 20), |
| | | child: const Text( |
| | | "取消", |
| | | style: TextStyle(color: Color(0xFFFF558D)), |
| | | )), |
| | | ) |
| | | ], |
| | | ); |
| | | } |
| | | } |
| | | |
| | | class SugguestSearchView extends StatefulWidget { |
| | | final ValueChanged<String>? onCancel; |
| | | |
| | | final ValueChanged<String>? onItemClick; |
| | | |
| | | final List<String> contentList; |
| | | |
| | | final SugguestSearchController? sugguestSearchController; |
| | | |
| | | SugguestSearchView( |
| | | {Key? key, |
| | | this.onCancel, |
| | | this.onItemClick, |
| | | required this.contentList, |
| | | this.sugguestSearchController}) |
| | | : super(key: key); |
| | | |
| | | @override |
| | | _SugguestSearchViewState createState() => _SugguestSearchViewState(); |
| | | } |
| | | |
| | | class _SugguestSearchViewState extends State<SugguestSearchView> { |
| | | List<String> contentList = []; |
| | | bool _show = false; |
| | | |
| | | @override |
| | | void initState() { |
| | | super.initState(); |
| | | contentList.addAll(widget.contentList); |
| | | if (contentList.isNotEmpty) { |
| | | setState(() { |
| | | _show = true; |
| | | }); |
| | | } else { |
| | | setState(() { |
| | | _show = false; |
| | | }); |
| | | } |
| | | bindController(); |
| | | } |
| | | |
| | | void bindController() { |
| | | if (widget.sugguestSearchController == null) { |
| | | return; |
| | | } |
| | | widget.sugguestSearchController!.setData = (list) { |
| | | setState(() { |
| | | contentList = list; |
| | | }); |
| | | }; |
| | | widget.sugguestSearchController!.setShow = (show) { |
| | | setState(() { |
| | | _show = show; |
| | | }); |
| | | }; |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return _show |
| | | ? Positioned( |
| | | top: MediaQuery.of(context).viewPadding.top + 39, |
| | | child: InkWell( |
| | | onTap: () { |
| | | if (widget.onCancel != null) { |
| | | setState(() { |
| | | _show = false; |
| | | }); |
| | | widget.onCancel!(""); |
| | | } |
| | | }, |
| | | child: Container( |
| | | color: Colors.transparent, |
| | | width: MediaQuery.of(context).size.width, |
| | | height: MediaQuery.of(context).size.height, |
| | | padding: const EdgeInsets.fromLTRB(50, 0, 80, 0), |
| | | margin: EdgeInsets.all(0), |
| | | child: Column(children: [ |
| | | Container( |
| | | height: 40 * contentList.length + |
| | | (contentList.length - 1) * |
| | | DimenUtil.getOnePixel(context), |
| | | decoration: const BoxDecoration( |
| | | color: Color(0xFF999999), |
| | | boxShadow: [ |
| | | BoxShadow( |
| | | color: Color(0x30000000), |
| | | blurRadius: 10, |
| | | offset: Offset(0, 3)) |
| | | ]), |
| | | child: ListView.builder( |
| | | padding: EdgeInsets.all(0), |
| | | itemBuilder: (BuildContext context, int index) { |
| | | return InkWell( |
| | | onTap: () { |
| | | if (widget.onItemClick != null) { |
| | | widget.onItemClick!(contentList[index]); |
| | | } |
| | | }, |
| | | child: getSuggestSearchItem(contentList[index])); |
| | | }, |
| | | itemCount: contentList.length, |
| | | )), |
| | | Expanded(child: Container()) |
| | | ]), |
| | | ), |
| | | )) |
| | | : Container(); |
| | | } |
| | | |
| | | Widget getSuggestSearchItem(String text) { |
| | | return Container( |
| | | height: 40, |
| | | alignment: Alignment.centerLeft, |
| | | margin: EdgeInsets.only(bottom: DimenUtil.getOnePixel(context)), |
| | | color: Colors.white, |
| | | padding: EdgeInsets.fromLTRB(20, 0, 20, 0), |
| | | child: Text( |
| | | text, |
| | | maxLines: 1, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: TextStyle(fontSize: 12, color: const Color(0xFF4A4A4A)), |
| | | ), |
| | | ); |
| | | } |
| | | } |
| | | |
| | | class SugguestSearchController { |
| | | ValueChanged<List<String>>? setData; |
| | | |
| | | ValueChanged<bool>? setShow; |
| | | } |
| | | |
| | | class SearchController { |
| | | ValueChanged<String>? setData; |
| | | } |
New file |
| | |
| | | import 'dart:math'; |
| | | import 'dart:ui' as ui; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | |
| | | ///SOS雷达扫描View |
| | | class RadarView extends StatefulWidget { |
| | | @override |
| | | _RadarViewState createState() => _RadarViewState(); |
| | | } |
| | | |
| | | class _RadarViewState extends State<RadarView> |
| | | with SingleTickerProviderStateMixin { |
| | | AnimationController? _controller; |
| | | Animation<double>? _animation; |
| | | |
| | | @override |
| | | void initState() { |
| | | _controller = |
| | | AnimationController(vsync: this, duration: Duration(seconds: 2)); |
| | | _animation = Tween(begin: .0, end: pi * 2).animate(_controller!); |
| | | _controller!.repeat(); |
| | | super.initState(); |
| | | } |
| | | |
| | | @override |
| | | void dispose() { |
| | | _controller!.dispose(); |
| | | super.dispose(); |
| | | } |
| | | |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | return AnimatedBuilder( |
| | | animation: _animation!, |
| | | builder: (context, child) { |
| | | return CustomPaint( |
| | | painter: RadarPainter(_animation!.value), |
| | | ); |
| | | }, |
| | | ); |
| | | } |
| | | } |
| | | |
| | | ///SOS雷达扫描动画 |
| | | class RadarPainter extends CustomPainter { |
| | | final double angle; |
| | | |
| | | final Paint _bgPaint = Paint() |
| | | ..color = Colors.white |
| | | ..strokeWidth = 1 |
| | | ..style = PaintingStyle.stroke; |
| | | |
| | | final Paint _paint = Paint()..style = PaintingStyle.fill; |
| | | |
| | | int circleCount = 0; |
| | | |
| | | RadarPainter(this.angle); |
| | | |
| | | @override |
| | | void paint(Canvas canvas, Size size) { |
| | | var radius = min(size.width / 2, size.height / 2); |
| | | |
| | | // canvas.drawLine(Offset(size.width / 2, size.height / 2 - radius), |
| | | // Offset(size.width / 2, size.height / 2 + radius), _bgPaint); |
| | | // canvas.drawLine(Offset(size.width / 2 - radius, size.height / 2), |
| | | // Offset(size.width / 2 + radius, size.height / 2), _bgPaint); |
| | | |
| | | for (var i = 1; i <= circleCount; ++i) { |
| | | canvas.drawCircle(Offset(size.width / 2, size.height / 2), |
| | | radius * i / circleCount, _bgPaint); |
| | | } |
| | | |
| | | _paint.shader = ui.Gradient.sweep( |
| | | Offset(size.width / 2, size.height / 2), |
| | | [Colors.white.withOpacity(.01), Colors.yellow.withOpacity(.6)], |
| | | [.0, 1.0], |
| | | TileMode.clamp, |
| | | .0, |
| | | pi / 4); |
| | | |
| | | canvas.save(); |
| | | double r = sqrt(pow(size.width, 2) + pow(size.height, 2)); |
| | | double startAngle = atan(size.height / size.width); |
| | | Point p0 = Point(r * cos(startAngle), r * sin(startAngle)); |
| | | Point px = Point(r * cos(angle + startAngle), r * sin(angle + startAngle)); |
| | | canvas.translate((p0.x - px.x) / 2, (p0.y - px.y) / 2); |
| | | canvas.rotate(angle); |
| | | |
| | | canvas.drawArc( |
| | | Rect.fromCircle( |
| | | center: Offset(size.width / 2, size.height / 2), radius: radius), |
| | | 0, |
| | | pi / 4, |
| | | true, |
| | | _paint); |
| | | canvas.restore(); |
| | | } |
| | | |
| | | @override |
| | | bool shouldRepaint(CustomPainter oldDelegate) { |
| | | return true; |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:convert'; |
| | | import 'dart:io'; |
| | | |
| | | import 'package:flutter/material.dart'; |
| | | import '../../model/video/home_type_model.dart'; |
| | | import '../../model/video/video_model.dart'; |
| | | import '../../ui/video/video_detail.dart'; |
| | | import '../../utils/jump_page.dart'; |
| | | import '../../utils/pageutils.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | import '../../utils/ui_utils.dart'; |
| | | import 'package:cached_network_image/cached_network_image.dart'; |
| | | import '../../utils/video/video_util.dart'; |
| | | |
| | | import '../../main.dart'; |
| | | |
| | | class VideoListUIUtil { |
| | | static Widget getHomeTypeItem( |
| | | double width, HomeTypeModel homeType, BuildContext context) { |
| | | double padding = 8; |
| | | width = width - padding * 2; |
| | | Widget videoList = Container(); |
| | | |
| | | List<Widget> list = |
| | | List.filled(homeType.homeVideoList!.length, Container()); |
| | | //填充大图 |
| | | for (var i = 0; i < homeType.homeVideoList!.length; i++) { |
| | | HomeVideoModel homeVideo = homeType.homeVideoList![i]; |
| | | if (homeVideo.bigPicture!) { |
| | | list[i] = getVideoItemColumn_1(width, homeVideo, context, native: Platform.isIOS); |
| | | } else { |
| | | switch (homeType.columns) { |
| | | case 1: |
| | | list[i] = |
| | | getVideoItemColumn_1(width, homeVideo, context, native: Platform.isIOS); |
| | | break; |
| | | case 2: |
| | | list[i] = |
| | | getVideoItemColumn_2(width, homeVideo, context, native: Platform.isIOS); |
| | | break; |
| | | case 3: |
| | | list[i] = |
| | | getVideoItemColumn_3(width, homeVideo, context, native: Platform.isIOS); |
| | | break; |
| | | default: |
| | | list[i] = |
| | | getVideoItemColumn_2(width, homeVideo, context, native: Platform.isIOS); |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (homeType.columns! <= 3) { |
| | | videoList = Wrap( |
| | | alignment: WrapAlignment.spaceBetween, |
| | | runSpacing: 10, |
| | | spacing: 7, |
| | | children: list, |
| | | ); |
| | | } else { |
| | | videoList = ListView.separated( |
| | | shrinkWrap: true, |
| | | scrollDirection: Axis.horizontal, |
| | | itemBuilder: (BuildContext context, int index) { |
| | | return getVideoItemColumn_n( |
| | | width, homeType.homeVideoList![index], context); |
| | | }, |
| | | itemCount: homeType.homeVideoList!.length, |
| | | separatorBuilder: (BuildContext context, int index) { |
| | | return Container( |
| | | width: 10, |
| | | ); |
| | | }, |
| | | ); |
| | | } |
| | | |
| | | return Container( |
| | | padding: const EdgeInsets.all(8), |
| | | child: Column( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | Container( |
| | | padding: const EdgeInsets.only(top: 0, bottom: 10), |
| | | child: Row(children: [ |
| | | Text( |
| | | homeType.name!, |
| | | style: |
| | | const TextStyle(color: Color(0xFF5F5F5F), fontSize: 16), |
| | | ) |
| | | ])), |
| | | videoList, |
| | | ], |
| | | ), |
| | | ); |
| | | } |
| | | |
| | | static Widget getVideoItemColumn_1( |
| | | double mx, HomeVideoModel homeVideo, BuildContext context, |
| | | {bool native = false}) { |
| | | return InkWell( |
| | | onTap: () { |
| | | jumpVideoDetail(context, homeVideo.video, native); |
| | | }, |
| | | child: Stack( |
| | | alignment: Alignment.bottomLeft, |
| | | children: [ |
| | | ClipRRect( |
| | | borderRadius: BorderRadius.circular(12.5), |
| | | child: VideoImage( |
| | | (homeVideo.picture != null && homeVideo.picture!.isNotEmpty) |
| | | ? homeVideo.picture! |
| | | : ((homeVideo.video!.hpicture != null && |
| | | homeVideo.video!.hpicture!.isNotEmpty) |
| | | ? homeVideo.video!.hpicture |
| | | : homeVideo.video!.picture), |
| | | width: mx, |
| | | height: mx * 0.4382, |
| | | ), |
| | | ), |
| | | Container( |
| | | padding: const EdgeInsets.fromLTRB(5, 5, 5, 5), |
| | | decoration: const BoxDecoration( |
| | | borderRadius: BorderRadius.only( |
| | | bottomLeft: Radius.circular(12.5), |
| | | bottomRight: Radius.circular(12.5)), |
| | | gradient: LinearGradient( |
| | | begin: Alignment.topCenter, |
| | | end: Alignment.bottomCenter, |
| | | colors: [ |
| | | Color(0x00000000), |
| | | Color(0x80000000), |
| | | ], |
| | | ), |
| | | ), |
| | | child: Row( |
| | | children: [ |
| | | Text( |
| | | homeVideo.video!.tag!, |
| | | style: _getTagStyle(), |
| | | ), |
| | | Expanded( |
| | | child: Container(), |
| | | ), |
| | | _getScoreText("9") |
| | | ], |
| | | ), |
| | | ) |
| | | ], |
| | | )); |
| | | } |
| | | |
| | | static Widget getVideoItemColumn_2( |
| | | double mx, HomeVideoModel homeVideo, BuildContext context, |
| | | {bool native = false}) { |
| | | return InkWell( |
| | | onTap: () { |
| | | jumpVideoDetail(context, homeVideo.video, native); |
| | | }, |
| | | child: Container( |
| | | width: (mx - 7.5) / 2, |
| | | child: Column( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | Stack( |
| | | alignment: Alignment.bottomLeft, |
| | | children: [ |
| | | ClipRRect( |
| | | borderRadius: BorderRadius.circular(12.5), |
| | | child: VideoImage( |
| | | (homeVideo.picture != null && |
| | | homeVideo.picture!.isNotEmpty) |
| | | ? homeVideo.picture! |
| | | : ((homeVideo.video!.hpicture != null && |
| | | homeVideo.video!.hpicture!.isNotEmpty) |
| | | ? homeVideo.video!.hpicture |
| | | : homeVideo.video!.picture), |
| | | width: (mx - 7.5) / 2, |
| | | height: ((mx - 7.5) / 2) * 0.5629, |
| | | fit: BoxFit.cover, |
| | | ), |
| | | ), |
| | | Container( |
| | | padding: EdgeInsets.fromLTRB(5, 5, 5, 5), |
| | | decoration: const BoxDecoration( |
| | | borderRadius: BorderRadius.only( |
| | | bottomLeft: Radius.circular(12.5), |
| | | bottomRight: Radius.circular(12.5)), |
| | | gradient: LinearGradient( |
| | | begin: Alignment.topCenter, |
| | | end: Alignment.bottomCenter, |
| | | colors: [ |
| | | Color(0x00000000), |
| | | Color(0x80000000), |
| | | ], |
| | | ), |
| | | ), |
| | | child: Row( |
| | | children: [ |
| | | Text( |
| | | homeVideo.video!.tag != null |
| | | ? homeVideo.video!.tag! |
| | | : "", |
| | | style: _getTagStyle()), |
| | | Expanded( |
| | | child: Container(), |
| | | ), |
| | | _getScoreText("9.3") |
| | | ], |
| | | ), |
| | | ) |
| | | ], |
| | | ), |
| | | Text( |
| | | homeVideo.video!.name!, |
| | | maxLines: 2, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: _getTitleStyle(), |
| | | ), |
| | | ], |
| | | ), |
| | | )); |
| | | } |
| | | |
| | | static Widget getVideoItemColumn_3( |
| | | double mx, HomeVideoModel homeVideo, BuildContext context, |
| | | {bool native = false}) { |
| | | return InkWell( |
| | | onTap: () { |
| | | jumpVideoDetail(context, homeVideo.video, native); |
| | | }, |
| | | child: Container( |
| | | width: (mx - 8 * 2) / 3, |
| | | child: Column( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | Stack( |
| | | alignment: Alignment.bottomLeft, |
| | | children: [ |
| | | ClipRRect( |
| | | borderRadius: BorderRadius.circular(12.5), |
| | | child: VideoImage( |
| | | (homeVideo.picture != null && |
| | | homeVideo.picture!.isNotEmpty) |
| | | ? homeVideo.picture! |
| | | : ((homeVideo.video!.vpicture != null && |
| | | homeVideo.video!.vpicture!.isNotEmpty) |
| | | ? homeVideo.video!.vpicture |
| | | : homeVideo.video!.picture), |
| | | width: (mx - 8) / 3, |
| | | height: ((mx - 8) / 3) * 1.4, |
| | | ), |
| | | ), |
| | | Container( |
| | | padding: const EdgeInsets.fromLTRB(5, 5, 5, 5), |
| | | decoration: const BoxDecoration( |
| | | borderRadius: BorderRadius.only( |
| | | bottomLeft: Radius.circular(12.5), |
| | | bottomRight: Radius.circular(12.5)), |
| | | gradient: LinearGradient( |
| | | begin: Alignment.topCenter, |
| | | end: Alignment.bottomCenter, |
| | | colors: [ |
| | | Color(0x00000000), |
| | | Color(0x80000000), |
| | | ], |
| | | ), |
| | | ), |
| | | child: Row( |
| | | children: [ |
| | | Expanded( |
| | | child: Text(homeVideo.video!.tag!, |
| | | maxLines: 1, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: _getTagStyle()), |
| | | ), |
| | | _getScoreText(homeVideo.video!.score) |
| | | ], |
| | | ), |
| | | ) |
| | | ], |
| | | ), |
| | | Text( |
| | | homeVideo.video!.name!, |
| | | maxLines: 2, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: _getTitleStyle(), |
| | | ) |
| | | ], |
| | | ), |
| | | )); |
| | | } |
| | | |
| | | static Widget getVideoItemColumn_n( |
| | | double mx, HomeVideoModel homeVideo, BuildContext context, |
| | | {bool native = false}) { |
| | | return InkWell( |
| | | onTap: () { |
| | | jumpVideoDetail(context, homeVideo.video, native); |
| | | }, |
| | | child: Container( |
| | | width: (mx - 7.5) / 2.5, |
| | | child: Column( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | Stack( |
| | | alignment: Alignment.bottomLeft, |
| | | children: [ |
| | | ClipRRect( |
| | | borderRadius: BorderRadius.circular(12.5), |
| | | child: VideoImage( |
| | | (homeVideo.picture != null && |
| | | homeVideo.picture!.isNotEmpty) |
| | | ? homeVideo.picture! |
| | | : ((homeVideo.video!.hpicture != null && |
| | | homeVideo.video!.hpicture!.isNotEmpty) |
| | | ? homeVideo.video!.hpicture |
| | | : homeVideo.video!.picture), |
| | | width: (mx - 7.5) / 2.5, |
| | | height: ((mx - 7.5) / 2.5) * 0.5629, |
| | | ), |
| | | ), |
| | | Container( |
| | | padding: EdgeInsets.fromLTRB(5, 5, 5, 5), |
| | | decoration: const BoxDecoration( |
| | | borderRadius: BorderRadius.only( |
| | | bottomLeft: Radius.circular(12.5), |
| | | bottomRight: Radius.circular(12.5)), |
| | | gradient: LinearGradient( |
| | | begin: Alignment.topCenter, |
| | | end: Alignment.bottomCenter, |
| | | colors: [ |
| | | Color(0x00000000), |
| | | Color(0x80000000), |
| | | ], |
| | | ), |
| | | ), |
| | | child: Row( |
| | | children: [ |
| | | Text(homeVideo.video!.tag!, style: _getTagStyle()), |
| | | Expanded( |
| | | child: Container(), |
| | | ), |
| | | _getScoreText(homeVideo.video!.score) |
| | | ], |
| | | ), |
| | | ) |
| | | ], |
| | | ), |
| | | Text( |
| | | homeVideo.video!.name!, |
| | | maxLines: 2, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: _getTitleStyle(), |
| | | ), |
| | | ], |
| | | ), |
| | | )); |
| | | } |
| | | |
| | | static Widget getSearchVideoAlbum( |
| | | BuildContext context, VideoInfoModel videoInfo) { |
| | | const double paddingLeft = 10; |
| | | const double paddingRight = 10; |
| | | double episodeWidth = (MediaQuery.of(context).size.width - |
| | | paddingRight - |
| | | paddingLeft - |
| | | 10 * 4 - |
| | | 1) / |
| | | 5; |
| | | |
| | | double episodeHeight = episodeWidth * 0.656; |
| | | |
| | | return Container( |
| | | alignment: Alignment.topLeft, |
| | | padding: const EdgeInsets.fromLTRB( |
| | | paddingLeft, |
| | | 0, |
| | | paddingRight, |
| | | 15, |
| | | ), |
| | | child: Column( |
| | | children: [ |
| | | SizedBox( |
| | | height: 154, |
| | | child: Row( |
| | | children: [ |
| | | //封面 |
| | | ClipRRect( |
| | | borderRadius: BorderRadius.circular(6), |
| | | child: VideoImage( |
| | | videoInfo.vpicture != null && |
| | | videoInfo.vpicture!.isNotEmpty |
| | | ? videoInfo.vpicture |
| | | : videoInfo.picture, |
| | | width: 112, |
| | | height: 154), |
| | | ), |
| | | Container( |
| | | width: 14, |
| | | ), |
| | | Expanded( |
| | | child: Column( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | Row( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | Expanded( |
| | | child: Text( |
| | | videoInfo.name!, |
| | | maxLines: 2, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: const TextStyle( |
| | | color: Color(0xFF232323), fontSize: 16), |
| | | )), |
| | | Container( |
| | | width: 10, |
| | | ), |
| | | _getAlbumScoreText(videoInfo.score) |
| | | ]), |
| | | Container( |
| | | height: 8, |
| | | ), |
| | | Text( |
| | | videoInfo.tag!, |
| | | maxLines: 1, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: const TextStyle( |
| | | color: Color(0xFFAEAEAE), fontSize: 12), |
| | | ), |
| | | Container( |
| | | height: 10, |
| | | ), |
| | | Text( |
| | | videoInfo.mainActor ?? "", |
| | | maxLines: 2, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: const TextStyle( |
| | | color: Color(0xFFAEAEAE), fontSize: 12), |
| | | ), |
| | | Expanded(child: Container()), |
| | | Container( |
| | | alignment: Alignment.centerRight, |
| | | child: InkWell( |
| | | onTap: () { |
| | | jumpVideoDetail(context, videoInfo, false); |
| | | }, |
| | | child: Container( |
| | | width: 127, |
| | | height: 30, |
| | | alignment: Alignment.center, |
| | | decoration: BoxDecoration( |
| | | borderRadius: BorderRadius.circular(8), |
| | | color: ColorConstant.theme), |
| | | child: const Text( |
| | | "立即观看", |
| | | style: TextStyle( |
| | | color: Colors.white, fontSize: 14), |
| | | ), |
| | | ))) |
| | | ], |
| | | )) |
| | | ], |
| | | )), |
| | | //剧集 |
| | | videoInfo.videoDetailList!.isNotEmpty |
| | | ? Container( |
| | | alignment: Alignment.centerLeft, |
| | | margin: const EdgeInsets.only(top: 16), |
| | | child: Wrap( |
| | | alignment: WrapAlignment.start, |
| | | spacing: 10, |
| | | children: videoInfo.videoDetailList! |
| | | .map((e) => getTVEpisodeItem( |
| | | e.tag, episodeWidth, episodeHeight, context, |
| | | onClick: () { |
| | | jumpVideoDetail(context, videoInfo, false, |
| | | position: int.tryParse(e.tag!) != null |
| | | ? int.parse(e.tag!) |
| | | : 0); |
| | | })) |
| | | .toList())) |
| | | : Container() |
| | | ], |
| | | ), |
| | | ); |
| | | } |
| | | |
| | | static Widget getSearchVideoCommon( |
| | | BuildContext context, VideoInfoModel videoInfo) { |
| | | const double paddingLeft = 10; |
| | | const double paddingRight = 10; |
| | | return InkWell( |
| | | onTap: () { |
| | | jumpVideoDetail(context, videoInfo, false); |
| | | }, |
| | | child: Container( |
| | | alignment: Alignment.topLeft, |
| | | padding: const EdgeInsets.fromLTRB(paddingLeft, 20, paddingRight, 0), |
| | | child: Column( |
| | | children: [ |
| | | SizedBox( |
| | | height: 76, |
| | | child: Row( |
| | | children: [ |
| | | //封面 |
| | | ClipRRect( |
| | | borderRadius: BorderRadius.circular(6), |
| | | child: VideoImage( |
| | | videoInfo.picture, |
| | | width: 136, |
| | | ), |
| | | ), |
| | | Container( |
| | | width: 14, |
| | | ), |
| | | Expanded( |
| | | child: Column( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | Text( |
| | | videoInfo.name!, |
| | | maxLines: 2, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: TextStyle( |
| | | color: Color(0xFF232323), fontSize: 16), |
| | | ), |
| | | Expanded(child: Container()), |
| | | Container( |
| | | alignment: Alignment.centerRight, |
| | | child: Text( |
| | | videoInfo.duration ?? "", |
| | | maxLines: 1, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: const TextStyle( |
| | | color: Color(0xFFB8AFB5), fontSize: 12), |
| | | )) |
| | | ], |
| | | )) |
| | | ], |
| | | )), |
| | | ], |
| | | ), |
| | | )); |
| | | } |
| | | |
| | | static Widget getRecommendVideo( |
| | | BuildContext context, VideoInfoModel videoInfo) { |
| | | return Container( |
| | | alignment: Alignment.topLeft, |
| | | padding: const EdgeInsets.fromLTRB(0, 0, 0, 0), |
| | | child: Column( |
| | | children: [ |
| | | Container( |
| | | height: 76, |
| | | child: Row( |
| | | children: [ |
| | | //封面 |
| | | ClipRRect( |
| | | borderRadius: BorderRadius.circular(6), |
| | | child: VideoImage( |
| | | VideoUtil.getHPicture(videoInfo), |
| | | width: 136, |
| | | ), |
| | | ), |
| | | Container( |
| | | width: 14, |
| | | ), |
| | | Expanded( |
| | | child: Column( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | Row( |
| | | crossAxisAlignment: CrossAxisAlignment.start, |
| | | children: [ |
| | | Expanded( |
| | | child: Text( |
| | | videoInfo.name!, |
| | | maxLines: 1, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: const TextStyle( |
| | | color: Color(0xFF232323), fontSize: 16), |
| | | )), |
| | | Container( |
| | | width: 10, |
| | | ), |
| | | _getAlbumScoreText(videoInfo.score) |
| | | ]), |
| | | Text( |
| | | videoInfo.tag ?? "", |
| | | maxLines: 1, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: const TextStyle( |
| | | color: Color(0xFFAEAEAE), fontSize: 12), |
| | | ), |
| | | Expanded(child: Container()), |
| | | const Text( |
| | | "", |
| | | maxLines: 2, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: |
| | | TextStyle(color: Color(0xFFAEAEAE), fontSize: 12), |
| | | ), |
| | | ], |
| | | )) |
| | | ], |
| | | )), |
| | | ], |
| | | ), |
| | | ); |
| | | } |
| | | |
| | | static TextStyle _getTagStyle() { |
| | | return TextStyle(color: Colors.white, fontSize: 12); |
| | | } |
| | | |
| | | static TextStyle _getTitleStyle() { |
| | | return TextStyle(color: Color(0xFF464646), fontSize: 14); |
| | | } |
| | | |
| | | static TextStyle _getSubTitleStyle() { |
| | | return TextStyle(color: Color(0xFFB3ADB3), fontSize: 12); |
| | | } |
| | | |
| | | static Text _getScoreText(String? score) { |
| | | if (score == null || score.isEmpty) { |
| | | return const Text(""); |
| | | } |
| | | List<TextSpan> spanList = []; |
| | | spanList.add(TextSpan( |
| | | text: score.split(".")[0], |
| | | style: const TextStyle(fontSize: 13, color: Color(0xFFFB9F00)))); |
| | | if (score.split(".").length > 1) { |
| | | spanList.add(TextSpan( |
| | | text: ".${score.split(".")[1]}", |
| | | style: const TextStyle(fontSize: 9, color: Color(0xFFFB9F00)))); |
| | | } |
| | | spanList.add(const TextSpan( |
| | | text: "分", style: TextStyle(fontSize: 8, color: Color(0xAAFB9F00)))); |
| | | return Text.rich(TextSpan(children: spanList)); |
| | | } |
| | | |
| | | static Text _getAlbumScoreText(String? score) { |
| | | if (score == null || score.isEmpty) { |
| | | return Text(""); |
| | | } |
| | | List<TextSpan> spanList = []; |
| | | spanList.add(TextSpan( |
| | | text: score, style: TextStyle(fontSize: 15, color: Color(0xFFFF9C00)))); |
| | | |
| | | spanList.add(const TextSpan( |
| | | text: "分", style: TextStyle(fontSize: 12, color: Color(0xFFB8B8B8)))); |
| | | return Text.rich(TextSpan(children: spanList)); |
| | | } |
| | | |
| | | static Widget getTVEpisodeItem( |
| | | text, double width, double height, BuildContext context, |
| | | {checked: false, VoidCallback? onClick}) { |
| | | return InkWell( |
| | | onTap: () { |
| | | if (onClick != null) { |
| | | onClick(); |
| | | } |
| | | }, |
| | | child: Container( |
| | | width: width, |
| | | alignment: Alignment.center, |
| | | height: height, |
| | | decoration: BoxDecoration( |
| | | color: checked ? ColorConstant.theme : Colors.white, |
| | | borderRadius: BorderRadius.circular(10), |
| | | border: Border.all( |
| | | width: checked ? 0 : DimenUtil.getOnePixel(context), |
| | | color: const Color(0xFFBBBBBB))), |
| | | child: Text(text, |
| | | style: TextStyle( |
| | | color: checked ? Colors.white : Color(0xFF232323), |
| | | fontSize: 16)), |
| | | )); |
| | | } |
| | | |
| | | static Widget getShowEpisodeItem(text, BuildContext context, |
| | | {checked: false, VoidCallback? onClick}) { |
| | | return InkWell( |
| | | onTap: () { |
| | | if (onClick != null) { |
| | | onClick(); |
| | | } |
| | | }, |
| | | child: Container( |
| | | padding: const EdgeInsets.all(10), |
| | | width: 200, |
| | | alignment: Alignment.topLeft, |
| | | decoration: BoxDecoration( |
| | | color: checked ? ColorConstant.theme : Colors.white, |
| | | borderRadius: BorderRadius.circular(10), |
| | | border: Border.all( |
| | | width: checked ? 0 : DimenUtil.getOnePixel(context), |
| | | color: const Color(0xFFBBBBBB))), |
| | | child: Text(text, |
| | | maxLines: 3, |
| | | overflow: TextOverflow.ellipsis, |
| | | style: TextStyle( |
| | | color: checked ? Colors.white : const Color(0xFF232323), |
| | | fontSize: 16)), |
| | | )); |
| | | } |
| | | } |
| | | |
| | | Widget VideoImage(url, {fit = BoxFit.cover, double? width, double? height}) { |
| | | return CachedNetworkImage( |
| | | imageUrl: url, |
| | | fit: fit, |
| | | width: width, |
| | | height: height, |
| | | placeholder: (context, st) => Container( |
| | | color: const Color(0xFFFFE8F0), |
| | | ), |
| | | errorWidget: (context, url, error) => const Icon(Icons.error), |
| | | ); |
| | | } |
| | | |
| | | Widget CommonImage(String url, |
| | | {fit = BoxFit.cover, |
| | | double? width, |
| | | double? height, |
| | | Widget? defaultWidget}) { |
| | | if (url.startsWith("http")) { |
| | | return CachedNetworkImage( |
| | | imageUrl: url, |
| | | fit: fit, |
| | | width: width, |
| | | height: height, |
| | | placeholder: (context, st) => |
| | | defaultWidget ?? |
| | | Container( |
| | | color: const Color(0xFFFFE8F0), |
| | | ), |
| | | errorWidget: (context, url, error) => |
| | | defaultWidget ?? const Icon(Icons.error), |
| | | ); |
| | | } else { |
| | | return Image.asset( |
| | | url, |
| | | width: width, |
| | | height: height, |
| | | fit: fit, |
| | | ); |
| | | } |
| | | } |
| | | |
| | | void jumpVideoDetail(BuildContext context, VideoInfoModel? video, bool native, |
| | | {int position = 0}) { |
| | | JumpPageUtil.jumpPage("VideoDetailPage", context, |
| | | params: {"position": position, "video": video!.toJson()}, native: native); |
| | | } |
New file |
| | |
| | | import 'dart:convert'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/services.dart'; |
| | | import '../../utils/share_preference.dart'; |
| | | import '../../utils/string_util.dart'; |
| | | |
| | | MethodChannel adMethodChannel = const MethodChannel('com.yeshi.video/ad'); |
| | | |
| | | enum AdType { csj, gdt } |
| | | |
| | | enum AdPosition { |
| | | //推荐 |
| | | other, |
| | | //搜索 |
| | | videoSearch, |
| | | //全屏视频 |
| | | videoDetailFullVideo, |
| | | videoPlayPre |
| | | } |
| | | |
| | | class CSJADConstant { |
| | | //推荐首页大图 |
| | | static const String PID_RECOMMEND_BIG_PICTURE = "948067685"; |
| | | //搜索页广告 |
| | | static const String PID_VIDEO_SEARCH = "948119448"; |
| | | //全屏视频广告 |
| | | static const String PID_VIDEO_DETAIL_FULLSCREEN = "948119553"; |
| | | } |
| | | |
| | | class GDTADConstant { |
| | | //推荐首页大图 |
| | | static const String PID_RECOMMEND_BIG_PICTURE = "5033406516973331"; |
| | | |
| | | //搜索页广告 |
| | | static const String PID_VIDEO_SEARCH = "4033805974201049"; |
| | | |
| | | //全屏视频广告 |
| | | static const String PID_VIDEO_DETAIL_FULLSCREEN = "6053800914701167"; |
| | | } |
| | | |
| | | class AdUtil { |
| | | static void loadFullScreenAd(AdType adType, String pid) { |
| | | if (adType == AdType.csj) { |
| | | adMethodChannel.invokeMethod("loadCSJFullScreenAd", {"pid": pid}); |
| | | } else { |
| | | adMethodChannel.invokeMethod("loadGDTFullScreenAd", {"pid": pid}); |
| | | } |
| | | } |
| | | |
| | | //获取广告配置信息 |
| | | static Future<Map<String, dynamic>?> _getAdConfig() async { |
| | | String result = await dataMethodChannel.invokeMethod("getAdConfig"); |
| | | print("广告配置:$result"); |
| | | if (StringUtil.isNullOrEmpty(result)) { |
| | | return null; |
| | | } |
| | | return jsonDecode(result); |
| | | } |
| | | |
| | | static Future<AdType?> getAdType(AdPosition position) async { |
| | | String key = ""; |
| | | if (position == AdPosition.other) { |
| | | key = "other"; |
| | | } else if (position == AdPosition.videoSearch) { |
| | | key = "videoSearch"; |
| | | } else if (position == AdPosition.videoDetailFullVideo) { |
| | | key = "videoDetailFullVideo"; |
| | | } else if (position == AdPosition.videoPlayPre) { |
| | | key = "videoPlayPre"; |
| | | } |
| | | |
| | | Map<String, dynamic>? config = await _getAdConfig(); |
| | | if (config == null) { |
| | | return null; |
| | | } |
| | | |
| | | print("广告key:$key 类型${config[key]}"); |
| | | if (config[key] == null) { |
| | | return null; |
| | | } |
| | | if (config[key]["type"] == "csj") { |
| | | return AdType.csj; |
| | | } |
| | | |
| | | if (config[key]["type"] == "gdt") { |
| | | return AdType.gdt; |
| | | } |
| | | |
| | | return null; |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:io'; |
| | | |
| | | import 'package:device_info/device_info.dart'; |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/services.dart'; |
| | | import 'package:fluwx_no_pay/fluwx_no_pay.dart'; |
| | | import 'package:package_info/package_info.dart'; |
| | | |
| | | import 'global.dart'; |
| | | |
| | | class AppUtil { |
| | | |
| | | static bool _inited = false; |
| | | |
| | | //初始化应用 |
| | | static Future<bool> initApp(BuildContext context) async { |
| | | if (_inited) { |
| | | return true; |
| | | } |
| | | _inited = true; |
| | | print("initApp"); |
| | | await registerWxApi( |
| | | appId: "wxd930ea5d5a228f5f", |
| | | universalLink: "https://your.univerallink.com/link/"); |
| | | |
| | | //初始化广告 |
| | | // await AdUtil.init(context); |
| | | |
| | | //初始化本地应用 |
| | | await _initNativeApp(); |
| | | |
| | | //初始化版本 |
| | | if (Platform.isAndroid) { |
| | | DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); |
| | | AndroidDeviceInfo _androidInfo = await deviceInfo.androidInfo; |
| | | Global.androidSDK = _androidInfo.version.sdkInt; |
| | | } |
| | | |
| | | // //初始化阿里云授权登录 |
| | | // await LoginPage.messageChannel.send({ |
| | | // "method": "init", |
| | | // "secret": Constant.ALIYUN_AUTH_SECRETINFO, |
| | | // "privacy": Constant.PRIVACY_URL, |
| | | // "protocol": Constant.PROTOCOL_URL |
| | | // }) as Map; |
| | | |
| | | return true; |
| | | } |
| | | |
| | | //本地应用初始化 |
| | | static _initNativeApp() async { |
| | | if (Platform.isAndroid) { |
| | | const platform = MethodChannel("com.yeshi.location/init"); //分析1 |
| | | try { |
| | | await platform.invokeMethod("initApp"); //分析2 |
| | | } on PlatformException catch (e) { |
| | | print(e.toString()); |
| | | } |
| | | //填充utdid |
| | | await Global.loadUtdId(); |
| | | //填充channel |
| | | await Global.loadChannel(); |
| | | } |
| | | } |
| | | |
| | | static Future<int> getVersionCode() async { |
| | | PackageInfo packageInfo = await PackageInfo.fromPlatform(); |
| | | return int.parse(packageInfo.buildNumber); |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:io'; |
| | | import 'package:path_provider/path_provider.dart'; |
| | | |
| | | /// 缓存管理类 |
| | | /// ./lib/utils/cache_util.dart |
| | | class CacheUtil { |
| | | /// 获取缓存大小 |
| | | static Future<int> total() async { |
| | | Directory tempDir = await getTemporaryDirectory(); |
| | | if (tempDir == null) return 0; |
| | | int total = await _reduce(tempDir); |
| | | return total; |
| | | } |
| | | |
| | | /// 清除缓存 |
| | | static Future<void> clear() async { |
| | | Directory tempDir = await getTemporaryDirectory(); |
| | | if (tempDir == null) return; |
| | | await _delete(tempDir); |
| | | } |
| | | |
| | | /// 递归缓存目录,计算缓存大小 |
| | | static Future<int> _reduce(final FileSystemEntity file) async { |
| | | /// 如果是一个文件,则直接返回文件大小 |
| | | if (file is File) { |
| | | int length = await file.length(); |
| | | return length; |
| | | } |
| | | |
| | | /// 如果是目录,则遍历目录并累计大小 |
| | | if (file is Directory) { |
| | | final List<FileSystemEntity> children = file.listSync(); |
| | | |
| | | int total = 0; |
| | | |
| | | if (children != null && children.isNotEmpty) |
| | | for (final FileSystemEntity child in children) |
| | | total += await _reduce(child); |
| | | |
| | | return total; |
| | | } |
| | | |
| | | return 0; |
| | | } |
| | | |
| | | /// 递归删除缓存目录和文件 |
| | | static Future<void> _delete(FileSystemEntity file) async { |
| | | if (file is Directory) { |
| | | final List<FileSystemEntity> children = file.listSync(); |
| | | for (final FileSystemEntity child in children) { |
| | | await _delete(child); |
| | | } |
| | | } else { |
| | | await file.delete(); |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:convert'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import '../../api/config_api.dart'; |
| | | import '../api/http.dart'; |
| | | import 'package:shared_preferences/shared_preferences.dart'; |
| | | |
| | | class ConfigUtil { |
| | | ///保存配置信息 |
| | | static void saveConfig(Map<String, dynamic> map) async { |
| | | SharedPreferences prefs = await SharedPreferences.getInstance(); |
| | | await prefs.setString("config_value", jsonEncode(map)); |
| | | } |
| | | |
| | | static Future<String?> getConfig(BuildContext context, String key) async { |
| | | SharedPreferences prefs = await SharedPreferences.getInstance(); |
| | | String? result = prefs.getString("config_value"); |
| | | if (result != null) { |
| | | Map<String, dynamic> map = jsonDecode(result); |
| | | return map[key]; |
| | | } else { |
| | | //重新请求 |
| | | ConfigApiUtil.getConfig(context).then((value) { |
| | | if (value == null) { |
| | | return; |
| | | } |
| | | if (value["code"] == 0) { |
| | | saveConfig(value["data"]); |
| | | } |
| | | }); |
| | | } |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | class ConfigKey { |
| | | //客服 |
| | | static const String kefu = "kefu"; |
| | | |
| | | //教程 |
| | | static const String course = "course"; |
| | | |
| | | //注销 |
| | | static const String unRegister = "unRegister"; |
| | | |
| | | //隐私投诉 |
| | | static const String privacyComplain = "privacyComplain"; |
| | | |
| | | //会员链接 |
| | | static const String vipLink = "vipLink"; |
| | | |
| | | //三方SDK链接 |
| | | static const String sdkList = "sdkList"; |
| | | } |
| | | |
| | | enum SharePlatform { wx, wxcircle, qq, qqzone, sina } |
New file |
| | |
| | | import 'dart:convert'; |
| | | |
| | | import '../../model/video/video_model.dart'; |
| | | import '../../model/video/watch_record_model.dart'; |
| | | |
| | | import 'sqlite_utils.dart'; |
| | | |
| | | class DBManager { |
| | | ///初始化 |
| | | static List<String> getTables() { |
| | | List<String> tables = [ |
| | | ''' |
| | | CREATE TABLE IF NOT Exists WATCH_RECORD( |
| | | _ID INTEGER NOT NULL PRIMARY KEY, |
| | | _VIDEO_ID TEXT NOT NULL UNIQUE, |
| | | _VIDEOS TEXT NOT NULL, |
| | | _VIDEO_DETAIL TEXT NOT NULL, |
| | | _POSITION INTEGER NOT NULL, |
| | | _CREATE_TIME INTEGER NOT NULL, |
| | | _UPDATE_TIME INTEGER NOT NULL |
| | | ); |
| | | ''' |
| | | ]; |
| | | return tables; |
| | | } |
| | | |
| | | ///添加观看记录 |
| | | static void addWatchRecord(VideoInfoModel videoInfo, |
| | | VideoDetailInfo detailInfo, int position) async { |
| | | VideoInfoModel temp=VideoInfoModel.fromJson(videoInfo.toJson()); |
| | | temp.videoDetailList = null; |
| | | List<Map> resultList = await SQLiteUtil.select( |
| | | "SELECT * FROM WATCH_RECORD where _VIDEO_ID = ?", [temp.id]); |
| | | if (resultList.isEmpty) { |
| | | String sql = |
| | | "INSERT INTO WATCH_RECORD(_VIDEO_ID,_VIDEOS,_VIDEO_DETAIL,_POSITION,_CREATE_TIME,_UPDATE_TIME) VALUES(?,?,?,?,?,?)"; |
| | | await SQLiteUtil.insert(sql, [ |
| | | [ |
| | | temp.id!, |
| | | jsonEncode(temp.toJson()), |
| | | jsonEncode(detailInfo.toJson()), |
| | | position, |
| | | DateTime.now().millisecondsSinceEpoch, |
| | | DateTime.now().millisecondsSinceEpoch, |
| | | ] |
| | | ]); |
| | | } else { |
| | | String sql = |
| | | "UPDATE WATCH_RECORD SET _VIDEOS=?,_VIDEO_DETAIL=?,_POSITION=?,_UPDATE_TIME=? WHERE _ID=?"; |
| | | await SQLiteUtil.executeSQLWithParams(sql, [ |
| | | jsonEncode(temp.toJson()), |
| | | jsonEncode(detailInfo.toJson()), |
| | | position, |
| | | DateTime.now().millisecondsSinceEpoch, |
| | | resultList[0]["_ID"] |
| | | ]); |
| | | } |
| | | } |
| | | |
| | | ///删除观看记录 |
| | | static Future deleteWatchRecord(List<String> ids) async { |
| | | String sql = "DELETE FROM WATCH_RECORD WHERE "; |
| | | for (var element in ids) { |
| | | sql += " _ID = ? OR"; |
| | | } |
| | | if (sql.endsWith("OR")) { |
| | | sql = sql.substring(0, sql.length - 2); |
| | | } |
| | | await SQLiteUtil.executeSQLWithParams(sql, ids); |
| | | } |
| | | |
| | | ///获取观看记录列表 |
| | | static Future<List<WatchRecordModel>> listWatchRecord( |
| | | int page, int pageSize) async { |
| | | String sql = |
| | | "select * from WATCH_RECORD ORDER BY _UPDATE_TIME DESC LIMIT ?,?"; |
| | | List<Map> result = |
| | | await SQLiteUtil.select(sql, [(page - 1) * pageSize, pageSize]); |
| | | List<WatchRecordModel> list = []; |
| | | result.forEach((element) { |
| | | list.add(WatchRecordModel( |
| | | id: element["_ID"], |
| | | videoId: element["_VIDEO_ID"], |
| | | video: VideoInfoModel.fromJson(jsonDecode(element["_VIDEOS"])), |
| | | videoDetail: |
| | | VideoDetailInfo.fromJson(jsonDecode(element["_VIDEO_DETAIL"])), |
| | | position: element["_POSITION"], |
| | | createTime: element["_CREATE_TIME"], |
| | | updateTime: element["_UPDATE_TIME"], |
| | | )); |
| | | }); |
| | | return list; |
| | | } |
| | | |
| | | static Future<int> countWatchRecord() async { |
| | | String sql = "select count(*) from WATCH_RECORD"; |
| | | return await SQLiteUtil.count(sql, []); |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:typed_data'; |
| | | |
| | | class CryptoUtil { |
| | | static List<int>? hex2List(String hexStr) { |
| | | if (hexStr == null || hexStr.length % 2 != 0) { |
| | | //十六进制字符串错误 |
| | | return null; |
| | | } |
| | | |
| | | if (hexStr.startsWith('0x')) { |
| | | hexStr = hexStr.substring(2); |
| | | } |
| | | List<int> result = List.filled(hexStr.length ~/ 2,0); |
| | | String temp = '0123456789ABCDEF'; |
| | | for (int i = 0; i < hexStr.length; i += 2) { |
| | | int h = temp.indexOf(hexStr.substring(i, i + 1)); |
| | | int l = temp.indexOf(hexStr.substring(i + 1, i + 2)); |
| | | result[i ~/ 2] = (h * 16 + l); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | static String list2Hex(List<int> list) { |
| | | List<String> results = List.filled(list.length * 2,""); |
| | | String temp = '0123456789ABCDEF'; |
| | | for (int i = 0; i < list.length; i++) { |
| | | int h = list[i] ~/ 16; |
| | | int l = list[i] % 16; |
| | | results[i * 2] = temp.substring(h, h + 1); |
| | | results[i * 2 + 1] = temp.substring(l, l + 1); |
| | | } |
| | | return results.join(); |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:convert'; |
| | | import 'dart:typed_data'; |
| | | |
| | | import 'crypto_util.dart'; |
| | | import 'number_utils.dart'; |
| | | import 'padding.dart'; |
| | | |
| | | /// author: karedem |
| | | /// 参考至: https://blog.csdn.net/yxtxiaotian/article/details/52025653 |
| | | /// 以及 https://www.cnblogs.com/songwenlong/p/5944139.html |
| | | /// |
| | | class DES { |
| | | static const String _iv = '01234567'; |
| | | static const BLOCK_SIZE = 8; |
| | | List<List<int>>? dispareKeys; |
| | | static const E_box = [ |
| | | //E |
| | | 32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, |
| | | 8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, |
| | | 16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, |
| | | 24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1 |
| | | ]; |
| | | |
| | | static const IP = [ |
| | | //IP |
| | | 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, |
| | | 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, |
| | | 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, |
| | | 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7 |
| | | ]; |
| | | |
| | | static const IP_1 = [ |
| | | //IP_R |
| | | 40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31, |
| | | 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, |
| | | 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, |
| | | 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25 |
| | | ]; |
| | | |
| | | static const PC_1 = [ |
| | | //PC_1 |
| | | 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, |
| | | 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36, |
| | | 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, |
| | | 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4 |
| | | ]; |
| | | |
| | | static const PC_2 = [ |
| | | //PC_2 |
| | | 14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10, |
| | | 23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2, |
| | | 41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, |
| | | 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32 |
| | | ]; |
| | | |
| | | static const S_Box = [ |
| | | [ |
| | | // S1 |
| | | 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, |
| | | 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, |
| | | 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0, |
| | | 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 |
| | | ], |
| | | [ |
| | | //S2 |
| | | 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, |
| | | 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, |
| | | 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15, |
| | | 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 |
| | | ], |
| | | [ |
| | | //S3 |
| | | 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, |
| | | 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, |
| | | 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7, |
| | | 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 |
| | | ], |
| | | [ |
| | | //S4 |
| | | 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, |
| | | 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, |
| | | 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4, |
| | | 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 |
| | | ], |
| | | [ |
| | | //S5 |
| | | 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, |
| | | 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, |
| | | 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14, |
| | | 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 |
| | | ], |
| | | [ |
| | | //S6 |
| | | 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, |
| | | 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, |
| | | 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6, |
| | | 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 |
| | | ], |
| | | [ |
| | | //S7 |
| | | 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, |
| | | 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, |
| | | 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2, |
| | | 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 |
| | | ], |
| | | [ |
| | | //S8 |
| | | 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, |
| | | 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, |
| | | 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8, |
| | | 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 |
| | | ] |
| | | ]; |
| | | |
| | | static const P_Box = [ |
| | | //P_box |
| | | 16, 7, 20, 21, |
| | | 29, 12, 28, 17, |
| | | 1, 15, 23, 26, |
| | | 5, 18, 31, 10, |
| | | 2, 8, 24, 14, |
| | | 32, 27, 3, 9, |
| | | 19, 13, 30, 6, |
| | | 22, 11, 4, 25 |
| | | ]; |
| | | |
| | | static const shift_digit = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]; |
| | | |
| | | ///将64字节的密钥压缩成56字节 字节数组转为二进制数组 |
| | | List<int> _compressKeyTo56(List<int> key) { |
| | | List<int> bitKey = List.filled(56,0); |
| | | for (int i = 0; i < 56; i++) { |
| | | int realIndex = PC_1[i] - 1; |
| | | bitKey[i] = (key[realIndex >> 3] >> (7 - realIndex & 7)) & 1; |
| | | } |
| | | return bitKey; |
| | | } |
| | | |
| | | ///离散得到16个子密钥 |
| | | List<List<int>> dispareKey(List<int> compressKey) { |
| | | List<List<int>> dispareKeys = List.filled(16,[]); |
| | | List<int> c0 = compressKey.sublist(0, 28); |
| | | List<int> d0 = compressKey.sublist(28); |
| | | List<int> tempc = c0; |
| | | List<int> tempd = d0; |
| | | for (int i = 0; i < 16; i++) { |
| | | tempc = left_shift(i, tempc); |
| | | tempd = left_shift(i, tempd); |
| | | List<int> tempAll = []; |
| | | tempAll.addAll(tempc); |
| | | tempAll.addAll(tempd); |
| | | |
| | | List<int> dispareKey = compressDispareKey(tempAll); |
| | | dispareKeys[i] = dispareKey; |
| | | } |
| | | return dispareKeys; |
| | | } |
| | | |
| | | ///左移函数 需要根据次数 左移 |
| | | static List<int> left_shift(int times, List<int> key) { |
| | | ///需移动的位数 |
| | | int shift_length = shift_digit[times]; |
| | | List<int> newList = key.sublist(shift_length); |
| | | newList.addAll(key.sublist(0, shift_length)); |
| | | return newList; |
| | | } |
| | | |
| | | ///将56位的密钥压缩为48位 二进制数组 处理 |
| | | List<int> compressDispareKey(List<int> dispareKey) { |
| | | List<int> bitKey = List.filled(48,0); |
| | | for (int i = 0; i < 48; i++) { |
| | | int realIndex = PC_2[i] - 1; |
| | | bitKey[i] = dispareKey[realIndex]; |
| | | } |
| | | return bitKey; |
| | | } |
| | | |
| | | //01001 9 1111 1111 >> 6 |
| | | |
| | | ///明文转换 字节数组转二进制数组 |
| | | List<int> compressPlain(List<int> plain) { |
| | | List<int> bitKey = List.filled(64,0); |
| | | for (int i = 0; i < 64; i++) { |
| | | int realIndex = IP[i] - 1; |
| | | bitKey[i] = (plain[realIndex >> 3] >> (7 - realIndex & 7)) & 1; |
| | | } |
| | | return bitKey; |
| | | } |
| | | |
| | | ///E盒扩展 |
| | | List<int> E_transform(List<int> list) { |
| | | ///左半部分为 L0 右半部分为R0 |
| | | //print("before E transform : " + list.toString()); |
| | | List<int> result = List.filled(48,0); |
| | | for (int i = 0; i < 48; i++) { |
| | | result[i] = list[E_box[i] - 1]; |
| | | } |
| | | //print("after E transform : " + result.toString()); |
| | | return result; |
| | | } |
| | | |
| | | ///P盒置换 |
| | | List<int> P_transform(List<int> list) { |
| | | List<int> bitKey = List.filled(32,0); |
| | | for (int i = 0; i < 32; i++) { |
| | | int realIndex = P_Box[i] - 1; |
| | | bitKey[i] = list[realIndex]; |
| | | } |
| | | return bitKey; |
| | | } |
| | | |
| | | ///S盒变换 二进制结果 |
| | | List<int> _S_Box_transform(List<int> list) { |
| | | ///check list length 48 |
| | | List<int> result = List.filled(32,0); |
| | | for (int i = 0; i < list.length; i += 6) { |
| | | int x = (list[i + 1] << 3 | |
| | | list[i + 2] << 2 | |
| | | list[i + 3] << 1 | |
| | | list[i + 4]); |
| | | int y = (list[i] << 1 | list[i + 5]); |
| | | int i_n = S_Box[i ~/ 6][(y << 4) + x]; |
| | | result[(i << 1) ~/ 3] = (i_n >> 3) & 1; |
| | | result[(i << 1) ~/ 3 + 1] = (i_n >> 2) & 1; |
| | | result[(i << 1) ~/ 3 + 2] = (i_n >> 1) & 1; |
| | | result[(i << 1) ~/ 3 + 3] = i_n & 1; |
| | | } |
| | | //print('before S box : ${list.toString()}'); |
| | | //print('after S box : ${result.toString()}'); |
| | | return result; |
| | | } |
| | | |
| | | ///异或 |
| | | List<int> _XOR_with_Left(List<int> left, List<int> presult) { |
| | | List<int> result = List.filled(left.length,0); |
| | | for (int i = 0; i < left.length; i++) { |
| | | result[i] = left[i] ^ presult[i]; |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | ///IP_1置换 |
| | | List<int> _IP_1_transform(List<int> list) { |
| | | ///check list length 48 |
| | | List<int> bitKey = List.filled(64,0); |
| | | for (int i = 0; i < 64; i++) { |
| | | int realIndex = IP_1[i] - 1; |
| | | bitKey[i] = list[realIndex]; |
| | | } |
| | | return bitKey; |
| | | } |
| | | |
| | | void initKey(List<int> key) { |
| | | if (dispareKeys != null) { |
| | | return; |
| | | } |
| | | List<int> pc1_key = _compressKeyTo56(key); |
| | | dispareKeys = dispareKey(pc1_key); |
| | | } |
| | | |
| | | /// ECB加密 加密结果为十六进制 默认为PKCS7填充 |
| | | /// plain : 待加密数据 (utf-8) |
| | | /// hexKey : 十六进制密钥 |
| | | String encryptToHexWithECB(String plain, String hexKey) { |
| | | return CryptoUtil.list2Hex(encrypWithEcb( |
| | | Utf8Encoder().convert(plain).toList(), CryptoUtil.hex2List(hexKey)!)); |
| | | } |
| | | |
| | | /// CBC加密 加密结果为十六进制 默认为PKCS7填充 |
| | | /// plain : 待加密数据 (utf-8) |
| | | /// hexKey : 十六进制密钥 |
| | | /// iv : 向量 (utf-8 默认值为_iv) |
| | | String encryptToHexWithCBC(String plain, String hexKey, {String iv = _iv}) { |
| | | return CryptoUtil.list2Hex(encryptWithCBC( |
| | | Utf8Encoder().convert(plain).toList(), CryptoUtil.hex2List(hexKey)!, |
| | | iv: iv)); |
| | | } |
| | | |
| | | /// CBC解密 |
| | | /// cipher : 待解密数据 (十六进制) |
| | | /// hexKey : 十六进制密钥 |
| | | /// iv : 向量 (utf-8 默认值为_iv) |
| | | String decryptFromHexWithCBC(String cipher, String hexKey, |
| | | {String iv = _iv}) { |
| | | return Utf8Decoder().convert(decryptWithCBC( |
| | | CryptoUtil.hex2List(cipher)!, CryptoUtil.hex2List(hexKey)!, |
| | | iv: iv)); |
| | | } |
| | | |
| | | /// ECB解密 |
| | | /// cipher : 待解密数据 (十六进制) |
| | | /// hexKey : 十六进制密钥 |
| | | String decryptFromHexWithECB(String cipher, String hexKey, |
| | | {String iv = _iv}) { |
| | | return Utf8Decoder().convert(decryptWithEcb( |
| | | CryptoUtil.hex2List(cipher)!, CryptoUtil.hex2List(hexKey)!)); |
| | | } |
| | | |
| | | ///加密 明文字节数组 密钥字节数组 |
| | | List<int> _encryptBlock(List<int> block) { |
| | | ///dispareKey right! |
| | | //print("block " + block.toString()); |
| | | var plainCompressed = compressPlain(block); |
| | | //print("plainCompressed " + plainCompressed.toString()); |
| | | List<int> L0 = plainCompressed.sublist(0, 32); |
| | | List<int> R0 = plainCompressed.sublist(32); |
| | | List<int> L0Z = L0; |
| | | List<int> R0Z = R0; |
| | | |
| | | for (int i = 0; i < 16; i++) { |
| | | var ln = R0Z; |
| | | var pResult = P_transform( |
| | | _S_Box_transform(_XOR_with_Left(dispareKeys![i], E_transform(R0Z)))); |
| | | |
| | | ///P盒置换的结果盒L0做异或 |
| | | var rn = _XOR_with_Left(pResult, L0Z); |
| | | L0Z = ln; |
| | | R0Z = rn; |
| | | } |
| | | List<int> result = []; |
| | | result.addAll(R0Z); |
| | | result.addAll(L0Z); |
| | | result = _IP_1_transform(result); |
| | | return NumberUtils.intListFromBits(result); |
| | | } |
| | | |
| | | List<int> encryptWithCBC(List<int> plain, List<int> key, {String iv = _iv}) { |
| | | int allLen = plain.length; |
| | | int blockCount = allLen >> 3; |
| | | List<int> padPlain = plain.sublist(0, blockCount << 3); |
| | | padPlain.addAll(Padding.pkcs7Padding(plain.sublist(blockCount << 3))); |
| | | //List<int> blockCipher = List(padPlain.length); |
| | | List<int> blockCipher = []; |
| | | List<int> tempIv = iv.codeUnits; |
| | | initKey(key); |
| | | for (int i = 0; i < padPlain.length; i += BLOCK_SIZE) { |
| | | List<int> xorPlain = |
| | | _XOR_with_Left(padPlain.sublist(i, i + BLOCK_SIZE), tempIv); |
| | | tempIv = _encryptBlock(xorPlain); |
| | | blockCipher.addAll(tempIv); |
| | | } |
| | | return blockCipher; |
| | | } |
| | | |
| | | List<int> decryptWithCBC(List<int> cipher, List<int> key, {String iv = _iv}) { |
| | | List<int> plain = []; |
| | | List<int> tempIv = iv.codeUnits; |
| | | initKey(key); |
| | | for (int i = 0; i < cipher.length; i += BLOCK_SIZE) { |
| | | if (i == cipher.length - BLOCK_SIZE) { |
| | | List<int> plainXor = decryptBlock(cipher.sublist(i, i + BLOCK_SIZE)); |
| | | List<int> plainBlock = |
| | | Padding.pkcs7UnPadding(_XOR_with_Left(plainXor, tempIv)); |
| | | tempIv = cipher.sublist(i, i + BLOCK_SIZE); |
| | | plain.addAll(plainBlock); |
| | | } else { |
| | | List<int> plainXor = decryptBlock(cipher.sublist(i, i + BLOCK_SIZE)); |
| | | List<int> plainBlock = _XOR_with_Left(plainXor, tempIv); |
| | | tempIv = cipher.sublist(i, i + BLOCK_SIZE); |
| | | plain.addAll(plainBlock); |
| | | } |
| | | } |
| | | return plain; |
| | | } |
| | | |
| | | ///加密 明文字节数组 密钥字节数组 |
| | | List<int> encrypWithEcb(List<int> plain, List<int> key) { |
| | | List<int> blockCipher = []; |
| | | int allLen = plain.length; |
| | | int blockCount = allLen >> 3; |
| | | List<int> padPlain = plain.sublist(0, blockCount << 3).toList(); |
| | | padPlain.addAll(Padding.pkcs7Padding(plain.sublist(blockCount << 3))); |
| | | initKey(key); |
| | | for (int i = 0; i < padPlain.length; i += BLOCK_SIZE) { |
| | | blockCipher.addAll(_encryptBlock(padPlain.sublist(i, i + BLOCK_SIZE))); |
| | | } |
| | | return blockCipher; |
| | | } |
| | | |
| | | List<int> decryptBlock(List<int> cipher) { |
| | | var plainCompressed = compressPlain(cipher); |
| | | List<int> L0 = plainCompressed.sublist(0, plainCompressed.length >> 1); |
| | | List<int> R0 = plainCompressed.sublist(plainCompressed.length >> 1); |
| | | List<int> L0Z = L0; |
| | | List<int> R0Z = R0; |
| | | for (int i = 0; i < 16; i++) { |
| | | var ln = R0Z; |
| | | var pResult = P_transform(_S_Box_transform( |
| | | _XOR_with_Left(dispareKeys![15 - i], E_transform(R0Z)))); |
| | | var rn = _XOR_with_Left(pResult, L0Z); |
| | | L0Z = ln; |
| | | R0Z = rn; |
| | | } |
| | | List<int> result = []; |
| | | result.addAll(R0Z); |
| | | result.addAll(L0Z); |
| | | result = _IP_1_transform(result); |
| | | return NumberUtils.intListFromBits(result); |
| | | } |
| | | |
| | | ///支持长数据解密 |
| | | List<int> decryptWithEcb(List<int> cipher, List<int> key) { |
| | | List<int> plain = []; |
| | | initKey(key); |
| | | for (int i = 0; i < cipher.length; i += BLOCK_SIZE) { |
| | | if (i == cipher.length - BLOCK_SIZE) { |
| | | plain.addAll(Padding.pkcs7UnPadding( |
| | | decryptBlock(cipher.sublist(i, i + BLOCK_SIZE)))); |
| | | } else { |
| | | plain.addAll(decryptBlock(cipher.sublist(i, i + BLOCK_SIZE))); |
| | | } |
| | | } |
| | | return plain; |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:math'; |
| | | |
| | | class NumberUtils { |
| | | ///二进制数组转字节数组 |
| | | static List<int> intListFromBits(List<int> bits) { |
| | | //print("${bits.toString()} ${bits.length ~/ 8}"); |
| | | List<int> result = List.generate(bits.length >> 3, (index) => 0); |
| | | for (int i = 0; i < bits.length; i++) { |
| | | result[i >> 3] |= (bits[i] << (7 - i & 7)); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | ///字节数组转二进制数组 |
| | | static List<int> bitsFromIntList(List<int> ints) { |
| | | List<int> result = List.filled(ints.length << 3,0); |
| | | for (int i = 0; i < (ints.length << 3); ++i) { |
| | | result[i] = (ints[i >> 3] >> (7 - i & 7)) & 1; |
| | | } |
| | | return result; |
| | | // List<int> result = []; |
| | | // ints.forEach((i) { |
| | | // result.addAll(to8Bit(i)); |
| | | // }); |
| | | // return result; |
| | | } |
| | | |
| | | ///二进制字符串转数字 |
| | | static int intFromBits(List<int> bits) { |
| | | //return int.parse(bits.join(), radix: 2); |
| | | int result = 0; |
| | | for (int i = 0; i < bits.length; i++) { |
| | | result |= (bits[i] << (7 - i & 7)); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | ///to8Bit 01100100 |
| | | static List<int> t8Bit(List<int> bytes) { |
| | | List<int> result = List.filled(8 * bytes.length,0); |
| | | for (int i = 0; i < 8 * bytes.length; ++i) { |
| | | result[i] = (bytes[i >> 3] >> (7 - i & 7)) & 1; |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | /// 4 -> 00000100 |
| | | static List<int> to8Bit(int num) { |
| | | List<int> result = List.filled(8,0); |
| | | for (int i = 0; i < 8; i++) { |
| | | result[i] = (num >> (7 - i & 7)) & 1; |
| | | } |
| | | return result; |
| | | |
| | | String temp = num.toRadixString(2); |
| | | int zeroLen = 8 - temp.length; |
| | | for (int i = 0; i < 8; i++) { |
| | | if (i < zeroLen) { |
| | | result[i] = 0; |
| | | } else { |
| | | result[i] = (temp.codeUnitAt(i - zeroLen) - 48); |
| | | } |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | /// 4 -> 0100 |
| | | static List<int> to4Bit(int num) { |
| | | List<int> result = List.filled(4,0); |
| | | for (int i = 0; i < 4; i++) { |
| | | result[i] = (num >> (3 - i & 3)) & 1; |
| | | } |
| | | return result; |
| | | |
| | | String temp = num.toRadixString(2); |
| | | temp.codeUnits.forEach((code) { |
| | | result.add(code - 48); |
| | | }); |
| | | int len = result.length; |
| | | if (len < 4) { |
| | | result.insertAll(0, List.generate(4, (index) => 0).sublist(len)); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | static String formatSeeds(int seeds) { |
| | | if (seeds > 9999) { |
| | | return '${(seeds / 10000.0).toStringAsFixed(1)}w'; |
| | | } else if (seeds > 999) { |
| | | return '${(seeds / 1000.0).toStringAsFixed(1)}k'; |
| | | } else { |
| | | return '$seeds'; |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | class Padding { |
| | | static List<int> pkcs7Padding(List<int> data) { |
| | | int fillLen = 8 - data.length % 8; |
| | | List<int> padDate = data.toList(); |
| | | for (int i = 0; i < fillLen; i++) { |
| | | padDate.add(fillLen); |
| | | } |
| | | return padDate; |
| | | } |
| | | |
| | | static List<int> pkcs7UnPadding(List<int> padData) { |
| | | int fillLen = padData.last; |
| | | if (fillLen < 0 || fillLen > 8) { |
| | | return padData; |
| | | } |
| | | List<int> data = padData.sublist(0, padData.length - fillLen); |
| | | return data; |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:convert'; |
| | | import 'package:crypto/crypto.dart'; |
| | | |
| | | class EncryptUtil { |
| | | static String MD5(String data) { |
| | | return md5.convert(utf8.encode(data)).toString(); |
| | | } |
| | | |
| | | |
| | | } |
New file |
| | |
| | | import 'package:event_bus/event_bus.dart'; |
| | | EventBus eventBus = EventBus(); |
| | | |
| | | |
| | | |
| | | class LoginEventBus { |
| | | final bool isLogin; |
| | | LoginEventBus(this.isLogin); |
| | | } |
| | | |
New file |
| | |
| | | import 'package:flutter/material.dart'; |
| | | import 'package:flutter/services.dart'; |
| | | |
| | | //全局跳转 |
| | | final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); |
| | | class Global { |
| | | static const messageChannel = |
| | | BasicMessageChannel('DeviceUtil', StandardMessageCodec()); |
| | | |
| | | static String? utdId; |
| | | |
| | | static String? channel; |
| | | |
| | | //android的版本 |
| | | static int? androidSDK; |
| | | |
| | | static Future loadUtdId() async { |
| | | String? value = |
| | | await messageChannel.send({"method": "getUtdid"}) as String?; |
| | | utdId = value; |
| | | } |
| | | |
| | | static Future loadChannel() async { |
| | | String? value = |
| | | await messageChannel.send({"method": "getChannel"}) as String?; |
| | | channel = value; |
| | | } |
| | | } |
New file |
| | |
| | | //图片工具 |
| | | import 'package:flutter/material.dart'; |
| | | import '../../utils/ui_constant.dart'; |
| | | import 'package:image_cropper/image_cropper.dart'; |
| | | import 'dart:io'; |
| | | import 'package:image_picker/image_picker.dart'; |
| | | |
| | | class ImageUtil{ |
| | | |
| | | //选择并裁剪图片 |
| | | static Future<File?> selectAndCropImage() async{ |
| | | final ImagePicker _picker = ImagePicker(); |
| | | final XFile? image = await _picker.pickImage(source: ImageSource.gallery); |
| | | ImageCropper cropper=ImageCropper(); |
| | | File? croppedFile = await cropper.cropImage( |
| | | cropStyle:CropStyle.circle, |
| | | sourcePath: image!.path, |
| | | aspectRatioPresets: [ |
| | | CropAspectRatioPreset.square |
| | | ], |
| | | androidUiSettings: const AndroidUiSettings( |
| | | toolbarTitle: 'Cropper', |
| | | toolbarColor: ColorConstant.theme, |
| | | toolbarWidgetColor: Colors.white, |
| | | initAspectRatio: CropAspectRatioPreset.square, |
| | | lockAspectRatio: true), |
| | | iosUiSettings: const IOSUiSettings( |
| | | minimumAspectRatio: 1.0, |
| | | ) |
| | | ); |
| | | return croppedFile; |
| | | } |
| | | |
| | | |
| | | |
| | | } |
New file |
| | |
| | | import 'dart:convert'; |
| | | import 'dart:io'; |
| | | |
| | | import 'package:dio/dio.dart'; |
| | | import 'package:flutter/services.dart'; |
| | | import 'package:flutter/widgets.dart'; |
| | | import '../api/http.dart' as http; |
| | | import '../utils/encrypt_util.dart'; |
| | | import '../utils/permission_util.dart'; |
| | | import '../utils/share_utils.dart'; |
| | | import '../utils/string_util.dart'; |
| | | import '../utils/ui_constant.dart'; |
| | | import '../utils/ui_utils.dart'; |
| | | import '../utils/user_util.dart'; |
| | | import 'package:permission_handler/permission_handler.dart'; |
| | | import 'package:webview_flutter/platform_interface.dart'; |
| | | import 'package:webview_flutter/webview_flutter.dart'; |
| | | import 'package:path_provider/path_provider.dart'; |
| | | import '../ui/widget/nav.dart'; |
| | | import 'package:image_gallery_saver/image_gallery_saver.dart'; |
| | | |
| | | import 'config_util.dart'; |
| | | import 'package:package_info/package_info.dart'; |
| | | |
| | | class JavascriptInterface { |
| | | final BuildContext context; |
| | | final WebViewController? _controller; |
| | | |
| | | JavascriptInterface( |
| | | BuildContext this.context, WebViewController? this._controller); |
| | | |
| | | Set<JavascriptChannel> getInterfaces() { |
| | | List<JavascriptChannel> list = []; |
| | | list.add(JavascriptChannel( |
| | | name: 'yestv', |
| | | onMessageReceived: (JavascriptMessage message) { |
| | | print("onMessageReceived"); |
| | | var data = jsonDecode(message.message); |
| | | String method = data["method"]; |
| | | var params = data["params"]; |
| | | String? _callback = data["callback"]; |
| | | switch (method) { |
| | | case "toast": |
| | | toast(params); |
| | | break; |
| | | case "copyText": |
| | | copyText(params); |
| | | break; |
| | | case "getUid": |
| | | getUid(params, _callback); |
| | | break; |
| | | case "getAppName": |
| | | getAppName(_callback); |
| | | break; |
| | | case "getRequestBaseParams": |
| | | getRequestBaseParams(params, _callback); |
| | | break; |
| | | case "showRewardVideoAd": |
| | | showRewardVideoAd(this.context, _callback); |
| | | break; |
| | | case "showLoading": |
| | | showLoading(); |
| | | break; |
| | | case "hideLoading": |
| | | hideLoading(); |
| | | break; |
| | | case "finishPage": |
| | | finishPage(); |
| | | break; |
| | | case "saveImg": |
| | | String url = params["url"]; |
| | | saveImg(url); |
| | | break; |
| | | case "shareImg": |
| | | String url = params["url"]; |
| | | String type = params["type"]; |
| | | shareImg(url, int.parse(type)); |
| | | break; |
| | | } |
| | | })); |
| | | return list.toSet(); |
| | | } |
| | | |
| | | callback(String method, var params, {bool finish = true}) { |
| | | _controller!.runJavascript("$method('$params')"); |
| | | if (finish) { |
| | | _controller!.runJavascript("delete $method"); |
| | | } |
| | | } |
| | | |
| | | //获取用户ID |
| | | getUid(var params, String? callbackName) { |
| | | if (callbackName != null) { |
| | | UserUtil.getUid().then((value) { |
| | | callback(callbackName, value != null ? value.toString() : ""); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | //获取用户ID |
| | | getAppName(String? callbackName) { |
| | | // |
| | | PackageInfo.fromPlatform().then((PackageInfo packageInfo) { |
| | | if (callbackName != null) { |
| | | callback(callbackName, packageInfo.appName); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | //toast |
| | | toast(params) { |
| | | if (params != null && params["msg"] != null) { |
| | | ToastUtil.toast(params["msg"], context); |
| | | } |
| | | } |
| | | |
| | | copyText(params) { |
| | | if (params != null && params["content"] != null) { |
| | | Clipboard.setData(ClipboardData(text: params["content"])); |
| | | } |
| | | } |
| | | |
| | | //获取基本的网络请求参数 |
| | | getRequestBaseParams(var params, String? callbackName) { |
| | | var ps = {}; |
| | | if (params != null) { |
| | | ps.addAll(params); |
| | | } |
| | | http.HttpUtil.getBaseParams(params).then((value) { |
| | | String result = jsonEncode(value); |
| | | if (callbackName != null) { |
| | | callback(callbackName, result); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | //展示激励视频 |
| | | showRewardVideoAd(BuildContext context, String? callbackName) { |
| | | // AdUtil.getAdInfo(context, AdPosition.vipReward).then((value) { |
| | | // AdUtil.loadReward(value, (status, msg) { |
| | | // switch (status) { |
| | | // case RewardAdStatus.verify: |
| | | // callback(callbackName!, 3, finish: false); |
| | | // break; |
| | | // case RewardAdStatus.ready: |
| | | // callback(callbackName!, 1, finish: false); |
| | | // break; |
| | | // case RewardAdStatus.close: |
| | | // callback(callbackName!, 10, finish: true); |
| | | // break; |
| | | // case RewardAdStatus.click: |
| | | // callback(callbackName!, 2, finish: false); |
| | | // break; |
| | | // case RewardAdStatus.fail: |
| | | // callback(callbackName!, 0, finish: true); |
| | | // break; |
| | | // } |
| | | // }); |
| | | // }); |
| | | } |
| | | |
| | | showLoading() { |
| | | http.showLoading(context); |
| | | } |
| | | |
| | | hideLoading() { |
| | | popPage(context); |
| | | } |
| | | |
| | | //结束页面 |
| | | finishPage() { |
| | | popPage(context); |
| | | } |
| | | |
| | | Future<bool> _dowloadImg(String url, String path) async { |
| | | PermissionStatus status = |
| | | await PermissionUtil.openPermission(Permission.storage, force: true); |
| | | if (status != PermissionStatus.granted) { |
| | | return false; |
| | | } |
| | | |
| | | //下载图片 |
| | | Dio dio = Dio(); |
| | | //设置连接超时时间 |
| | | dio.options.connectTimeout = 20000; |
| | | //设置数据接收超时时间 |
| | | dio.options.receiveTimeout = 20000; |
| | | Response response; |
| | | try { |
| | | response = await dio.download(url, path); |
| | | if (response.statusCode == 200) { |
| | | await ImageGallerySaver.saveFile(path); |
| | | return true; |
| | | } else { |
| | | return false; |
| | | } |
| | | } catch (e) { |
| | | ToastUtil.toast("网络连接失败", context); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | //保存图片 |
| | | saveImg(String url) { |
| | | Future<Directory?> dir; |
| | | if (Platform.isAndroid) { |
| | | dir = getExternalStorageDirectory(); |
| | | } else { |
| | | dir = getApplicationDocumentsDirectory(); |
| | | } |
| | | dir.then((value) { |
| | | if (value == null) { |
| | | ToastUtil.toast("获取缓存目录失败", context); |
| | | return; |
| | | } |
| | | |
| | | String doc = value.path; |
| | | String path = doc + "/" + EncryptUtil.MD5(url) + ".png"; |
| | | _dowloadImg(url, path).then((value) { |
| | | if (value) { |
| | | ToastUtil.toast("保存成功", context); |
| | | } |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | ///分享图片 |
| | | ///type: 1-微信 2-qq 3-新浪 |
| | | shareImg(String url, int type) { |
| | | getTemporaryDirectory().then((value) { |
| | | String path = value.path + "/" + EncryptUtil.MD5(url) + ".png"; |
| | | _dowloadImg(url, path).then((value) { |
| | | if (value) { |
| | | //开始分享 |
| | | switch (type) { |
| | | case 1: |
| | | ShareUtil.shareImg(context, File(path), SharePlatform.wx); |
| | | break; |
| | | case 2: |
| | | ShareUtil.shareImg(context, File(path), SharePlatform.qq); |
| | | break; |
| | | case 3: |
| | | ShareUtil.shareImg(context, File(path), SharePlatform.sina); |
| | | break; |
| | | } |
| | | } |
| | | }); |
| | | }); |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:convert'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import '../../utils/pageutils.dart'; |
| | | import '../../utils/ui_utils.dart'; |
| | | import 'package:flutter_boost/flutter_boost.dart'; |
| | | |
| | | import '../main.dart'; |
| | | |
| | | class JumpPageUtil { |
| | | |
| | | static jumpPage(String pageName, BuildContext context, |
| | | {Map<String,dynamic>? params, bool native = false, PageDataLisener? callback}) { |
| | | Map<String, dynamic> routeMap = { |
| | | "page": pageName, |
| | | }; |
| | | if (params != null) { |
| | | routeMap["params"] = params; |
| | | } |
| | | |
| | | if (native) { |
| | | print("原生传递参数:$params"); |
| | | //uiMethodChannel.invokeMapMethod("pushPage", jsonEncode(routeMap)); |
| | | // BoostChannel.instance.sendEventToNative("pushPage", routeMap); |
| | | BoostNavigator.instance.push( |
| | | pageName, //required |
| | | withContainer: true, //optional |
| | | arguments: params, //optional |
| | | opaque: true, //optional,default value is true |
| | | ); |
| | | } else { |
| | | NavigatorUtil.navigateToNextPage( |
| | | context, widgetForRoute(jsonEncode(routeMap)), (data) { |
| | | if (callback != null) { |
| | | callback(data); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | |
| | | import 'global.dart'; |
| | | |
| | | typedef PageDataLisener = void Function(dynamic data); |
| | | |
| | | //滑动效果 |
| | | class CustomRouteSlide extends PageRouteBuilder { |
| | | final Widget widget; |
| | | |
| | | CustomRouteSlide(this.widget) |
| | | : super( |
| | | transitionDuration: const Duration(milliseconds: 500), |
| | | pageBuilder: (BuildContext context, Animation<double> animation1, |
| | | Animation<double> animation2) { |
| | | return widget; |
| | | }, |
| | | transitionsBuilder: (BuildContext context, |
| | | Animation<double> animation1, |
| | | Animation<double> animation2, |
| | | Widget child) { |
| | | return SlideTransition( |
| | | position: Tween<Offset>( |
| | | begin: Offset(1.0, 0.0), end: Offset(0.0, 0.0)) |
| | | .animate(CurvedAnimation( |
| | | parent: animation1, curve: Curves.fastOutSlowIn)), |
| | | child: child, |
| | | ); |
| | | }); |
| | | } |
| | | |
| | | class NavigatorUtil { |
| | | // static void navigateToNextPage(BuildContext context, PageRoute route, |
| | | // PageDataLisener? dataLisener) async { |
| | | // final result = await Navigator.of(context).push(route); |
| | | // dataLisener!(result); |
| | | // } |
| | | |
| | | static void navigateToNextPage( |
| | | BuildContext context, Widget page, PageDataLisener? dataLisener) async { |
| | | final result = |
| | | await Navigator.of(context).push(CupertinoPageRoute(builder: (context) { |
| | | return page; |
| | | })); |
| | | dataLisener!(result); |
| | | } |
| | | |
| | | static void navigateToNextPagePush(Widget page) { |
| | | |
| | | navigatorKey.currentState!.push( |
| | | CupertinoPageRoute (builder: (BuildContext context) => page)); |
| | | } |
| | | |
| | | static void navigateToNextPageWithFinish( |
| | | BuildContext context, PageRoute route) { |
| | | Navigator.of(context).pushReplacement(route); |
| | | } |
| | | } |
| | | |
| | | class KeepAliveWrapper extends StatefulWidget { |
| | | const KeepAliveWrapper({ |
| | | Key? key, |
| | | this.keepAlive = true, |
| | | required this.child, |
| | | }) : super(key: key); |
| | | final bool keepAlive; |
| | | final Widget child; |
| | | |
| | | @override |
| | | _KeepAliveWrapperState createState() => _KeepAliveWrapperState(); |
| | | } |
| | | |
| | | class _KeepAliveWrapperState extends State<KeepAliveWrapper> |
| | | with AutomaticKeepAliveClientMixin { |
| | | @override |
| | | Widget build(BuildContext context) { |
| | | super.build(context); |
| | | return widget.child; |
| | | } |
| | | |
| | | @override |
| | | void didUpdateWidget(covariant KeepAliveWrapper oldWidget) { |
| | | if (oldWidget.keepAlive != widget.keepAlive) { |
| | | // keepAlive 状态需要更新,实现在 AutomaticKeepAliveClientMixin 中 |
| | | updateKeepAlive(); |
| | | } |
| | | super.didUpdateWidget(oldWidget); |
| | | } |
| | | |
| | | @override |
| | | bool get wantKeepAlive => widget.keepAlive; |
| | | } |
New file |
| | |
| | | import 'dart:convert'; |
| | | import 'dart:io'; |
| | | |
| | | import '../utils/global.dart'; |
| | | import 'package:permission_handler/permission_handler.dart'; |
| | | import 'package:shared_preferences/shared_preferences.dart'; |
| | | |
| | | import 'event_bus_util.dart'; |
| | | |
| | | class PermissionUtil { |
| | | static var deniedSets = Set(); |
| | | |
| | | static Permission getLocationPermission() { |
| | | if (Platform.isAndroid) { |
| | | //获取系统版本 |
| | | if (Global.androidSDK != null && Global.androidSDK! < 29) { |
| | | return Permission.locationAlways; |
| | | } else { |
| | | return Permission.location; |
| | | } |
| | | } else { |
| | | return Permission.locationAlways; |
| | | } |
| | | } |
| | | |
| | | static Future _loadDeniedPermissions() async { |
| | | //加载 |
| | | SharedPreferences prefs = await SharedPreferences.getInstance(); |
| | | List<String>? list = prefs.getStringList("permission_delay_list"); |
| | | deniedSets.clear(); |
| | | if (list != null) { |
| | | deniedSets.addAll(list); |
| | | } |
| | | |
| | | print(jsonEncode(list)); |
| | | } |
| | | |
| | | static Future _denyPermission(Permission permission) async { |
| | | SharedPreferences prefs = await SharedPreferences.getInstance(); |
| | | List<String>? list = prefs.getStringList("permission_delay_list"); |
| | | list ??= []; |
| | | bool hasAdd = false; |
| | | list.forEach((element) { |
| | | if (element == permission.value.toString()) { |
| | | hasAdd = true; |
| | | } |
| | | }); |
| | | |
| | | if (!hasAdd) { |
| | | list.add(permission.value.toString()); |
| | | } |
| | | await prefs.setStringList("permission_delay_list", list); |
| | | } |
| | | |
| | | static Future _removeDenyPermission(Permission permission) async { |
| | | SharedPreferences prefs = await SharedPreferences.getInstance(); |
| | | List<String>? list = prefs.getStringList("permission_delay_list"); |
| | | list ??= []; |
| | | |
| | | for (int i = 0; i < list.length; i++) { |
| | | if (list[i] == permission.value.toString()) { |
| | | list.removeAt(i); |
| | | i--; |
| | | } |
| | | } |
| | | await prefs.setStringList("permission_delay_list", list); |
| | | } |
| | | |
| | | ///打开应用设置 |
| | | static openAppSetting() async { |
| | | await openAppSettings(); |
| | | } |
| | | |
| | | ///打开权限, |
| | | ///force:强制打开,权限被拒绝后记录 |
| | | static Future<PermissionStatus> openPermission(Permission permission, |
| | | {force: false}) async { |
| | | await _loadDeniedPermissions(); |
| | | PermissionStatus status = await permission.status; |
| | | PermissionStatus? resultStatus; |
| | | switch (status) { |
| | | //Android授权 |
| | | case PermissionStatus.granted: |
| | | resultStatus = status; |
| | | break; |
| | | //Android拒绝 |
| | | case PermissionStatus.denied: |
| | | if (deniedSets.contains(permission.value.toString()) && !force) { |
| | | resultStatus = PermissionStatus.denied; |
| | | } else { |
| | | resultStatus = await permission.request(); |
| | | } |
| | | break; |
| | | //Android禁止后不再提示 |
| | | case PermissionStatus.permanentlyDenied: |
| | | if (deniedSets.contains(permission.value.toString()) && !force) { |
| | | resultStatus = PermissionStatus.permanentlyDenied; |
| | | } else { |
| | | bool success = await openAppSettings(); |
| | | resultStatus = await permission.status; |
| | | } |
| | | break; |
| | | //IOS |
| | | case PermissionStatus.limited: |
| | | if (deniedSets.contains(permission.value.toString()) && !force) { |
| | | resultStatus = PermissionStatus.limited; |
| | | } else { |
| | | resultStatus = await permission.request(); |
| | | } |
| | | break; |
| | | //IOS |
| | | case PermissionStatus.restricted: |
| | | if (deniedSets.contains(permission.value.toString()) && !force) { |
| | | resultStatus = PermissionStatus.restricted; |
| | | } else { |
| | | resultStatus = await permission.request(); |
| | | } |
| | | break; |
| | | } |
| | | |
| | | if (resultStatus == PermissionStatus.granted) { |
| | | _removeDenyPermission(permission); |
| | | } else { |
| | | _denyPermission(permission); |
| | | } |
| | | |
| | | return resultStatus; |
| | | } |
| | | |
| | | ///批量打开权限 |
| | | static openPermissions(List<Permission> permissions, {force: true}) async { |
| | | await _loadDeniedPermissions(); |
| | | List<Permission> requestPS = []; |
| | | permissions.forEach((element) { |
| | | if (!deniedSets.contains(element.value.toString())) { |
| | | requestPS.add(element); |
| | | } |
| | | }); |
| | | |
| | | if (requestPS.isNotEmpty) { |
| | | await requestPS.request(); |
| | | } |
| | | } |
| | | |
| | | ///关闭权限 |
| | | static Future<PermissionStatus> closePermission(Permission permission) async { |
| | | bool success = await openAppSettings(); |
| | | return await permission.status; |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:convert'; |
| | | import 'dart:io'; |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/services.dart'; |
| | | import 'package:fluwx_no_pay/fluwx_no_pay.dart'; |
| | | import 'package:flutter/services.dart'; |
| | | import '../api/http.dart'; |
| | | import '../model/user/user_info.dart'; |
| | | import '../utils/event_bus_util.dart'; |
| | | import '../utils/pageutils.dart'; |
| | | import '../utils/user_util.dart'; |
| | | |
| | | import 'global.dart'; |
| | | |
| | | class PushUtil { |
| | | |
| | | static init(BuildContext context) async { |
| | | //如果登录了就设置用户的uid |
| | | bool isLogin = await UserUtil.isLogin(); |
| | | if (isLogin) { |
| | | UserInfo? user = await UserUtil.getUserInfo(); |
| | | setAlias(user!.id!.toString()); |
| | | } |
| | | } |
| | | |
| | | ///添加alias |
| | | static Future setAlias(String alias) async { |
| | | // try { |
| | | // await _jpush.setAlias(alias); |
| | | // } catch (e) {} |
| | | } |
| | | |
| | | //删除alias |
| | | static Future removeAlias() async { |
| | | // await _jpush.deleteAlias(); |
| | | } |
| | | } |
New file |
| | |
| | | |
| | | import 'package:shared_preferences/shared_preferences.dart'; |
| | | |
| | | class SettingUtil { |
| | | //设置推送 |
| | | static Future<bool> setPush(bool enable) async { |
| | | SharedPreferences prefs = await SharedPreferences.getInstance(); |
| | | return await prefs.setBool("setting_push", enable); |
| | | } |
| | | |
| | | ///是否允许推送 |
| | | static Future<bool> isEnablePush() async { |
| | | SharedPreferences prefs = await SharedPreferences.getInstance(); |
| | | bool? result = prefs.getBool("setting_push"); |
| | | result ??= true; |
| | | return result; |
| | | } |
| | | |
| | | //设置推荐广告 |
| | | static Future<bool> setRecommendAd(bool enable) async { |
| | | SharedPreferences prefs = await SharedPreferences.getInstance(); |
| | | return await prefs.setBool("setting_recommend_ad", enable); |
| | | } |
| | | |
| | | ///是否允许推荐广告 |
| | | static Future<bool> isEnableRecommendAd() async { |
| | | SharedPreferences prefs = await SharedPreferences.getInstance(); |
| | | bool? result = prefs.getBool("setting_recommend_ad"); |
| | | result ??= true; |
| | | return result; |
| | | } |
| | | |
| | | //设置推荐广告 |
| | | static Future<bool> setLocationSpan(int second) async { |
| | | SharedPreferences prefs = await SharedPreferences.getInstance(); |
| | | return await prefs.setInt("setting_location_span", second); |
| | | } |
| | | |
| | | ///是否允许推荐广告 |
| | | static Future<int> getLocationSpan() async { |
| | | SharedPreferences prefs = await SharedPreferences.getInstance(); |
| | | int? result = prefs.getInt("setting_location_span"); |
| | | result ??= 30; |
| | | return result; |
| | | } |
| | | } |
New file |
| | |
| | | import 'package:flutter/services.dart'; |
| | | |
| | | class DataMethodChannel extends MethodChannel { |
| | | DataMethodChannel(String name) : super(name) { |
| | | setMethodCallHandler((call) { |
| | | switch (call.method) { |
| | | } |
| | | return Future.value(); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | DataMethodChannel dataMethodChannel = DataMethodChannel('com.yeshi.video/data'); |
| | | |
| | | class MySharedPreferences { |
| | | static MySharedPreferences? _instance; |
| | | |
| | | static MySharedPreferences getInstance() { |
| | | _instance ??= MySharedPreferences(); |
| | | return _instance!; |
| | | } |
| | | |
| | | Future<String?> getString(String key) async { |
| | | String? value = await _getValue(key); |
| | | return value; |
| | | } |
| | | |
| | | Future<bool?> getBool(String key) async { |
| | | String? value = await _getValue(key); |
| | | if (value == null) { |
| | | return null; |
| | | } |
| | | return value as bool; |
| | | } |
| | | |
| | | Future<int?> getInt(String key) async { |
| | | String? value = await _getValue(key); |
| | | if (value == null) { |
| | | return null; |
| | | } |
| | | return int.parse(value); |
| | | } |
| | | |
| | | Future<bool> setString(String key, String value) async { |
| | | return await _setValue(key, value); |
| | | } |
| | | |
| | | Future<bool> setBool(String key, bool value) async { |
| | | return await _setValue(key, value.toString()); |
| | | } |
| | | |
| | | Future<bool> setInt(String key, bool value) async { |
| | | return await _setValue(key, value.toString()); |
| | | } |
| | | |
| | | Future<bool> remove(String key) async { |
| | | return await _removeValue(key); |
| | | } |
| | | |
| | | Future<bool> _setValue(String key, String? value) async { |
| | | String? success = await dataMethodChannel |
| | | .invokeMethod<String>("setSharedValue", {"key": key, "value": value}); |
| | | print("MySharedPreferences 设置值:$success"); |
| | | if (success != null && success == "1") { |
| | | return true; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | Future<bool> _removeValue(String key) async { |
| | | String? success = |
| | | await dataMethodChannel.invokeMethod<String>("removeSharedValue", key); |
| | | print("MySharedPreferences 删除值:$success"); |
| | | if (success != null && success == "1") { |
| | | return true; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | Future<String?> _getValue(String key) async { |
| | | String? result = |
| | | await dataMethodChannel.invokeMethod<String>("getSharedValue", key); |
| | | print("MySharedPreferences 获取值:$result"); |
| | | return result; |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:io'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/services.dart'; |
| | | import 'package:share_plus/share_plus.dart'; |
| | | |
| | | import 'config_util.dart'; |
| | | |
| | | class ShareUtil { |
| | | static shareImg( |
| | | BuildContext context, File f, SharePlatform sharePlatform) async { |
| | | if (Platform.isAndroid) { |
| | | const platform = MethodChannel("com.yeshi.location/share"); //分析1 |
| | | bool result = false; |
| | | try { |
| | | var params = { |
| | | "path": f.path, |
| | | "platform": sharePlatform.runtimeType.toString() |
| | | }; |
| | | switch (sharePlatform) { |
| | | case SharePlatform.qq: |
| | | params["platform"] = "qq"; |
| | | break; |
| | | case SharePlatform.wx: |
| | | params["platform"] = "wx"; |
| | | break; |
| | | case SharePlatform.sina: |
| | | params["platform"] = "sina"; |
| | | break; |
| | | } |
| | | |
| | | result = await platform.invokeMethod("shareImg", params); //分析2 |
| | | } on PlatformException catch (e) { |
| | | print(e.toString()); |
| | | } |
| | | } else { |
| | | Share.shareFiles([f.path]); |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | import '../../utils/db_manager.dart'; |
| | | import 'package:sqflite/sqflite.dart'; |
| | | import 'package:path_provider/path_provider.dart'; |
| | | import 'dart:io'; |
| | | |
| | | class SQLiteUtil { |
| | | static const String DB_NAME = "hanju.db"; |
| | | |
| | | static Future<Database> _getDB() async { |
| | | var path = await getDatabasesPath() + "/$DB_NAME"; |
| | | print("databasepath:$path"); |
| | | Database database = await openDatabase(path, version: 1, |
| | | onCreate: (Database db, int version) async { |
| | | // When creating the db, create the table |
| | | List<String> tables = DBManager.getTables(); |
| | | for (var i = 0; i < tables.length; i++) { |
| | | await db.execute(tables[i]); |
| | | } |
| | | }); |
| | | return database; |
| | | } |
| | | |
| | | static Future executeSQL(String sql) async { |
| | | var db = await openDatabase(""); |
| | | await db.execute(sql); |
| | | await db.close(); |
| | | } |
| | | |
| | | static Future executeSQLWithParams(String sql, List params) async { |
| | | var db = await _getDB(); |
| | | await db.execute(sql, params); |
| | | await db.close(); |
| | | } |
| | | |
| | | static Future executeSQLs(List<String> sqls) async { |
| | | var db = await _getDB(); |
| | | for (var i = 0; i < sqls.length; i++) { |
| | | await db.execute(sqls[i]); |
| | | } |
| | | await db.close(); |
| | | } |
| | | |
| | | ///批量插入 |
| | | ///sql示例:INSERT INTO artists (name) VALUES (?) |
| | | static Future insert(String sql, List<List> list) async { |
| | | var db = await _getDB(); |
| | | await db.transaction((txn) async { |
| | | for (var i = 0; i < list.length; i++) { |
| | | await txn.rawInsert(sql, list[i]); |
| | | } |
| | | }); |
| | | await db.close(); |
| | | } |
| | | |
| | | ///查询 |
| | | static Future<List<Map>> select(String sql, List params) async { |
| | | var db = await _getDB(); |
| | | List<Map> list = await db.rawQuery(sql, params); |
| | | await db.close(); |
| | | return list; |
| | | } |
| | | |
| | | static Future<int> count(String sql, List params) async { |
| | | var db = await _getDB(); |
| | | int? count = Sqflite.firstIntValue(await db.rawQuery(sql, params)); |
| | | await db.close(); |
| | | return count!; |
| | | } |
| | | } |
New file |
| | |
| | | class StringUtil { |
| | | //是否为电话号码 |
| | | static bool isMobile(String str) { |
| | | return RegExp( |
| | | '^((13[0-9])|(15[^4])|(166)|(17[0-8])|(18[0-9])|(19[8-9])|(147,145))\\d{8}\$') |
| | | .hasMatch(str); |
| | | } |
| | | |
| | | static bool isEmail(String str) { |
| | | return RegExp( |
| | | '^(\\w)+(\\.\\w+)*@(\\w)+((\\.\\w+)+)\$') |
| | | .hasMatch(str); |
| | | } |
| | | |
| | | static bool isNullOrEmpty(String? str) { |
| | | return str==null|| str.isEmpty || str.trim().isEmpty; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | |
| | | //滑动效果 |
| | | class ColorConstant { |
| | | static const Color theme = Color(0xFFFF4D88);//Color(0xFF0E95FE); |
| | | static const Color title = Color(0xFF333333); |
| | | } |
| | | |
| | | class Constant { |
| | | |
| | | static const String IOS_APP_ID = |
| | | "1135798414"; |
| | | |
| | | //阿里云授权登录 |
| | | static const String ALIYUN_AUTH_SECRETINFO = |
| | | "v+dQBemz/CZaLI0YP/A5AxxG2YgF7IvP+RNzRkmOGln1bbd7tcMt7uvqt8dvb9ekawjGbvSZ6Y/N9WT0kpUb6lrkS11BxfhCpXtuxmOxei97xP3l5Hd8PVqPv2jPC8uVDRhl6kaAqf/6I1gwL1d7am+8w1sGkYnZ3UEgd9ljDBKjeRbbpp+KEzLiqnKWYDNqMLSRdU0BmzTSGqtkM5c1TYOZgx68NxwE2oM9VzcjQEeFP0yiQatMyNIQ5mJjbyU3zi9qiyMQaeTLHeACvqZ2XCYQBbAeqJh6DPrhIHGlfGc="; |
| | | |
| | | //隐私政策链接 |
| | | static const String PRIVACY_URL = |
| | | "http://h5.hanju.goxcw.com/hanju/privacy_ios.html"; |
| | | |
| | | //用户协议链接 |
| | | static const String PROTOCOL_URL = |
| | | "http://h5.hanju.goxcw.com/hanju/user_protocol_ios.html"; |
| | | |
| | | //微信 |
| | | static const String WX_APPID = "wxd930ea5d5a228f5f"; |
| | | static const String WX_UNIVERSAL_LINK = "https://your.univerallink.com/link/"; |
| | | |
| | | static const String APP_DOWNLOAD_LINK = |
| | | "https://a.app.qq.com/o/simple.jsp?pkgname=com.dw.zzql"; |
| | | |
| | | |
| | | static const String MAP_LINK = |
| | | "http://web.location.izzql.com/map/map.html"; |
| | | |
| | | |
| | | } |
New file |
| | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/material.dart'; |
| | | import 'package:flutter/physics.dart'; |
| | | import 'package:flutter/services.dart'; |
| | | import 'package:pull_to_refresh/pull_to_refresh.dart'; |
| | | |
| | | class UIMethodChannel extends MethodChannel { |
| | | UIMethodChannel(String name) : super(name) { |
| | | setMethodCallHandler((call) { |
| | | switch (call.method) { |
| | | case "resumePage": |
| | | {} |
| | | } |
| | | return Future.value(); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | MethodChannel uiMethodChannel = UIMethodChannel('com.yeshi.video/ui'); |
| | | |
| | | ///包裹整个页面,下拉刷新,,上拉加载的默认配置 |
| | | Widget getBasePage(Widget widget) { |
| | | return RefreshConfiguration( |
| | | headerBuilder: () => const WaterDropHeader(), |
| | | // 配置默认头部指示器,假如你每个页面的头部指示器都一样的话,你需要设置这个 |
| | | footerBuilder: () => const ClassicFooter(), |
| | | // 配置默认底部指示器 |
| | | headerTriggerDistance: 80.0, |
| | | // 头部触发刷新的越界距离 |
| | | springDescription: |
| | | const SpringDescription(stiffness: 170, damping: 16, mass: 1.9), |
| | | // 自定义回弹动画,三个属性值意义请查询flutter api |
| | | maxOverScrollExtent: 100, |
| | | //头部最大可以拖动的范围,如果发生冲出视图范围区域,请设置这个属性 |
| | | maxUnderScrollExtent: 0, |
| | | // 底部最大可以拖动的范围 |
| | | enableScrollWhenRefreshCompleted: true, |
| | | //这个属性不兼容PageView和TabBarView,如果你特别需要TabBarView左右滑动,你需要把它设置为true |
| | | enableLoadingWhenFailed: true, |
| | | //在加载失败的状态下,用户仍然可以通过手势上拉来触发加载更多 |
| | | hideFooterWhenNotFull: false, |
| | | // Viewport不满一屏时,禁用上拉加载更多功能 |
| | | enableBallisticLoad: true, |
| | | // 可以通过惯性滑动触发加载更多 |
| | | child: MaterialApp( |
| | | //国际化处理 |
| | | // localizationsDelegates: const [ |
| | | // // 这行是关键 |
| | | // RefreshLocalizations.delegate, |
| | | // ], |
| | | // supportedLocales: const [ |
| | | // Locale('en'), |
| | | // Locale('zh'), |
| | | // ], |
| | | // localeResolutionCallback: |
| | | // (Locale? locale, Iterable<Locale> supportedLocales) { |
| | | // //print("change language"); |
| | | // return locale; |
| | | // }, |
| | | home: widget)); |
| | | } |
| | | |
| | | class ToastUtil { |
| | | static toast(String text, BuildContext context) { |
| | | // Toast.show(text, context); |
| | | // showToast(text,position: ToastPosition.bottom); |
| | | print("toast:$text"); |
| | | uiMethodChannel.invokeMethod("toast", text); |
| | | } |
| | | } |
| | | |
| | | class DialogUtil { |
| | | static Future<dynamic> showDialog(BuildContext context, Dialog dialog) async { |
| | | return await showGeneralDialog( |
| | | context: context, |
| | | pageBuilder: (BuildContext buildContext, Animation<double> animation, |
| | | Animation<double> secondaryAnimation) { |
| | | return dialog; |
| | | }); |
| | | } |
| | | |
| | | static Future<dynamic> showDialogBottom(BuildContext context, Widget widget, |
| | | {BorderRadius? borderRadius, Color? backgroundColor}) async { |
| | | return await showModalBottomSheet<void>( |
| | | backgroundColor: backgroundColor ?? Colors.transparent, |
| | | shape: RoundedRectangleBorder( |
| | | borderRadius: borderRadius ?? BorderRadius.zero, |
| | | ), |
| | | context: context, |
| | | builder: (BuildContext context) { |
| | | return widget; |
| | | }); |
| | | } |
| | | } |
| | | |
| | | class DimenUtil { |
| | | //获取像素比 |
| | | static double getPixelRatio(BuildContext context) { |
| | | MediaQueryData mediaQuery = MediaQuery.of(context); |
| | | return mediaQuery.devicePixelRatio; |
| | | } |
| | | |
| | | //获取1像素的取值 |
| | | static double getOnePixel(BuildContext context) { |
| | | return 1 / MediaQuery.of(context).devicePixelRatio; |
| | | } |
| | | } |
New file |
| | |
| | | import 'dart:async'; |
| | | import 'dart:convert'; |
| | | import 'dart:ui'; |
| | | |
| | | import 'package:flutter/cupertino.dart'; |
| | | import 'package:flutter/services.dart'; |
| | | import 'package:fluwx_no_pay/fluwx_no_pay.dart' as fluwx; |
| | | import '../../api/user_api.dart'; |
| | | import '../../utils/share_preference.dart'; |
| | | import '../api/http.dart'; |
| | | import '../model/user/user_info.dart'; |
| | | import '../utils/event_bus_util.dart'; |
| | | import '../utils/string_util.dart'; |
| | | import 'package:shared_preferences/shared_preferences.dart'; |
| | | |
| | | Timer? locationTimer; |
| | | |
| | | class UserUtil { |
| | | static const _loginMessageChannel = |
| | | BasicMessageChannel('ThirdLogin', StandardMessageCodec()); |
| | | |
| | | //是否同意了用户协议 |
| | | static Future<bool> isAgreeProtocol() async { |
| | | SharedPreferences prefs = await SharedPreferences.getInstance(); |
| | | bool? agree = prefs.getBool("agree_protocol"); |
| | | if (agree == null) { |
| | | return false; |
| | | } |
| | | return agree; |
| | | } |
| | | |
| | | //设置已同意用户协议 |
| | | static Future<bool> setAgreeProtocol() async { |
| | | SharedPreferences prefs = await SharedPreferences.getInstance(); |
| | | return prefs.setBool("agree_protocol", true); |
| | | } |
| | | |
| | | ///微信登录 |
| | | static void loginWX() async { |
| | | fluwx |
| | | .sendWeChatAuth(scope: "snsapi_userinfo", state: "wechat_sdk_demo_test") |
| | | .then((value) {}); |
| | | } |
| | | |
| | | ///QQ登录 |
| | | static Future<Map> loginQQ() async { |
| | | Map value = await _loginMessageChannel.send({"method": "loginQQ"}) as Map; |
| | | return value; |
| | | } |
| | | |
| | | //是否已经登录 |
| | | static Future<bool> isLogin() async { |
| | | UserInfo? user = await getUserInfo(); |
| | | return user != null; |
| | | } |
| | | |
| | | //用户信息 |
| | | static Future<UserInfo?> getUserInfo() async { |
| | | |
| | | // return null; |
| | | |
| | | MySharedPreferences prefs = MySharedPreferences.getInstance(); |
| | | String? result =await prefs.getString("user_info"); |
| | | if (StringUtil.isNullOrEmpty(result)) { |
| | | return null; |
| | | } else { |
| | | return UserInfo.fromJson(jsonDecode(result!)); |
| | | } |
| | | } |
| | | |
| | | static Future<UserInfo?> updateUserInfo(BuildContext context) async { |
| | | String? uid = await getUid(); |
| | | if (uid == null) { |
| | | return null; |
| | | } |
| | | |
| | | Map<String, dynamic>? result = await UserApiUtil.getUserInfo(context, uid); |
| | | var code = result!["IsPost"]; |
| | | if (code == "true") { |
| | | UserInfo user = UserInfo.fromJson(result["Data"]); |
| | | //保存用户信息 |
| | | UserUtil.setUserInfo(user); |
| | | |
| | | return user; |
| | | } else if (code == 80001 || code == 80002) { |
| | | await logout(); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | static Future<String?> getUid() async { |
| | | UserInfo? user = await getUserInfo(); |
| | | if (user != null) { |
| | | return user.id; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | static Future setUserInfo(UserInfo user) async { |
| | | MySharedPreferences prefs = MySharedPreferences.getInstance(); |
| | | await prefs.setString("user_info", jsonEncode(user)); |
| | | } |
| | | |
| | | //退出登录 |
| | | static Future logout() async { |
| | | await _logout(); |
| | | eventBus.fire(LoginEventBus(false)); |
| | | } |
| | | |
| | | static Future _logout() async { |
| | | MySharedPreferences prefs = MySharedPreferences.getInstance(); |
| | | await prefs.remove("user_info"); |
| | | } |
| | | } |
New file |
| | |
| | | ///搜索记录帮助类 |
| | | import 'package:shared_preferences/shared_preferences.dart'; |
| | | |
| | | class SearchRecordUtil { |
| | | static void addRecord(String st) async { |
| | | if (st.isEmpty || st.trim().isEmpty) { |
| | | return; |
| | | } |
| | | final prefs = await SharedPreferences.getInstance(); |
| | | List<String> list = await listRecord(); |
| | | |
| | | //删除重复的数据 |
| | | for (var i = 0; i < list.length; i++) { |
| | | if (list[i] == st.trim()) { |
| | | list.removeAt(i); |
| | | i--; |
| | | } |
| | | } |
| | | |
| | | list.insert(0, st.trim()); |
| | | if (list.length > 10) { |
| | | list = list.sublist(0, 10); |
| | | } |
| | | await prefs.setStringList("searchRecord", list); |
| | | } |
| | | |
| | | static void clearRecord() async { |
| | | final prefs = await SharedPreferences.getInstance(); |
| | | await prefs.setStringList("searchRecord", []); |
| | | } |
| | | |
| | | static Future<List<String>> listRecord() async { |
| | | final prefs = await SharedPreferences.getInstance(); |
| | | List<String>? list = prefs.getStringList("searchRecord"); |
| | | list ??= []; |
| | | return list; |
| | | } |
| | | } |
New file |
| | |
| | | import '../../model/video/video_model.dart'; |
| | | |
| | | class VideoUtil { |
| | | static String getVPicture(VideoInfoModel video) { |
| | | if (video.vpicture != null && video.vpicture!.isNotEmpty) { |
| | | return video.vpicture!; |
| | | } |
| | | return video.picture!; |
| | | } |
| | | |
| | | static String getHPicture(VideoInfoModel video) { |
| | | if (video.hpicture != null && video.hpicture!.isNotEmpty) { |
| | | return video.hpicture!; |
| | | } |
| | | return video.picture!; |
| | | } |
| | | |
| | | |
| | | } |