admin
2022-03-31 02f1c9fd2c594323f772f8e8f0f2187a285c1749
service服务bug修复
123个文件已添加
14512 ■■■■■ 已修改文件
src/main/resources/code/android/library-ad/src/main/res/xml/file_paths.xml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/android/library-ad/src/main/res/xml/gdt_file_path.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/android/library-flutter/src/main/java/com/demo/library_flutter/FlutterMineFragment.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/android/library-flutter/src/main/java/com/demo/library_flutter/FlutterRecommendFragment.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/android/library-flutter/src/main/res/layout/activity_common_flutter.xml 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/android/library-flutter/src/main/res/xml/filepaths.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/images/flutter-mark-square-64.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/advice/ic_advice_top.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/advice/icon_advice_like.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/advice/icon_advice_msg.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/common/ic_empty.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/common/ic_network_error.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/common/icon_array_right.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/common/icon_browser_link.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/common/icon_browser_out.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/common/icon_browser_refresh.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/common/icon_check_false.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/common/icon_check_true.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/home/icon_home_category_novel.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/ic_portrait_default.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/icon.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/icon_mine_fun_about_us.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/icon_mine_fun_feedback.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/icon_mine_fun_input.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/icon_mine_fun_privacy.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/icon_mine_fun_settings.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/icon_mine_fun_share.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/icon_person_info_input.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/icon_search_home.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/login/ic_login_logo.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/login/icon_code.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/login/icon_email.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/login/icon_pwd.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/video/ic_pause.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/video/ic_play.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/video/ic_play_back.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/video/icon_check_false.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/video/icon_check_true.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/video/icon_collected.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/video/icon_download.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/video/icon_share.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/assets/imgs/video/icon_uncollected.png 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/api/config_api.dart 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/api/feed_back_api.dart 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/api/http.dart 184 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/api/user_api.dart 196 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/api/video_api.dart 309 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/home.dart 418 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/mine.dart 632 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/model/common/adinfo_model.dart 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/model/common/http_model.dart 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/model/user/user_info.dart 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/model/video/home_ad_model.dart 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/model/video/home_type_model.dart 221 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/model/video/search_special_model.dart 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/model/video/video_attention_model.dart 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/model/video/video_detail_model.dart 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/model/video/video_model.dart 381 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/model/video/video_play_url_model.dart 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/model/video/watch_record_model.dart 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/splash.dart 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/common/browser.dart 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/demo_page.dart 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/main.dart 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/main/home.dart 539 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/mine/about_us.dart 166 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/mine/advice.dart 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/mine/advice_submit.dart 198 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/mine/email_login.dart 381 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/mine/email_pwd_find.dart 346 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/mine/email_register.dart 421 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/mine/login.dart 496 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/mine/person_info.dart 536 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/mine/settings.dart 363 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/search/search.dart 369 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/search/search_result.dart 286 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/test.dart 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/video/video_attention_list.dart 228 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/video/video_collected_list.dart 296 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/video/video_detail.dart 935 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/video/video_download_list.dart 302 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/video/video_list.dart 166 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/video/video_player_browser.dart 321 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/video/video_scan_record_list.dart 321 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/widget/ad_express.dart 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/widget/base_ui.dart 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/widget/button.dart 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/widget/capture.dart 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/widget/common_ui.dart 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/widget/dialog.dart 415 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/widget/nav.dart 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/widget/refresh_listview.dart 244 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/widget/search_widget.dart 313 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/widget/sos_ui.dart 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/ui/widget/video_item.dart 755 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/ad_util.dart 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/app_util.dart 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/cache_util.dart 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/config_util.dart 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/db_manager.dart 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/des/crypto_util.dart 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/des/des.dart 415 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/des/number_utils.dart 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/des/padding.dart 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/encrypt_util.dart 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/event_bus_util.dart 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/global.dart 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/image_util.dart 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/jsinterface.dart 250 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/jump_page.dart 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/pageutils.dart 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/permission_util.dart 150 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/push_util.dart 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/setting_util.dart 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/share_preference.dart 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/share_utils.dart 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/sqlite_utils.dart 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/string_util.dart 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/ui_constant.dart 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/ui_utils.dart 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/user_util.dart 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/video/search_record_util.dart 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/flutter_module/lib/utils/video/video_util.dart 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/code/android/library-ad/src/main/res/xml/file_paths.xml
New file
@@ -0,0 +1,8 @@
<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>
src/main/resources/code/android/library-ad/src/main/res/xml/gdt_file_path.xml
New file
@@ -0,0 +1,9 @@
<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>
src/main/resources/code/android/library-flutter/src/main/java/com/demo/library_flutter/FlutterMineFragment.java
New file
@@ -0,0 +1,35 @@
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);
    }
}
src/main/resources/code/android/library-flutter/src/main/java/com/demo/library_flutter/FlutterRecommendFragment.java
New file
@@ -0,0 +1,37 @@
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);
    }
}
src/main/resources/code/android/library-flutter/src/main/res/layout/activity_common_flutter.xml
New file
@@ -0,0 +1,14 @@
<?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>
src/main/resources/code/android/library-flutter/src/main/res/xml/filepaths.xml
New file
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <cache-path
        name="files_path"
        path="/"></cache-path>
</paths>
src/main/resources/code/flutter_module/assets/images/flutter-mark-square-64.png
src/main/resources/code/flutter_module/assets/imgs/advice/ic_advice_top.png
src/main/resources/code/flutter_module/assets/imgs/advice/icon_advice_like.png
src/main/resources/code/flutter_module/assets/imgs/advice/icon_advice_msg.png
src/main/resources/code/flutter_module/assets/imgs/common/ic_empty.png
src/main/resources/code/flutter_module/assets/imgs/common/ic_network_error.png
src/main/resources/code/flutter_module/assets/imgs/common/icon_array_right.png
src/main/resources/code/flutter_module/assets/imgs/common/icon_browser_link.png
src/main/resources/code/flutter_module/assets/imgs/common/icon_browser_out.png
src/main/resources/code/flutter_module/assets/imgs/common/icon_browser_refresh.png
src/main/resources/code/flutter_module/assets/imgs/common/icon_check_false.png
src/main/resources/code/flutter_module/assets/imgs/common/icon_check_true.png
src/main/resources/code/flutter_module/assets/imgs/home/icon_home_category_novel.png
src/main/resources/code/flutter_module/assets/imgs/ic_portrait_default.png
src/main/resources/code/flutter_module/assets/imgs/icon.png
src/main/resources/code/flutter_module/assets/imgs/icon_mine_fun_about_us.png
src/main/resources/code/flutter_module/assets/imgs/icon_mine_fun_feedback.png
src/main/resources/code/flutter_module/assets/imgs/icon_mine_fun_input.png
src/main/resources/code/flutter_module/assets/imgs/icon_mine_fun_privacy.png
src/main/resources/code/flutter_module/assets/imgs/icon_mine_fun_settings.png
src/main/resources/code/flutter_module/assets/imgs/icon_mine_fun_share.png
src/main/resources/code/flutter_module/assets/imgs/icon_person_info_input.png
src/main/resources/code/flutter_module/assets/imgs/icon_search_home.png
src/main/resources/code/flutter_module/assets/imgs/login/ic_login_logo.png
src/main/resources/code/flutter_module/assets/imgs/login/icon_code.png
src/main/resources/code/flutter_module/assets/imgs/login/icon_email.png
src/main/resources/code/flutter_module/assets/imgs/login/icon_pwd.png
src/main/resources/code/flutter_module/assets/imgs/video/ic_pause.png
src/main/resources/code/flutter_module/assets/imgs/video/ic_play.png
src/main/resources/code/flutter_module/assets/imgs/video/ic_play_back.png
src/main/resources/code/flutter_module/assets/imgs/video/icon_check_false.png
src/main/resources/code/flutter_module/assets/imgs/video/icon_check_true.png
src/main/resources/code/flutter_module/assets/imgs/video/icon_collected.png
src/main/resources/code/flutter_module/assets/imgs/video/icon_download.png
src/main/resources/code/flutter_module/assets/imgs/video/icon_share.png
src/main/resources/code/flutter_module/assets/imgs/video/icon_uncollected.png
src/main/resources/code/flutter_module/lib/api/config_api.dart
New file
@@ -0,0 +1,17 @@
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;
  }
}
src/main/resources/code/flutter_module/lib/api/feed_back_api.dart
New file
@@ -0,0 +1,34 @@
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;
  }
}
src/main/resources/code/flutter_module/lib/api/http.dart
New file
@@ -0,0 +1,184 @@
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;
  }
}
src/main/resources/code/flutter_module/lib/api/user_api.dart
New file
@@ -0,0 +1,196 @@
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;
  }
}
src/main/resources/code/flutter_module/lib/api/video_api.dart
New file
@@ -0,0 +1,309 @@
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;
  }
}
src/main/resources/code/flutter_module/lib/home.dart
New file
@@ -0,0 +1,418 @@
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);
  }
}
src/main/resources/code/flutter_module/lib/mine.dart
New file
@@ -0,0 +1,632 @@
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,
              ),
            ],
          )),
    );
  }
}
src/main/resources/code/flutter_module/lib/model/common/adinfo_model.dart
New file
@@ -0,0 +1,29 @@
/// 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;
  }
}
src/main/resources/code/flutter_module/lib/model/common/http_model.dart
New file
@@ -0,0 +1,16 @@
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});
}
src/main/resources/code/flutter_module/lib/model/user/user_info.dart
New file
@@ -0,0 +1,73 @@
/// 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;
  }
}
src/main/resources/code/flutter_module/lib/model/video/home_ad_model.dart
New file
@@ -0,0 +1,74 @@
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;
  }
}
src/main/resources/code/flutter_module/lib/model/video/home_type_model.dart
New file
@@ -0,0 +1,221 @@
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;
  }
}
src/main/resources/code/flutter_module/lib/model/video/search_special_model.dart
New file
@@ -0,0 +1,36 @@
/// 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;
  }
}
src/main/resources/code/flutter_module/lib/model/video/video_attention_model.dart
New file
@@ -0,0 +1,49 @@
/// 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;
  }
}
src/main/resources/code/flutter_module/lib/model/video/video_detail_model.dart
src/main/resources/code/flutter_module/lib/model/video/video_model.dart
New file
@@ -0,0 +1,381 @@
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;
  }
}
src/main/resources/code/flutter_module/lib/model/video/video_play_url_model.dart
New file
@@ -0,0 +1,53 @@
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;
  }
}
src/main/resources/code/flutter_module/lib/model/video/watch_record_model.dart
New file
@@ -0,0 +1,69 @@
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;
  }
}
src/main/resources/code/flutter_module/lib/splash.dart
New file
@@ -0,0 +1,10 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'mine.dart';
void main(){
  runApp(MinePage(title: ""));
}
src/main/resources/code/flutter_module/lib/ui/common/browser.dart
New file
@@ -0,0 +1,132 @@
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;
                      }
                    });
                  },
                ))
              ],
            )));
  }
}
src/main/resources/code/flutter_module/lib/ui/demo_page.dart
New file
@@ -0,0 +1,81 @@
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("结束页面"),)
          ],
        ));
  }
}
src/main/resources/code/flutter_module/lib/ui/main.dart
New file
@@ -0,0 +1,16 @@
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();
}
src/main/resources/code/flutter_module/lib/ui/main/home.dart
New file
@@ -0,0 +1,539 @@
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;
}
src/main/resources/code/flutter_module/lib/ui/mine/about_us.dart
New file
@@ -0,0 +1,166 @@
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),
            )
          ],
        ),
      ),
    );
  }
}
src/main/resources/code/flutter_module/lib/ui/mine/advice.dart
New file
@@ -0,0 +1,153 @@
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,
            )
          ],
        ),
      ),
    );
  }
}
src/main/resources/code/flutter_module/lib/ui/mine/advice_submit.dart
New file
@@ -0,0 +1,198 @@
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;
  }
}
src/main/resources/code/flutter_module/lib/ui/mine/email_login.dart
New file
@@ -0,0 +1,381 @@
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>登录即表明同意&nbsp;<a href='${Constant.PROTOCOL_URL}'>用户协议</a>&nbsp;和&nbsp;<a href='${Constant.PRIVACY_URL}'>隐私政策</a>&nbsp;</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);
                    }
                  });
                },
              ),
            ],
          );
  }
}
src/main/resources/code/flutter_module/lib/ui/mine/email_pwd_find.dart
New file
@@ -0,0 +1,346 @@
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);
              }
            });
          },
        ),
      ],
    );
  }
}
src/main/resources/code/flutter_module/lib/ui/mine/email_register.dart
New file
@@ -0,0 +1,421 @@
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>登录即表明同意&nbsp;<a href='${Constant.PROTOCOL_URL}'>用户协议</a>&nbsp;和&nbsp;<a href='${Constant.PRIVACY_URL}'>隐私政策</a>&nbsp;</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);
              }
            });
          },
        ),
      ],
    );
  }
}
src/main/resources/code/flutter_module/lib/ui/mine/login.dart
New file
@@ -0,0 +1,496 @@
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>登录即表明同意&nbsp;<a href='${Constant.PROTOCOL_URL}'>用户协议</a>&nbsp;和&nbsp;<a href='${Constant.PRIVACY_URL}'>隐私政策</a>&nbsp;</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),
              )
            ],
          )),
    );
  }
}
src/main/resources/code/flutter_module/lib/ui/mine/person_info.dart
New file
@@ -0,0 +1,536 @@
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;
      });
    });
  }
}
src/main/resources/code/flutter_module/lib/ui/mine/settings.dart
New file
@@ -0,0 +1,363 @@
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,
                    )))
          ])),
    );
  }
}
src/main/resources/code/flutter_module/lib/ui/search/search.dart
New file
@@ -0,0 +1,369 @@
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);
}
src/main/resources/code/flutter_module/lib/ui/search/search_result.dart
New file
@@ -0,0 +1,286 @@
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;
}
src/main/resources/code/flutter_module/lib/ui/test.dart
New file
@@ -0,0 +1,45 @@
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("点击事件"),
                )
              ],
            ))));
  }
}
src/main/resources/code/flutter_module/lib/ui/video/video_attention_list.dart
New file
@@ -0,0 +1,228 @@
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);
                      },
                    )))
          ],
        ));
  }
}
src/main/resources/code/flutter_module/lib/ui/video/video_collected_list.dart
New file
@@ -0,0 +1,296 @@
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}");
              }
            });
          },
        ));
  }
}
src/main/resources/code/flutter_module/lib/ui/video/video_detail.dart
New file
@@ -0,0 +1,935 @@
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 }
src/main/resources/code/flutter_module/lib/ui/video/video_download_list.dart
New file
@@ -0,0 +1,302 @@
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 "";
    }
  }
}
src/main/resources/code/flutter_module/lib/ui/video/video_list.dart
New file
@@ -0,0 +1,166 @@
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);
              },
            )),
          ],
        ));
  }
}
src/main/resources/code/flutter_module/lib/ui/video/video_player_browser.dart
New file
@@ -0,0 +1,321 @@
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;
                      }
                    });
                  },
                ))
              ],
            )));
  }
}
src/main/resources/code/flutter_module/lib/ui/video/video_scan_record_list.dart
New file
@@ -0,0 +1,321 @@
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 "";
    }
  }
}
src/main/resources/code/flutter_module/lib/ui/widget/ad_express.dart
New file
@@ -0,0 +1,149 @@
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("");
    });
  }
}
src/main/resources/code/flutter_module/lib/ui/widget/base_ui.dart
New file
@@ -0,0 +1,2 @@
import 'package:flutter/cupertino.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
src/main/resources/code/flutter_module/lib/ui/widget/button.dart
New file
@@ -0,0 +1,138 @@
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,
                  ),
          )),
    );
  }
}
src/main/resources/code/flutter_module/lib/ui/widget/capture.dart
New file
@@ -0,0 +1,47 @@
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 "";
  }
}
src/main/resources/code/flutter_module/lib/ui/widget/common_ui.dart
New file
@@ -0,0 +1,35 @@
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;
}
src/main/resources/code/flutter_module/lib/ui/widget/dialog.dart
New file
@@ -0,0 +1,415 @@
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,
                        ))
                  ],
                ))));
  }
}
src/main/resources/code/flutter_module/lib/ui/widget/nav.dart
New file
@@ -0,0 +1,129 @@
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()
                  ]),
                ))
          ],
        ),
      )
    ]);
  }
}
src/main/resources/code/flutter_module/lib/ui/widget/refresh_listview.dart
New file
@@ -0,0 +1,244 @@
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 }
src/main/resources/code/flutter_module/lib/ui/widget/search_widget.dart
New file
@@ -0,0 +1,313 @@
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;
}
src/main/resources/code/flutter_module/lib/ui/widget/sos_ui.dart
New file
@@ -0,0 +1,105 @@
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;
  }
}
src/main/resources/code/flutter_module/lib/ui/widget/video_item.dart
New file
@@ -0,0 +1,755 @@
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);
}
src/main/resources/code/flutter_module/lib/utils/ad_util.dart
New file
@@ -0,0 +1,92 @@
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;
  }
}
src/main/resources/code/flutter_module/lib/utils/app_util.dart
New file
@@ -0,0 +1,70 @@
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);
  }
}
src/main/resources/code/flutter_module/lib/utils/cache_util.dart
New file
@@ -0,0 +1,57 @@
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();
    }
  }
}
src/main/resources/code/flutter_module/lib/utils/config_util.dart
New file
@@ -0,0 +1,56 @@
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 }
src/main/resources/code/flutter_module/lib/utils/db_manager.dart
New file
@@ -0,0 +1,99 @@
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, []);
  }
}
src/main/resources/code/flutter_module/lib/utils/des/crypto_util.dart
New file
@@ -0,0 +1,34 @@
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();
  }
}
src/main/resources/code/flutter_module/lib/utils/des/des.dart
New file
@@ -0,0 +1,415 @@
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;
  }
}
src/main/resources/code/flutter_module/lib/utils/des/number_utils.dart
New file
@@ -0,0 +1,95 @@
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';
    }
  }
}
src/main/resources/code/flutter_module/lib/utils/des/padding.dart
New file
@@ -0,0 +1,19 @@
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;
  }
}
src/main/resources/code/flutter_module/lib/utils/encrypt_util.dart
New file
@@ -0,0 +1,10 @@
import 'dart:convert';
import 'package:crypto/crypto.dart';
class EncryptUtil {
  static String MD5(String data) {
    return md5.convert(utf8.encode(data)).toString();
  }
}
src/main/resources/code/flutter_module/lib/utils/event_bus_util.dart
New file
@@ -0,0 +1,10 @@
import 'package:event_bus/event_bus.dart';
EventBus eventBus = EventBus();
class LoginEventBus {
  final bool isLogin;
  LoginEventBus(this.isLogin);
}
src/main/resources/code/flutter_module/lib/utils/global.dart
New file
@@ -0,0 +1,28 @@
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;
  }
}
src/main/resources/code/flutter_module/lib/utils/image_util.dart
New file
@@ -0,0 +1,36 @@
//图片工具
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;
  }
}
src/main/resources/code/flutter_module/lib/utils/jsinterface.dart
New file
@@ -0,0 +1,250 @@
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;
          }
        }
      });
    });
  }
}
src/main/resources/code/flutter_module/lib/utils/jump_page.dart
New file
@@ -0,0 +1,40 @@
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);
        }
      });
    }
  }
}
src/main/resources/code/flutter_module/lib/utils/pageutils.dart
New file
@@ -0,0 +1,95 @@
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;
}
src/main/resources/code/flutter_module/lib/utils/permission_util.dart
New file
@@ -0,0 +1,150 @@
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;
  }
}
src/main/resources/code/flutter_module/lib/utils/push_util.dart
New file
@@ -0,0 +1,39 @@
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();
  }
}
src/main/resources/code/flutter_module/lib/utils/setting_util.dart
New file
@@ -0,0 +1,46 @@
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;
  }
}
src/main/resources/code/flutter_module/lib/utils/share_preference.dart
New file
@@ -0,0 +1,86 @@
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;
  }
}
src/main/resources/code/flutter_module/lib/utils/share_utils.dart
New file
@@ -0,0 +1,40 @@
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]);
    }
  }
}
src/main/resources/code/flutter_module/lib/utils/sqlite_utils.dart
New file
@@ -0,0 +1,69 @@
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!;
  }
}
src/main/resources/code/flutter_module/lib/utils/string_util.dart
New file
@@ -0,0 +1,19 @@
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;
  }
}
src/main/resources/code/flutter_module/lib/utils/ui_constant.dart
New file
@@ -0,0 +1,40 @@
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";
}
src/main/resources/code/flutter_module/lib/utils/ui_utils.dart
New file
@@ -0,0 +1,107 @@
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;
  }
}
src/main/resources/code/flutter_module/lib/utils/user_util.dart
New file
@@ -0,0 +1,114 @@
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");
  }
}
src/main/resources/code/flutter_module/lib/utils/video/search_record_util.dart
New file
@@ -0,0 +1,38 @@
///搜索记录帮助类
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;
  }
}
src/main/resources/code/flutter_module/lib/utils/video/video_util.dart
New file
@@ -0,0 +1,19 @@
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!;
  }
}