├── .github ├── FUNDING.yml └── workflows │ └── main.yml ├── .gitignore ├── .metadata ├── LICENSE ├── README-en.md ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── suntengfei │ │ │ │ └── easy_tv_live │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── launch_image.png │ │ │ ├── mipmap-ldpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── playstore-icon.png │ │ │ ├── values-night-v27 │ │ │ └── styles.xml │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ ├── values-v27 │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets └── images │ ├── appreciate.png │ ├── logo.png │ └── video_bg.png ├── devtools_options.yaml ├── img_1.jpeg ├── img_2.jpeg ├── img_3.jpeg ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── icon-1024.png │ │ │ ├── icon-20@2x.png │ │ │ ├── icon-20@3x.png │ │ │ ├── icon-29@2x.png │ │ │ ├── icon-29@3x.png │ │ │ ├── icon-38@2x.png │ │ │ ├── icon-38@3x.png │ │ │ ├── icon-40@2x.png │ │ │ ├── icon-40@3x.png │ │ │ ├── icon-60@2x.png │ │ │ ├── icon-60@3x.png │ │ │ ├── icon-64@2x.png │ │ │ ├── icon-64@3x.png │ │ │ ├── icon-68@2x.png │ │ │ ├── icon-76@2x.png │ │ │ └── icon-83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h └── RunnerTests │ └── RunnerTests.swift ├── lib ├── channel_drawer_page.dart ├── entity │ ├── font_model.dart │ ├── play_channel_list_model.dart │ └── subScribe_model.dart ├── generated │ ├── intl │ │ ├── messages_all.dart │ │ ├── messages_en_US.dart │ │ └── messages_zh_CN.dart │ └── l10n.dart ├── l10n │ ├── intl_en_US.arb │ └── intl_zh_CN.arb ├── live_home_page.dart ├── main.dart ├── mobile_video_widget.dart ├── provider │ ├── download_provider.dart │ └── theme_provider.dart ├── router_keys.dart ├── setting │ ├── qr_scan_page.dart │ ├── reward_page.dart │ ├── setting_beautify_page.dart │ ├── setting_font_page.dart │ ├── setting_page.dart │ └── subscribe_page.dart ├── table_video_widget.dart ├── tv │ ├── html_string.dart │ ├── tv_appreciate_page.dart │ ├── tv_channel_drawer_page.dart │ ├── tv_page.dart │ └── tv_setting_page.dart ├── util │ ├── bing_util.dart │ ├── check_version_util.dart │ ├── date_util.dart │ ├── device_sync_util.dart │ ├── env_util.dart │ ├── epg_util.dart │ ├── font_util.dart │ ├── http_util.dart │ ├── latency_checker_util.dart │ ├── log_util.dart │ └── m3u_util.dart └── widget │ ├── date_position_widget.dart │ ├── empty_page.dart │ ├── focus_button.dart │ ├── focus_card.dart │ ├── no_scroller_behavior.dart │ ├── update_download_btn.dart │ ├── video_hold_bg.dart │ └── volume_brightness_widget.dart ├── linux ├── .gitignore ├── CMakeLists.txt ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake ├── main.cc ├── my_application.cc └── my_application.h ├── macos ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── icon-128.png │ │ │ ├── icon-128@2x.png │ │ │ ├── icon-16.png │ │ │ ├── icon-16@2x.png │ │ │ ├── icon-256.png │ │ │ ├── icon-256@2x.png │ │ │ ├── icon-32.png │ │ │ ├── icon-32@2x.png │ │ │ ├── icon-512.png │ │ │ └── icon-512@2x.png │ ├── Base.lproj │ │ └── MainMenu.xib │ ├── Configs │ │ ├── AppInfo.xcconfig │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── Warnings.xcconfig │ ├── DebugProfile.entitlements │ ├── Info.plist │ ├── MainFlutterWindow.swift │ ├── Release.entitlements │ └── zh-Hans.lproj │ │ └── MainMenu.strings └── RunnerTests │ └── RunnerTests.swift ├── pubspec.lock ├── pubspec.yaml ├── reward.txt ├── temp ├── test └── widget_test.dart ├── versions.json └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter ├── CMakeLists.txt ├── generated_plugin_registrant.cc ├── generated_plugin_registrant.h └── generated_plugins.cmake ├── runner ├── CMakeLists.txt ├── Runner.rc ├── flutter_window.cpp ├── flutter_window.h ├── main.cpp ├── resource.h ├── resources │ └── app_icon.ico ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h └── system32 ├── msvcp140.dll ├── vcruntime140.dll └── vcruntime140_1.dll /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | # open_collective: easytv 3 | buy_me_a_coffee: aiyakuaile 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "5dcb86f68f239346676ceb1ed1ea385bd215fba1" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 17 | base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 18 | - platform: android 19 | create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 20 | base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 21 | - platform: ios 22 | create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 23 | base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 24 | - platform: linux 25 | create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 26 | base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 27 | - platform: macos 28 | create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 29 | base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 30 | - platform: web 31 | create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 32 | base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 33 | - platform: windows 34 | create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 35 | base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 2 | 3 | You are free to: 4 | - Share: copy and redistribute the material in any medium or format 5 | - Adapt: remix, transform, and build upon the material 6 | 7 | Under the following terms: 8 | - Attribution: You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. 9 | - NonCommercial: You may not use the material for commercial purposes. 10 | - ShareAlike: If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original. 11 | 12 | No additional restrictions: You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. 13 | 14 | View the full license at: https://creativecommons.org/licenses/by-nc-sa/4.0/ 15 | -------------------------------------------------------------------------------- /README-en.md: -------------------------------------------------------------------------------- 1 | ![logo](https://fastly.jsdelivr.net/gh/aiyakuaile/images/tv-flow.png) 2 | 3 | [![English](https://img.shields.io/badge/Language-English-blueviolet?style=for-the-badge)](README-en.md) 4 | [![简体中文](https://img.shields.io/badge/语言-简体中文-blueviolet?style=for-the-badge)](README.md) 5 | 6 | # EasyTV 7 | EasyTV is a lightweight IPTV player that supports all platforms and Android TV large screens. It is simple and easy to use. Welcome to download and experience! 8 | 9 | #### Features 10 | - [x] IPTV player that supports all platforms 11 | - [x] Ability to add live source links independently 12 | - [x] Clean and simple UI interaction 13 | - [x] Support for parsing .m3u files 14 | - [x] Support for parsing .txt files (added in v1.5.0) 15 | - [x] Supports Android TV (added in v2.0.0, supports Android OS 5.0+) 16 | - [x] Desktop support (added in v2.6.0) 17 | - [x] Support switching fonts and font sizes 18 | - [x] Support using Bing daily images as video backgrounds 19 | - [x] Automatically merge and group the same channels 20 | - [x] Support channel switching via number keys 21 | - [x] Support channel switching with up and down keys (needs to be manually enabled in '设置->实现设置->上下键切换频道') 22 | 23 | #### TV Version Interaction 24 | - Up key: Switch lines 25 | - Down key: Modify or add subscription sources 26 | - OK key: Confirm operation, display channel list 27 | 28 | #### Fonts 29 | The fonts that support switching need to use the [easy_tv_font](https://github.com/aiyakuaile/easy_tv_font) project. If you want to add other fonts, please follow the instructions in this project. 30 | 31 | > For iOS, after downloading the ipa, you need to sign it for installation using tools. 32 | > If the added live source is an IPv6 address, please confirm that your current network can access IPv6 addresses normally. 33 | > [IPv6 Test Address](https://v6t.ipip.net/) 34 | 35 | ## Preview 36 | 37 | ![image_2](https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/main/img_2.jpeg) 38 | 39 | ![image_1](https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/main/img_1.jpeg) | ![image_3](https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/main/img_3.jpeg) 40 | ---|--- 41 | 42 | 43 | ## Sponsorship 44 | If you find this project useful, consider sponsoring me for a cup of coffee ❤ 45 | 46 | Buy Me A Coffee 47 | 48 | For easier tracking of sponsors, we recommend using WeChat Scan to make a direct donation. Thank 49 | 微信赞赏 50 | 51 | ## 贡献 52 | 53 | 54 | 55 | 56 | 57 | ### Star History 58 | 59 | 65 | 71 | Star History Chart 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo](https://fastly.jsdelivr.net/gh/aiyakuaile/images/tv-flow.png) 2 | 3 | [![English](https://img.shields.io/badge/Language-English-blueviolet?style=for-the-badge)](README-en.md) 4 | [![简体中文](https://img.shields.io/badge/语言-简体中文-blueviolet?style=for-the-badge)](README.md) 5 | 6 | # 极简TV 7 | 8 | 极简TV,一款轻量级,支持全平台及安卓电视大屏的IPTV播放器,简洁易用,欢迎下载体验! 9 | 10 | #### TV源 11 | 本应用自带的节目源,来源自[IPTV-API](https://github.com/Guovin/iptv-api)这个项目,感谢此项目的开源与分享,推荐在应用中自主订阅```https://raw.githubusercontent.com/Guovin/iptv-api/gd/output/result.m3u```或者```https://cdn.jsdelivr.net/gh/Guovin/iptv-api@gd/output/result.m3u```,来保持节目源的及时更新!最新订阅链接请前往此项目查看。 12 | 13 | #### 功能 14 | 15 | - [x] 全平台支持的IPTV播放器 16 | - [x] 可自主添加直播源链接 17 | - [x] 干净简洁的UI交互 18 | - [x] 增加对.m3u文件的解析支持 19 | - [x] 增加对.txt文件的解析支持(v1.5.0新增) 20 | - [x] 支持Android TV(v2.0.0新增,支持AndroidOS 5.0+) 21 | - [x] 桌面端支持(v2.6.0新增) 22 | - [x] 支持切换字体和字体大小 23 | - [x] 支持使用Bing每日图片作为视频背景 24 | - [x] 相同频道自动合并,自动分组 25 | - [x] 支持数字换台 26 | - [x] 支持上下键换台(需要在'设置->实验设置->上下键切换频道'中手动打开) 27 | 28 | ### TV版交互 29 | 30 | ⬆️上键:切换频道或线路 31 | 32 | ⬇️下键:切换频道或进入设置页面 33 | 34 | 🆗ok键:确认操作、显示频道列表 35 | 36 | ### 字体 37 | 支持切换的字体需要使用[easy_tv_font](https://github.com/aiyakuaile/easy_tv_font)这个项目, 38 | 您如果想要添加其他字体,请按照这个项目的提示进行操作。 39 | 40 | > ios 支持15.5.0+以上版本,下载ipa后需要自行签名安装,例如:爱思助手,轻松签,巨魔等 41 | 42 | > 如果添加的直播源是ipv6地址,请先确认您当前的网络是否可以正常访问ipv6地址 43 | > [ipv6测试地址](https://v6t.ipip.net/) 44 | 45 | ## 预览 46 | 47 | ![image_2](https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/main/img_2.jpeg) 48 | 49 | ![image_1](https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/main/img_1.jpeg) | ![image_3](https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/main/img_3.jpeg) 50 | ---|--- 51 | 52 | 53 | ## 赞助 54 | 如果觉得此项目有用,可以考虑赞助我喝杯咖啡❤ 55 | 56 | Buy Me A Coffee 57 | 58 | 为了方便统计打赏名单,推荐使用微信扫一扫直接赞赏,感谢您的支持! 59 | 60 | 微信赞赏 61 | 62 | 63 | ## 贡献 64 | 65 | 66 | 67 | 68 | 69 | ## Star History 70 | 71 | 77 | 83 | Star History Chart 87 | 88 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. 5 | id "dev.flutter.flutter-gradle-plugin" 6 | } 7 | 8 | def localProperties = new Properties() 9 | def localPropertiesFile = rootProject.file("local.properties") 10 | if (localPropertiesFile.exists()) { 11 | localPropertiesFile.withReader("UTF-8") { reader -> 12 | localProperties.load(reader) 13 | } 14 | } 15 | 16 | def flutterVersionCode = localProperties.getProperty("flutter.versionCode") 17 | if (flutterVersionCode == null) { 18 | flutterVersionCode = "1" 19 | } 20 | 21 | def flutterVersionName = localProperties.getProperty("flutter.versionName") 22 | if (flutterVersionName == null) { 23 | flutterVersionName = "1.0" 24 | } 25 | 26 | android { 27 | namespace = "com.suntengfei.easy_tv_live" 28 | compileSdk = 34 29 | // ndkVersion = flutter.ndkVersion 30 | 31 | compileOptions { 32 | sourceCompatibility = JavaVersion.VERSION_17 33 | targetCompatibility = JavaVersion.VERSION_17 34 | } 35 | 36 | kotlinOptions { 37 | jvmTarget = "17" 38 | } 39 | 40 | defaultConfig { 41 | applicationId = "com.easy.tvlive" 42 | minSdk = 21 43 | targetSdk = 34 44 | versionCode = flutterVersionCode.toInteger() 45 | versionName = flutterVersionName 46 | } 47 | 48 | signingConfigs { 49 | release { 50 | storeFile file("../../keystore.jks") 51 | storePassword System.getenv("KEYSTORE_PASSWORD") 52 | keyAlias System.getenv("KEY_ALIAS") 53 | keyPassword System.getenv("KEY_ALIAS_PASSWORD") 54 | } 55 | } 56 | 57 | buildTypes { 58 | release { 59 | signingConfig = signingConfigs.debug 60 | signingConfig = signingConfigs.release 61 | ndk { 62 | abiFilters "arm64-v8a", "armeabi-v7a" 63 | } 64 | } 65 | } 66 | } 67 | 68 | flutter { 69 | source = "../.." 70 | } 71 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 39 | 48 | 52 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 65 | 68 | 69 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/suntengfei/easy_tv_live/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.suntengfei.easy_tv_live 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/android/app/src/main/res/mipmap-hdpi/launch_image.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/android/app/src/main/res/mipmap-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/playstore-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/android/app/src/main/res/playstore-icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night-v27/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 15 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-v27/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = "../build" 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(":app") 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version "8.5.0" apply false 22 | id "org.jetbrains.kotlin.android" version "1.9.23" apply false 23 | } 24 | 25 | include ":app" 26 | -------------------------------------------------------------------------------- /assets/images/appreciate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/assets/images/appreciate.png -------------------------------------------------------------------------------- /assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/assets/images/logo.png -------------------------------------------------------------------------------- /assets/images/video_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/assets/images/video_bg.png -------------------------------------------------------------------------------- /devtools_options.yaml: -------------------------------------------------------------------------------- 1 | description: This file stores settings for Dart & Flutter DevTools. 2 | documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states 3 | extensions: 4 | -------------------------------------------------------------------------------- /img_1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/img_1.jpeg -------------------------------------------------------------------------------- /img_2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/img_2.jpeg -------------------------------------------------------------------------------- /img_3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/img_3.jpeg -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '12.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | target 'RunnerTests' do 36 | inherit! :search_paths 37 | end 38 | end 39 | 40 | post_install do |installer| 41 | installer.pods_project.targets.each do |target| 42 | flutter_additional_ios_build_settings(target) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "size": "20x20", 5 | "idiom": "universal", 6 | "filename": "icon-20@2x.png", 7 | "scale": "2x", 8 | "platform": "ios" 9 | }, 10 | { 11 | "size": "20x20", 12 | "idiom": "universal", 13 | "filename": "icon-20@3x.png", 14 | "scale": "3x", 15 | "platform": "ios" 16 | }, 17 | { 18 | "size": "29x29", 19 | "idiom": "universal", 20 | "filename": "icon-29@2x.png", 21 | "scale": "2x", 22 | "platform": "ios" 23 | }, 24 | { 25 | "size": "29x29", 26 | "idiom": "universal", 27 | "filename": "icon-29@3x.png", 28 | "scale": "3x", 29 | "platform": "ios" 30 | }, 31 | { 32 | "size": "38x38", 33 | "idiom": "universal", 34 | "filename": "icon-38@2x.png", 35 | "scale": "2x", 36 | "platform": "ios" 37 | }, 38 | { 39 | "size": "38x38", 40 | "idiom": "universal", 41 | "filename": "icon-38@3x.png", 42 | "scale": "3x", 43 | "platform": "ios" 44 | }, 45 | { 46 | "size": "40x40", 47 | "idiom": "universal", 48 | "filename": "icon-40@2x.png", 49 | "scale": "2x", 50 | "platform": "ios" 51 | }, 52 | { 53 | "size": "40x40", 54 | "idiom": "universal", 55 | "filename": "icon-40@3x.png", 56 | "scale": "3x", 57 | "platform": "ios" 58 | }, 59 | { 60 | "size": "60x60", 61 | "idiom": "universal", 62 | "filename": "icon-60@2x.png", 63 | "scale": "2x", 64 | "platform": "ios" 65 | }, 66 | { 67 | "size": "60x60", 68 | "idiom": "universal", 69 | "filename": "icon-60@3x.png", 70 | "scale": "3x", 71 | "platform": "ios" 72 | }, 73 | { 74 | "size": "64x64", 75 | "idiom": "universal", 76 | "filename": "icon-64@2x.png", 77 | "scale": "2x", 78 | "platform": "ios" 79 | }, 80 | { 81 | "size": "64x64", 82 | "idiom": "universal", 83 | "filename": "icon-64@3x.png", 84 | "scale": "3x", 85 | "platform": "ios" 86 | }, 87 | { 88 | "size": "68x68", 89 | "idiom": "universal", 90 | "filename": "icon-68@2x.png", 91 | "scale": "2x", 92 | "platform": "ios" 93 | }, 94 | { 95 | "size": "76x76", 96 | "idiom": "universal", 97 | "filename": "icon-76@2x.png", 98 | "scale": "2x", 99 | "platform": "ios" 100 | }, 101 | { 102 | "size": "83.5x83.5", 103 | "idiom": "universal", 104 | "filename": "icon-83.5@2x.png", 105 | "scale": "2x", 106 | "platform": "ios" 107 | }, 108 | { 109 | "size": "1024x1024", 110 | "idiom": "universal", 111 | "filename": "icon-1024.png", 112 | "scale": "1x", 113 | "platform": "ios" 114 | } 115 | ], 116 | "info": { 117 | "version": 1, 118 | "author": "icon.wuruihong.com" 119 | } 120 | } -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-38@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-38@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-38@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-38@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-64@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-64@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-64@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-64@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-68@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-68@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | 极简TV 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | easy_tv_live 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(FLUTTER_BUILD_NAME) 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | $(FLUTTER_BUILD_NUMBER) 27 | LSRequiresIPhoneOS 28 | 29 | UIApplicationSupportsIndirectInputEvents 30 | 31 | UILaunchStoryboardName 32 | LaunchScreen 33 | UIMainStoryboardFile 34 | Main 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | NSAppTransportSecurity 49 | 50 | NSAllowsArbitraryLoads 51 | 52 | 53 | NSCameraUsageDescription 54 | Can I use the camera please? 55 | 56 | 57 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /lib/entity/font_model.dart: -------------------------------------------------------------------------------- 1 | class FontModel { 2 | FontModel( 3 | {this.id, this.fontName, this.fontKey, this.fontType, this.progress}); 4 | FontModel.fromJson(dynamic json) { 5 | id = json['id']; 6 | fontName = json['font_name']; 7 | fontKey = json['font_key']; 8 | fontType = json['font_type']; 9 | progress = json['progress'] ?? 0.0; 10 | } 11 | String? id; 12 | String? fontName; 13 | String? fontKey; 14 | String? fontType; 15 | double? progress; 16 | } 17 | -------------------------------------------------------------------------------- /lib/entity/play_channel_list_model.dart: -------------------------------------------------------------------------------- 1 | enum PlayListType { 2 | m3u, 3 | txt, 4 | } 5 | 6 | class PlayChannelListModel { 7 | PlayChannelListModel({ 8 | this.epgUrl, 9 | this.type, 10 | this.playList, 11 | this.playGroupIndex = 0, 12 | this.playChannelIndex = 0, 13 | }); 14 | 15 | PlayChannelListModel.fromJson(dynamic json) { 16 | playGroupIndex = 0; 17 | playChannelIndex = 0; 18 | epgUrl = json['epgUrl']; 19 | type = PlayListType.values[json['type']]; 20 | if (json['playList'] != null) { 21 | playList = []; 22 | json['playList'].forEach((v) { 23 | playList?.add(PlayModel.fromJson(v)); 24 | }); 25 | } 26 | } 27 | 28 | String? epgUrl; 29 | List? playList; 30 | PlayListType? type; 31 | int? playGroupIndex; 32 | int? playChannelIndex; 33 | } 34 | 35 | class PlayModel { 36 | PlayModel({ 37 | this.group, 38 | this.channel, 39 | }); 40 | 41 | PlayModel.fromJson(dynamic json) { 42 | group = json['group']; 43 | if (json['channel'] != null) { 44 | channel = []; 45 | json['channel'].forEach((v) { 46 | channel?.add(Channel.fromJson(v)); 47 | }); 48 | } 49 | } 50 | 51 | String? group; 52 | List? channel; 53 | 54 | PlayModel copyWith({ 55 | String? group, 56 | List? channel, 57 | }) => 58 | PlayModel( 59 | group: group ?? this.group, 60 | channel: channel ?? this.channel, 61 | ); 62 | 63 | Map toJson() { 64 | final map = {}; 65 | map['group'] = group; 66 | if (channel != null) { 67 | map['channel'] = channel?.map((v) => v.toJson()).toList(); 68 | } 69 | return map; 70 | } 71 | } 72 | 73 | class Channel { 74 | Channel({ 75 | this.id, 76 | this.logo, 77 | this.title, 78 | this.urls, 79 | this.serialNum, 80 | this.groupIndex, 81 | this.channelIndex, 82 | }); 83 | 84 | Channel.fromJson(dynamic json) { 85 | id = json['id']; 86 | logo = json['logo']; 87 | title = json['title']; 88 | urls = json['urls'] != null ? json['urls'].cast() : []; 89 | serialNum = json['serialNum']; 90 | groupIndex = json['groupIndex']; 91 | channelIndex = json['channelIndex']; 92 | } 93 | int? serialNum; 94 | String? id; 95 | String? logo; 96 | String? title; 97 | List? urls; 98 | int? groupIndex; 99 | int? channelIndex; 100 | 101 | Channel copyWith({ 102 | String? id, 103 | String? logo, 104 | String? title, 105 | List? urls, 106 | int? serialNum, 107 | int? groupIndex, 108 | int? channelIndex, 109 | }) => 110 | Channel( 111 | id: id ?? this.id, 112 | logo: logo ?? this.logo, 113 | title: title ?? this.title, 114 | urls: urls ?? this.urls, 115 | serialNum: serialNum ?? this.serialNum, 116 | groupIndex: groupIndex ?? this.groupIndex, 117 | channelIndex: channelIndex ?? this.channelIndex, 118 | ); 119 | 120 | Map toJson() { 121 | final map = {}; 122 | map['id'] = id; 123 | map['logo'] = logo; 124 | map['title'] = title; 125 | map['urls'] = urls; 126 | map['serialNum'] = serialNum; 127 | map['groupIndex'] = groupIndex; 128 | map['channelIndex'] = channelIndex; 129 | return map; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /lib/entity/subScribe_model.dart: -------------------------------------------------------------------------------- 1 | class SubScribeModel { 2 | SubScribeModel({this.time, this.link, this.selected = false}); 3 | 4 | SubScribeModel.fromJson(dynamic json) { 5 | time = json['time']; 6 | link = json['link']; 7 | selected = json['selected'] ?? false; 8 | } 9 | String? time; 10 | String? link; 11 | bool? selected; 12 | 13 | Map toJson() { 14 | final map = {}; 15 | map['time'] = time; 16 | map['link'] = link; 17 | map['selected'] = selected ?? false; 18 | return map; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/generated/intl/messages_all.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that looks up messages for specific locales by 3 | // delegating to the appropriate library. 4 | 5 | // Ignore issues from commonly used lints in this file. 6 | // ignore_for_file:implementation_imports, file_names, unnecessary_new 7 | // ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering 8 | // ignore_for_file:argument_type_not_assignable, invalid_assignment 9 | // ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases 10 | // ignore_for_file:comment_references 11 | 12 | import 'dart:async'; 13 | 14 | import 'package:flutter/foundation.dart'; 15 | import 'package:intl/intl.dart'; 16 | import 'package:intl/message_lookup_by_library.dart'; 17 | import 'package:intl/src/intl_helpers.dart'; 18 | 19 | import 'messages_en_US.dart' as messages_en_us; 20 | import 'messages_zh_CN.dart' as messages_zh_cn; 21 | 22 | typedef Future LibraryLoader(); 23 | Map _deferredLibraries = { 24 | 'en_US': () => new SynchronousFuture(null), 25 | 'zh_CN': () => new SynchronousFuture(null), 26 | }; 27 | 28 | MessageLookupByLibrary? _findExact(String localeName) { 29 | switch (localeName) { 30 | case 'en_US': 31 | return messages_en_us.messages; 32 | case 'zh_CN': 33 | return messages_zh_cn.messages; 34 | default: 35 | return null; 36 | } 37 | } 38 | 39 | /// User programs should call this before using [localeName] for messages. 40 | Future initializeMessages(String localeName) { 41 | var availableLocale = Intl.verifiedLocale( 42 | localeName, (locale) => _deferredLibraries[locale] != null, 43 | onFailure: (_) => null); 44 | if (availableLocale == null) { 45 | return new SynchronousFuture(false); 46 | } 47 | var lib = _deferredLibraries[availableLocale]; 48 | lib == null ? new SynchronousFuture(false) : lib(); 49 | initializeInternalMessageLookup(() => new CompositeMessageLookup()); 50 | messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor); 51 | return new SynchronousFuture(true); 52 | } 53 | 54 | bool _messagesExistFor(String locale) { 55 | try { 56 | return _findExact(locale) != null; 57 | } catch (e) { 58 | return false; 59 | } 60 | } 61 | 62 | MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) { 63 | var actualLocale = 64 | Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null); 65 | if (actualLocale == null) return null; 66 | return _findExact(actualLocale); 67 | } 68 | -------------------------------------------------------------------------------- /lib/generated/intl/messages_zh_CN.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that provides messages for a zh_CN locale. All the 3 | // messages from the main program should be duplicated here with the same 4 | // function name. 5 | 6 | // Ignore issues from commonly used lints in this file. 7 | // ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new 8 | // ignore_for_file:prefer_single_quotes,comment_references, directives_ordering 9 | // ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases 10 | // ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes 11 | // ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes 12 | 13 | import 'package:intl/intl.dart'; 14 | import 'package:intl/message_lookup_by_library.dart'; 15 | 16 | final messages = new MessageLookup(); 17 | 18 | typedef String MessageIfAbsent(String messageStr, List args); 19 | 20 | class MessageLookup extends MessageLookupByLibrary { 21 | String get localeName => 'zh_CN'; 22 | 23 | static String m0(index) => "线路${index}"; 24 | 25 | static String m1(line, channel) => "线路${line}播放: ${channel}"; 26 | 27 | static String m2(code) => "响应异常${code}"; 28 | 29 | static String m3(version) => "新版本v${version}"; 30 | 31 | static String m4(address) => "推送地址:${address}"; 32 | 33 | static String m5(line) => "切换线路${line} ..."; 34 | 35 | final messages = _notInlinedMessages(_notInlinedMessages); 36 | static Map _notInlinedMessages(_) => { 37 | "addDataSource": MessageLookupByLibrary.simpleMessage("添加订阅源"), 38 | "addFiledHintText": 39 | MessageLookupByLibrary.simpleMessage("请输入或粘贴.m3u或.txt格式的订阅源链接"), 40 | "addNoHttpLink": 41 | MessageLookupByLibrary.simpleMessage("请输入http/https链接"), 42 | "addRepeat": MessageLookupByLibrary.simpleMessage("已添加过此订阅源"), 43 | "appName": MessageLookupByLibrary.simpleMessage("极简TV"), 44 | "checkUpdate": MessageLookupByLibrary.simpleMessage("检查更新"), 45 | "createTime": MessageLookupByLibrary.simpleMessage("创建时间"), 46 | "dataSourceContent": MessageLookupByLibrary.simpleMessage("确定添加此数据源吗?"), 47 | "defaultText": MessageLookupByLibrary.simpleMessage("默认"), 48 | "delete": MessageLookupByLibrary.simpleMessage("删除"), 49 | "dialogCancel": MessageLookupByLibrary.simpleMessage("取消"), 50 | "dialogConfirm": MessageLookupByLibrary.simpleMessage("确定"), 51 | "dialogDeleteContent": 52 | MessageLookupByLibrary.simpleMessage("确定删除此订阅吗?"), 53 | "dialogTitle": MessageLookupByLibrary.simpleMessage("温馨提示"), 54 | "findNewVersion": MessageLookupByLibrary.simpleMessage("发现新版本"), 55 | "fullScreen": MessageLookupByLibrary.simpleMessage("全屏切换"), 56 | "getDefaultError": MessageLookupByLibrary.simpleMessage("获取默认数据源失败"), 57 | "homePage": MessageLookupByLibrary.simpleMessage("主页"), 58 | "inUse": MessageLookupByLibrary.simpleMessage("使用中"), 59 | "landscape": MessageLookupByLibrary.simpleMessage("横屏模式"), 60 | "latestVersion": MessageLookupByLibrary.simpleMessage("已是最新版本"), 61 | "lineIndex": m0, 62 | "lineToast": m1, 63 | "loading": MessageLookupByLibrary.simpleMessage("正在加载"), 64 | "netBadResponse": m2, 65 | "netCancel": MessageLookupByLibrary.simpleMessage("请求取消"), 66 | "netReceiveTimeout": MessageLookupByLibrary.simpleMessage("响应超时"), 67 | "netSendTimeout": MessageLookupByLibrary.simpleMessage("请求超时"), 68 | "netTimeOut": MessageLookupByLibrary.simpleMessage("连接超时"), 69 | "newVersion": m3, 70 | "noEPG": MessageLookupByLibrary.simpleMessage("暂无节目信息"), 71 | "okRefresh": MessageLookupByLibrary.simpleMessage("【OK键】刷新"), 72 | "parseError": MessageLookupByLibrary.simpleMessage("解析数据源出错"), 73 | "pasterContent": 74 | MessageLookupByLibrary.simpleMessage("复制订阅源后,回到此页面可自动添加订阅源"), 75 | "playError": MessageLookupByLibrary.simpleMessage("此视频无法播放,请更换其它频道"), 76 | "playReconnect": MessageLookupByLibrary.simpleMessage("出错了,尝试重新连接..."), 77 | "portrait": MessageLookupByLibrary.simpleMessage("竖屏模式"), 78 | "pushAddress": m4, 79 | "refresh": MessageLookupByLibrary.simpleMessage("刷新"), 80 | "releaseHistory": MessageLookupByLibrary.simpleMessage("发布历史"), 81 | "setDefault": MessageLookupByLibrary.simpleMessage("设为默认"), 82 | "settings": MessageLookupByLibrary.simpleMessage("设置"), 83 | "subscribe": MessageLookupByLibrary.simpleMessage("IPTV订阅"), 84 | "switchLine": m5, 85 | "tipChangeLine": MessageLookupByLibrary.simpleMessage("切换线路"), 86 | "tipChannelList": MessageLookupByLibrary.simpleMessage("频道列表"), 87 | "tvParseParma": MessageLookupByLibrary.simpleMessage("参数错误"), 88 | "tvParsePushError": MessageLookupByLibrary.simpleMessage("请推送正确的链接"), 89 | "tvParseSuccess": MessageLookupByLibrary.simpleMessage("推送成功"), 90 | "tvPushContent": MessageLookupByLibrary.simpleMessage( 91 | "注意:必须在同一WIFI网络环境下\n1、使用极简TV手机版扫码可快速完成数据添加和双向同步\n2、使用其他App扫码,在扫码结果页,输入新的订阅源,点击页面中的推送即可添加成功"), 92 | "tvScanTip": MessageLookupByLibrary.simpleMessage("扫码添加订阅源"), 93 | "update": MessageLookupByLibrary.simpleMessage("立即更新"), 94 | "updateContent": MessageLookupByLibrary.simpleMessage("更新内容") 95 | }; 96 | } 97 | -------------------------------------------------------------------------------- /lib/l10n/intl_en_US.arb: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "EasyTV", 3 | "loading": "Loading", 4 | "lineToast": "Line {line} playing: {channel}", 5 | "@lineToast": { 6 | "placeholders": { 7 | "line": {}, 8 | "channel": {} 9 | } 10 | }, 11 | "playError": "This video cannot be played, please switch to another channel", 12 | "switchLine": "Switching to line {line} ...", 13 | "@switchLine": { 14 | "placeholders": { 15 | "line": {} 16 | } 17 | }, 18 | "playReconnect": "An error occurred, trying to reconnect...", 19 | "lineIndex": "Line {index}", 20 | "@lineIndex": { 21 | "placeholders": { 22 | "index": {} 23 | } 24 | }, 25 | "tipChannelList": "Channel List", 26 | "tipChangeLine": "Switch Line", 27 | "portrait": "Portrait Mode", 28 | "landscape": "Landscape Mode", 29 | "fullScreen": "Full Screen Toggle", 30 | "settings": "Settings", 31 | "homePage": "Home Page", 32 | "releaseHistory": "Release History", 33 | "checkUpdate": "Check for Updates", 34 | "newVersion": "New Version v{version}", 35 | "@newVersion": { 36 | "placeholders": { 37 | "version": {} 38 | } 39 | }, 40 | "update": "Update Now", 41 | "latestVersion": "You are on the latest version", 42 | "findNewVersion": "New version found", 43 | "updateContent": "Update Content", 44 | "dialogTitle": "Friendly Reminder", 45 | "dataSourceContent": "Are you sure you want to add this data source?", 46 | "dialogCancel": "Cancel", 47 | "dialogConfirm": "Confirm", 48 | "subscribe": "IPTV Subscription", 49 | "createTime": "Creation Time", 50 | "dialogDeleteContent": "Are you sure you want to delete this subscription?", 51 | "delete": "Delete", 52 | "setDefault": "Set as Default", 53 | "inUse": "In Use", 54 | "tvParseParma": "Parameter Error", 55 | "tvParseSuccess": "Push Successful", 56 | "tvParsePushError": "Please push the correct link", 57 | "tvScanTip": "Scan to add subscription source", 58 | "pushAddress": "Push Address: {address}", 59 | "@pushAddress": { 60 | "placeholders": { 61 | "address": {} 62 | } 63 | }, 64 | "tvPushContent": "On the scan result page, enter the new subscription source and click the push button to add successfully", 65 | "pasterContent": "After copying the subscription source, return to this page to automatically add the subscription source", 66 | "addDataSource": "Add Subscription Source", 67 | "addFiledHintText": "Please enter or paste the .m3u or .txt format subscription link", 68 | "addRepeat": "This subscription source has been added", 69 | "addNoHttpLink": "Please enter an http/https link", 70 | "netTimeOut": "Connection timed out", 71 | "netSendTimeout": "Request timed out", 72 | "netReceiveTimeout": "Response timed out", 73 | "netBadResponse": "Abnormal response {code}", 74 | "@netBadResponse": { 75 | "placeholders": { 76 | "code": {} 77 | } 78 | }, 79 | "netCancel": "Request Cancelled", 80 | "parseError": "Error parsing data source", 81 | "defaultText": "Default", 82 | "getDefaultError": "Failed to get the default data source", 83 | "okRefresh": "【OK key】 Refresh", 84 | "refresh": "Refresh", 85 | "noEPG": "NO EPG DATA" 86 | } -------------------------------------------------------------------------------- /lib/l10n/intl_zh_CN.arb: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "极简TV", 3 | "loading": "正在加载", 4 | "lineToast": "线路{line}播放: {channel}", 5 | "@lineToast": { 6 | "placeholders": { 7 | "line": {}, 8 | "channel": {} 9 | } 10 | }, 11 | "playError": "此视频无法播放,请更换其它频道", 12 | "switchLine": "切换线路{line} ...", 13 | "@switchLine": { 14 | "placeholders": { 15 | "line": {} 16 | } 17 | }, 18 | "playReconnect": "出错了,尝试重新连接...", 19 | "lineIndex": "线路{index}", 20 | "@lineIndex": { 21 | "placeholders": { 22 | "index": {} 23 | } 24 | }, 25 | "tipChannelList": "频道列表", 26 | "tipChangeLine": "切换线路", 27 | "portrait": "竖屏模式", 28 | "landscape": "横屏模式", 29 | "fullScreen": "全屏切换", 30 | "settings": "设置", 31 | "homePage": "主页", 32 | "releaseHistory": "发布历史", 33 | "checkUpdate": "检查更新", 34 | "newVersion": "新版本v{version}", 35 | "@newVersion": { 36 | "placeholders": { 37 | "version": {} 38 | } 39 | }, 40 | "update": "立即更新", 41 | "latestVersion": "已是最新版本", 42 | "findNewVersion": "发现新版本", 43 | "updateContent": "更新内容", 44 | "dialogTitle": "温馨提示", 45 | "dataSourceContent": "确定添加此数据源吗?", 46 | "dialogCancel": "取消", 47 | "dialogConfirm": "确定", 48 | "subscribe": "IPTV订阅", 49 | "createTime": "创建时间", 50 | "dialogDeleteContent": "确定删除此订阅吗?", 51 | "delete": "删除", 52 | "setDefault": "设为默认", 53 | "inUse": "使用中", 54 | "tvParseParma": "参数错误", 55 | "tvParseSuccess": "推送成功", 56 | "tvParsePushError": "请推送正确的链接", 57 | "tvScanTip": "扫码添加订阅源", 58 | "pushAddress": "推送地址:{address}", 59 | "@pushAddress": { 60 | "placeholders": { 61 | "address": {} 62 | } 63 | }, 64 | "tvPushContent": "注意:必须在同一WIFI网络环境下\n1、使用极简TV手机版扫码可快速完成数据添加和双向同步\n2、使用其他App扫码,在扫码结果页,输入新的订阅源,点击页面中的推送即可添加成功", 65 | "pasterContent": "复制订阅源后,回到此页面可自动添加订阅源", 66 | "addDataSource": "添加订阅源", 67 | "addFiledHintText": "请输入或粘贴.m3u或.txt格式的订阅源链接", 68 | "addRepeat": "已添加过此订阅源", 69 | "addNoHttpLink": "请输入http/https链接", 70 | "netTimeOut": "连接超时", 71 | "netSendTimeout": "请求超时", 72 | "netReceiveTimeout": "响应超时", 73 | "netBadResponse": "响应异常{code}", 74 | "@netBadResponse": { 75 | "placeholders": { 76 | "code": {} 77 | } 78 | }, 79 | "netCancel": "请求取消", 80 | "parseError": "解析数据源出错", 81 | "defaultText": "默认", 82 | "getDefaultError": "获取默认数据源失败", 83 | "okRefresh": "【OK键】刷新", 84 | "refresh": "刷新", 85 | "noEPG": "暂无节目信息" 86 | } -------------------------------------------------------------------------------- /lib/mobile_video_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:easy_tv_live/router_keys.dart'; 2 | import 'package:easy_tv_live/setting/subscribe_page.dart'; 3 | import 'package:easy_tv_live/table_video_widget.dart'; 4 | import 'package:easy_tv_live/widget/empty_page.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:sp_util/sp_util.dart'; 7 | import 'package:video_player/video_player.dart'; 8 | import 'package:window_manager/window_manager.dart'; 9 | 10 | import 'generated/l10n.dart'; 11 | import 'util/env_util.dart'; 12 | 13 | class MobileVideoWidget extends StatefulWidget { 14 | final VideoPlayerController? controller; 15 | final GestureTapCallback? changeChannelSources; 16 | final String? toastString; 17 | final bool isLandscape; 18 | final Widget drawChild; 19 | final bool isBuffering; 20 | final bool isPlaying; 21 | final double aspectRatio; 22 | final GestureTapCallback onChangeSubSource; 23 | 24 | const MobileVideoWidget({ 25 | super.key, 26 | required this.controller, 27 | required this.drawChild, 28 | required this.isBuffering, 29 | required this.isPlaying, 30 | required this.aspectRatio, 31 | // 数据源改变 32 | required this.onChangeSubSource, 33 | this.toastString, 34 | // 线路切换 35 | this.changeChannelSources, 36 | this.isLandscape = true, 37 | }); 38 | 39 | @override 40 | State createState() => _MobileVideoWidgetState(); 41 | } 42 | 43 | class _MobileVideoWidgetState extends State { 44 | @override 45 | Widget build(BuildContext context) { 46 | return Scaffold( 47 | appBar: AppBar( 48 | backgroundColor: Colors.black, 49 | centerTitle: true, 50 | title: Text(S.current.appName), 51 | leading: IconButton( 52 | icon: const Icon(Icons.qr_code_scanner), 53 | onPressed: () async { 54 | final isPlaying = widget.controller?.value.isPlaying ?? false; 55 | if (isPlaying) { 56 | widget.controller?.pause(); 57 | } 58 | final res = await Navigator.of(context).pushNamed(RouterKeys.settingQrScan); 59 | if (res != null && res != '') { 60 | await Navigator.of(context).push(MaterialPageRoute(builder: (ctx) { 61 | final ip = Uri.parse(res!.toString()).host; 62 | return SubScribePage(remoteIp: ip, isTV: false); 63 | })); 64 | widget.controller?.play(); 65 | final m3uData = SpUtil.getString('m3u_cache', defValue: '')!; 66 | if (m3uData == '') { 67 | widget.onChangeSubSource(); 68 | } 69 | } 70 | }, 71 | ), 72 | actions: [ 73 | IconButton( 74 | onPressed: () async { 75 | if (!EnvUtil.isMobile) { 76 | windowManager.setTitleBarStyle(TitleBarStyle.hidden, windowButtonVisibility: false); 77 | } 78 | final isPlaying = widget.controller?.value.isPlaying ?? false; 79 | if (isPlaying) { 80 | widget.controller?.pause(); 81 | } 82 | await Navigator.of(context).pushNamed(RouterKeys.subScribe); 83 | widget.controller?.play(); 84 | final m3uData = SpUtil.getString('m3u_cache', defValue: '')!; 85 | if (m3uData == '') { 86 | widget.onChangeSubSource(); 87 | } 88 | if (!EnvUtil.isMobile) { 89 | windowManager.setTitleBarStyle(TitleBarStyle.hidden, windowButtonVisibility: true); 90 | } 91 | }, 92 | icon: const Icon(Icons.add)), 93 | IconButton( 94 | onPressed: () async { 95 | if (!EnvUtil.isMobile) { 96 | windowManager.setTitleBarStyle(TitleBarStyle.hidden, windowButtonVisibility: false); 97 | } 98 | widget.controller?.pause(); 99 | await Navigator.of(context).pushNamed(RouterKeys.setting); 100 | widget.controller?.play(); 101 | if (!EnvUtil.isMobile) { 102 | windowManager.setTitleBarStyle(TitleBarStyle.hidden, windowButtonVisibility: true); 103 | } 104 | }, 105 | icon: const Icon(Icons.settings_outlined)), 106 | ], 107 | ), 108 | body: Column( 109 | children: [ 110 | AspectRatio( 111 | aspectRatio: widget.aspectRatio, 112 | child: TableVideoWidget( 113 | controller: widget.controller, 114 | toastString: widget.toastString, 115 | isLandscape: false, 116 | aspectRatio: widget.aspectRatio, 117 | isBuffering: widget.isBuffering, 118 | isPlaying: widget.isPlaying, 119 | changeChannelSources: widget.changeChannelSources, 120 | onChangeSubSource: widget.onChangeSubSource, 121 | drawerIsOpen: false, 122 | ), 123 | ), 124 | Flexible(child: widget.toastString == 'UNKNOWN' ? EmptyPage(onRefresh: widget.onChangeSubSource) : widget.drawChild) 125 | ], 126 | ), 127 | ); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /lib/provider/download_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:apk_installer/apk_installer.dart'; 2 | import 'package:easy_tv_live/util/log_util.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:path_provider/path_provider.dart'; 5 | 6 | import '../util/http_util.dart'; 7 | 8 | class DownloadProvider extends ChangeNotifier { 9 | bool _isDownloading = false; 10 | double _progress = 0.0; 11 | 12 | bool get isDownloading => _isDownloading; 13 | double get progress => _progress; 14 | 15 | Future downloadApk(String url) async { 16 | _isDownloading = true; 17 | notifyListeners(); 18 | 19 | final savePath = '${(await getTemporaryDirectory()).path}/apk/${url.split('/').last}'; 20 | LogUtil.v('download apk :::: $url'); 21 | LogUtil.v('apk save path:::: $savePath'); 22 | 23 | final code = await HttpUtil().downloadFile(url, savePath, progressCallback: (double currentProgress) { 24 | _progress = currentProgress; 25 | notifyListeners(); 26 | }); 27 | if (code == 200) { 28 | _isDownloading = false; 29 | await ApkInstaller.installApk(filePath: savePath); 30 | notifyListeners(); 31 | } else { 32 | _isDownloading = false; 33 | notifyListeners(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/provider/theme_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:sp_util/sp_util.dart'; 3 | 4 | import '../util/font_util.dart'; 5 | 6 | class ThemeProvider extends ChangeNotifier { 7 | String _fontFamily = 'system'; 8 | double _textScaleFactor = 1.0; 9 | String _fontUrl = ''; 10 | bool _isBingBg = false; 11 | bool _useLightVersionCheck = true; 12 | bool _useDataProxy = true; 13 | bool _useAutoUpdate = false; 14 | bool _useLeftRightSelect = false; 15 | int _prePlaySerialNum = 1; 16 | 17 | bool get useAutoUpdate => _useAutoUpdate; 18 | bool get useLightVersionCheck => _useLightVersionCheck; 19 | bool get useDataProxy => _useDataProxy; 20 | String get fontFamily => _fontFamily; 21 | double get textScaleFactor => _textScaleFactor; 22 | String get fontUrl => _fontUrl; 23 | bool get isBingBg => _isBingBg; 24 | bool get useLeftRightSelect => _useLeftRightSelect; 25 | int get prePlaySerialNum => _prePlaySerialNum; 26 | 27 | ThemeProvider() { 28 | _useAutoUpdate = SpUtil.getBool('autoUpdate', defValue: false)!; 29 | _useLightVersionCheck = SpUtil.getBool('lightVersionCheck', defValue: true)!; 30 | _useDataProxy = SpUtil.getBool('dataProxy', defValue: true)!; 31 | _fontFamily = SpUtil.getString('appFontFamily', defValue: 'system')!; 32 | _fontUrl = SpUtil.getString('appFontUrl', defValue: '')!; 33 | _textScaleFactor = SpUtil.getDouble('fontScale', defValue: 1.0)!; 34 | _isBingBg = SpUtil.getBool('bingBg', defValue: false)!; 35 | _useLeftRightSelect = SpUtil.getBool('leftRightSelect', defValue: false)!; 36 | _prePlaySerialNum = SpUtil.getInt('prePlaySerialNum', defValue: 1)!; 37 | if (_fontFamily != 'system') { 38 | FontUtil().loadFont(_fontUrl, _fontFamily); 39 | } 40 | } 41 | 42 | void setFontFamily(String fontFamilyName, [String fontFullUrl = '']) { 43 | SpUtil.putString('appFontFamily', fontFamilyName); 44 | SpUtil.putString('appFontUrl', fontFullUrl); 45 | _fontFamily = fontFamilyName; 46 | _fontUrl = fontFullUrl; 47 | notifyListeners(); 48 | } 49 | 50 | void setTextScale(double textScaleFactor) { 51 | SpUtil.putDouble('fontScale', textScaleFactor); 52 | _textScaleFactor = textScaleFactor; 53 | notifyListeners(); 54 | } 55 | 56 | void setBingBg(bool isOpen) { 57 | SpUtil.putBool('bingBg', isOpen); 58 | _isBingBg = isOpen; 59 | notifyListeners(); 60 | } 61 | 62 | void setLightVersionCheck(bool isOpen) { 63 | SpUtil.putBool('lightVersionCheck', isOpen); 64 | _useLightVersionCheck = isOpen; 65 | notifyListeners(); 66 | } 67 | 68 | void setDataProxy(bool isOpen) { 69 | SpUtil.putBool('dataProxy', isOpen); 70 | _useDataProxy = isOpen; 71 | notifyListeners(); 72 | } 73 | 74 | void setAutoUpdate(bool isOpen) { 75 | SpUtil.putBool('autoUpdate', isOpen); 76 | _useAutoUpdate = isOpen; 77 | notifyListeners(); 78 | } 79 | 80 | void setLeftRightSelect(bool isOpen) { 81 | SpUtil.putBool('leftRightSelect', isOpen); 82 | _useLeftRightSelect = isOpen; 83 | notifyListeners(); 84 | } 85 | 86 | void setPrePlaySerialNum(int serialNum) { 87 | SpUtil.putInt('prePlaySerialNum', serialNum); 88 | _prePlaySerialNum = serialNum; 89 | notifyListeners(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /lib/router_keys.dart: -------------------------------------------------------------------------------- 1 | class RouterKeys { 2 | RouterKeys._(); 3 | static const String subScribe = '/subScribe'; 4 | static const String setting = '/setting'; 5 | static const String settingFont = '/setting/font'; 6 | static const String settingBeautify = '/setting/beautify'; 7 | static const String settingReward = '/setting/reward'; 8 | static const String settingQrScan = '/setting/qrScan'; 9 | } 10 | -------------------------------------------------------------------------------- /lib/setting/qr_scan_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:mobile_scanner/mobile_scanner.dart'; 5 | 6 | class QrScanPage extends StatefulWidget { 7 | const QrScanPage({super.key}); 8 | 9 | @override 10 | State createState() => _QrScanPageState(); 11 | } 12 | 13 | class _QrScanPageState extends State with WidgetsBindingObserver { 14 | final MobileScannerController controller = MobileScannerController(formats: [BarcodeFormat.qrCode]); 15 | 16 | String? _code; 17 | 18 | @override 19 | void initState() { 20 | WidgetsBinding.instance.addObserver(this); 21 | unawaited(controller.start()); 22 | super.initState(); 23 | } 24 | 25 | @override 26 | Future dispose() async { 27 | WidgetsBinding.instance.removeObserver(this); 28 | super.dispose(); 29 | await controller.dispose(); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return Scaffold( 35 | appBar: AppBar( 36 | title: Text('扫一扫'), 37 | ), 38 | body: MobileScanner( 39 | controller: controller, 40 | onDetect: (result) async { 41 | if (_code != null && _code!.isNotEmpty) return; 42 | _code = result.barcodes.first.displayValue; 43 | if (_code != null && _code!.isNotEmpty) { 44 | await controller.stop(); 45 | debugPrint('onCapture::::$_code'); 46 | Navigator.pop(context, _code); 47 | } 48 | }, 49 | ), 50 | ); 51 | } 52 | 53 | @override 54 | void didChangeAppLifecycleState(AppLifecycleState state) { 55 | if (!controller.value.isInitialized) { 56 | return; 57 | } 58 | switch (state) { 59 | case AppLifecycleState.detached: 60 | case AppLifecycleState.hidden: 61 | case AppLifecycleState.paused: 62 | return; 63 | case AppLifecycleState.resumed: 64 | unawaited(controller.start()); 65 | case AppLifecycleState.inactive: 66 | unawaited(controller.stop()); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/setting/reward_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../util/env_util.dart'; 4 | import '../util/http_util.dart'; 5 | 6 | class RewardPage extends StatefulWidget { 7 | const RewardPage({super.key}); 8 | 9 | @override 10 | State createState() => _RewardPageState(); 11 | } 12 | 13 | class _RewardPageState extends State { 14 | String rewardText = ''; 15 | 16 | @override 17 | void initState() { 18 | _loadReward(); 19 | super.initState(); 20 | } 21 | 22 | _loadReward() async { 23 | final res = await HttpUtil().getRequest(EnvUtil.rewardLink(), isShowLoading: false); 24 | if (mounted && res != null && res != '') { 25 | setState(() { 26 | rewardText = res.toString(); 27 | }); 28 | } 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return Scaffold( 34 | appBar: AppBar( 35 | title: const Text('赞赏榜'), 36 | ), 37 | body: SingleChildScrollView( 38 | padding: const EdgeInsets.only(left: 20, right: 20, bottom: 20), 39 | child: Column( 40 | mainAxisSize: MainAxisSize.min, 41 | crossAxisAlignment: CrossAxisAlignment.start, 42 | children: [ 43 | const Text( 44 | '使用微信扫一扫下方赞赏码,支持本软件!', 45 | ), 46 | const Divider(), 47 | Image.asset('assets/images/appreciate.png'), 48 | const SizedBox(height: 20), 49 | const Text( 50 | '🌈特别鸣谢以下老铁!', 51 | style: TextStyle(fontSize: 17), 52 | ), 53 | const Text( 54 | '若有遗漏请前往Github联系我补充!', 55 | style: TextStyle(fontSize: 10), 56 | ), 57 | const Divider(), 58 | Text( 59 | rewardText, 60 | style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500, height: 2.0), 61 | ) 62 | ], 63 | ), 64 | ), 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/setting/setting_beautify_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:easy_tv_live/provider/download_provider.dart'; 2 | import 'package:easy_tv_live/provider/theme_provider.dart'; 3 | import 'package:easy_tv_live/util/check_version_util.dart'; 4 | import 'package:easy_tv_live/util/env_util.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:provider/provider.dart'; 7 | 8 | class SettingBeautifyPage extends StatelessWidget { 9 | const SettingBeautifyPage({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Scaffold( 14 | backgroundColor: const Color(0xFF1E1F22), 15 | appBar: AppBar( 16 | title: const Text('实验设置'), 17 | backgroundColor: const Color(0xFF1E1F22), 18 | leading: const SizedBox.shrink(), 19 | ), 20 | body: Align( 21 | alignment: Alignment.center, 22 | child: Container( 23 | alignment: Alignment.center, 24 | child: ListView( 25 | children: [ 26 | SwitchListTile( 27 | autofocus: true, 28 | title: const Text('背景美化'), 29 | value: context.watch().isBingBg, 30 | subtitle: const Text('未播放时的屏幕背景,每日更换图片'), 31 | onChanged: (value) { 32 | context.read().setBingBg(value); 33 | }, 34 | ), 35 | if (EnvUtil.isTV()) 36 | SwitchListTile( 37 | autofocus: false, 38 | title: const Text('上下键切换频道'), 39 | value: context.watch().useLeftRightSelect, 40 | subtitle: const Text('开启后原先的设置页面和线路选择页面改成左右键打开'), 41 | onChanged: (value) { 42 | context.read().setLeftRightSelect(value); 43 | }, 44 | ), 45 | SwitchListTile( 46 | title: const Text('更新提示免打扰'), 47 | value: context.watch().useLightVersionCheck, 48 | subtitle: const Text('开启后,播放页面的更新弹窗将会变成普通的消息提醒'), 49 | onChanged: (value) { 50 | context.read().setLightVersionCheck(value); 51 | }, 52 | ), 53 | SwitchListTile( 54 | title: const Text('自动更新'), 55 | value: context.watch().useAutoUpdate, 56 | subtitle: const Text('发现新版本将会自动下载并安装'), 57 | onChanged: (value) { 58 | context.read().setAutoUpdate(value); 59 | }, 60 | ), 61 | SwitchListTile( 62 | title: const Text('数据代理'), 63 | value: context.watch().useDataProxy, 64 | subtitle: const Text('Github访问受限的用户需开启'), 65 | onChanged: (value) { 66 | context.read().setDataProxy(value); 67 | }, 68 | ), 69 | Builder(builder: (ctx) { 70 | final provider = context.watch(); 71 | return ListTile( 72 | title: const Text('检查更新'), 73 | trailing: CheckVersionUtil.latestVersionEntity == null 74 | ? const Text('已是最新版本') 75 | : provider.isDownloading 76 | ? Text( 77 | '新版本正在下载中...${(provider.progress * 100).toStringAsFixed(1)}%', 78 | ) 79 | : Text('🔴 发现新版本:v${CheckVersionUtil.latestVersionEntity?.latestVersion}'), 80 | onTap: () { 81 | if (!context.read().isDownloading) { 82 | CheckVersionUtil.checkVersion(context, true, true); 83 | } 84 | }, 85 | ); 86 | }), 87 | if (!CheckVersionUtil.isTV) 88 | ListTile( 89 | title: const Text('应用主页'), 90 | trailing: Icon(Icons.arrow_forward_ios), 91 | onTap: () { 92 | CheckVersionUtil.launchBrowserUrl(CheckVersionUtil.homeLink); 93 | }, 94 | ), 95 | if (!CheckVersionUtil.isTV) 96 | ListTile( 97 | title: const Text('发布历史'), 98 | trailing: Icon(Icons.arrow_forward_ios), 99 | onTap: () { 100 | CheckVersionUtil.launchBrowserUrl(CheckVersionUtil.releaseLink); 101 | }, 102 | ), 103 | ], 104 | ), 105 | ), 106 | ), 107 | ); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lib/tv/html_string.dart: -------------------------------------------------------------------------------- 1 | String getHtmlString(String ipAddress) => ''' 2 | 3 | 4 | 5 | 6 | 7 | 极简TV 8 | 55 | 85 | 86 | 87 |

添加订阅源

88 | 89 |

90 | 91 | 92 | 93 | '''; 94 | -------------------------------------------------------------------------------- /lib/tv/tv_setting_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:easy_tv_live/setting/setting_font_page.dart'; 2 | import 'package:easy_tv_live/setting/subscribe_page.dart'; 3 | import 'package:easy_tv_live/tv/tv_appreciate_page.dart'; 4 | import 'package:easy_tv_live/util/check_version_util.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | import '../setting/setting_beautify_page.dart'; 8 | 9 | class TvSettingPage extends StatefulWidget { 10 | const TvSettingPage({super.key}); 11 | 12 | @override 13 | State createState() => _TvSettingPageState(); 14 | } 15 | 16 | class _TvSettingPageState extends State { 17 | int _selectedIndex = -1; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return Row( 22 | children: [ 23 | SizedBox( 24 | width: 240, 25 | child: Scaffold( 26 | appBar: AppBar( 27 | title: const Text('设置'), 28 | ), 29 | body: Column( 30 | children: [ 31 | Flexible( 32 | child: ListView( 33 | children: [ 34 | ListTile( 35 | leading: const Icon(Icons.card_giftcard), 36 | title: const Text('扫码赞赏'), 37 | selected: _selectedIndex == -1, 38 | autofocus: true, 39 | onTap: () { 40 | setState(() { 41 | _selectedIndex = -1; 42 | }); 43 | }, 44 | ), 45 | ListTile( 46 | leading: const Icon(Icons.subscriptions), 47 | title: const Text('订阅资源'), 48 | selected: _selectedIndex == 0, 49 | onTap: () { 50 | setState(() { 51 | _selectedIndex = 0; 52 | }); 53 | }, 54 | ), 55 | ListTile( 56 | leading: const Icon(Icons.font_download), 57 | title: const Text('字体设置'), 58 | selected: _selectedIndex == 1, 59 | onTap: () { 60 | setState(() { 61 | _selectedIndex = 1; 62 | }); 63 | }, 64 | ), 65 | ListTile( 66 | leading: const Icon(Icons.account_balance_outlined), 67 | title: Text('实验设置 ${CheckVersionUtil.latestVersionEntity == null ? '' : '🔴'}'), 68 | selected: _selectedIndex == 2, 69 | onTap: () { 70 | setState(() { 71 | _selectedIndex = 2; 72 | }); 73 | }, 74 | ) 75 | ], 76 | ), 77 | ), 78 | Container( 79 | alignment: Alignment.centerLeft, 80 | padding: const EdgeInsets.only(left: 16), 81 | child: Text('V${CheckVersionUtil.version}'), 82 | ) 83 | ], 84 | ), 85 | ), 86 | ), 87 | if (_selectedIndex == -1) 88 | const Expanded( 89 | child: TvAppreciatePage(), 90 | ), 91 | if (_selectedIndex == 0) 92 | const Expanded( 93 | child: SubScribePage(isTV: true), 94 | ), 95 | if (_selectedIndex == 1) const Expanded(child: SettingFontPage(isTV: true)), 96 | if (_selectedIndex == 2) const Expanded(child: SettingBeautifyPage()), 97 | ], 98 | ); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/util/bing_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:easy_tv_live/util/http_util.dart'; 2 | 3 | class BingUtil { 4 | static String? bingImgUrl; 5 | 6 | static Future getBingImgUrl() async { 7 | if (bingImgUrl != null && bingImgUrl != '') return bingImgUrl; 8 | final res = await HttpUtil().getRequest('https://bing.biturl.top/', isShowLoading: false); 9 | if (res != null && res['url'] != null && res['url'] != '') { 10 | bingImgUrl = res['url']; 11 | return bingImgUrl; 12 | } 13 | return null; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/util/device_sync_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter_easyloading/flutter_easyloading.dart'; 3 | import 'package:provider/provider.dart'; 4 | import 'package:sp_util/sp_util.dart'; 5 | 6 | import '../entity/subScribe_model.dart'; 7 | import '../provider/theme_provider.dart'; 8 | import 'font_util.dart'; 9 | import 'm3u_util.dart'; 10 | 11 | class DeviceSyncUtil { 12 | DeviceSyncUtil._(); 13 | 14 | static applyAllSettings(BuildContext context, Map syncMap) async { 15 | EasyLoading.show(status: '正在处理'); 16 | final textScaleFactor = syncMap['textScaleFactor']; 17 | final fontFamily = syncMap['fontFamily']; 18 | final fontUrl = syncMap['fontUrl']; 19 | final isBingBg = syncMap['isBingBg']; 20 | final videoSource = syncMap['videoSource']; 21 | 22 | if (textScaleFactor != null) { 23 | context.read().setTextScale(textScaleFactor); 24 | } 25 | 26 | if (isBingBg != null) { 27 | context.read().setBingBg(isBingBg); 28 | } 29 | 30 | if (fontUrl != null || fontFamily != null) { 31 | final fontRes = fontFamily == 'system' ? true : await FontUtil().loadFont(fontUrl, fontFamily); 32 | if (fontRes) { 33 | if (context.mounted) context.read().setFontFamily(fontFamily, fontUrl); 34 | } 35 | } 36 | 37 | if (videoSource != null) { 38 | final source = videoSource as List; 39 | await M3uUtil.saveLocalData(source.map((e) => SubScribeModel.fromJson(e)).toList()); 40 | await SpUtil.remove('m3u_cache'); 41 | } 42 | EasyLoading.showSuccess('同步成功'); 43 | } 44 | 45 | static Future> syncAllSettings(BuildContext context) async { 46 | final textScaleFactor = context.read().textScaleFactor; 47 | final fontFamily = context.read().fontFamily; 48 | final fontUrl = context.read().fontUrl; 49 | final isBingBg = context.read().isBingBg; 50 | final source = await _getVideoSource(); 51 | final msg = {'textScaleFactor': textScaleFactor, 'fontFamily': fontFamily, 'fontUrl': fontUrl, 'isBingBg': isBingBg, 'videoSource': source}; 52 | return msg; 53 | } 54 | 55 | static Future> syncVideoList() async { 56 | final source = await _getVideoSource(); 57 | final msg = {'videoSource': source}; 58 | return msg; 59 | } 60 | 61 | static Future> syncFont(BuildContext context) async { 62 | final textScaleFactor = context.read().textScaleFactor; 63 | final fontFamily = context.read().fontFamily; 64 | final fontUrl = context.read().fontUrl; 65 | final msg = { 66 | 'textScaleFactor': textScaleFactor, 67 | 'fontFamily': fontFamily, 68 | 'fontUrl': fontUrl, 69 | }; 70 | return msg; 71 | } 72 | 73 | static Future> syncPrettify(BuildContext context) async { 74 | final isBingBg = context.read().isBingBg; 75 | final msg = { 76 | 'isBingBg': isBingBg, 77 | }; 78 | return msg; 79 | } 80 | 81 | static Future>> _getVideoSource() async { 82 | final res = await M3uUtil.getLocalData(); 83 | return res.map((e) => e.toJson()).toList(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/util/env_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:sp_util/sp_util.dart'; 4 | 5 | class EnvUtil { 6 | static bool? _isMobile; 7 | static const String proxy = 'https://easytv.suntengfei.top/'; 8 | 9 | static bool isTV() { 10 | return const bool.fromEnvironment('isTV'); 11 | } 12 | 13 | static bool get _isUseProxy => SpUtil.getBool('dataProxy', defValue: true)!; 14 | 15 | static bool get isMobile { 16 | if (_isMobile != null) return _isMobile!; 17 | _isMobile = Platform.isAndroid || Platform.isIOS; 18 | return _isMobile!; 19 | } 20 | 21 | static String sourceDownloadHost() { 22 | return '${_isUseProxy ? proxy : ''}https://github.com/aiyakuaile/easy_tv_live/releases/download'; 23 | } 24 | 25 | static String sourceReleaseHost() { 26 | return 'https://github.com/aiyakuaile/easy_tv_live/releases'; 27 | } 28 | 29 | static String sourceHomeHost() { 30 | return 'https://github.com/aiyakuaile/easy_tv_live'; 31 | } 32 | 33 | static String videoDefaultChannelHost() { 34 | return '${_isUseProxy ? proxy : ''}https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/main/temp'; 35 | } 36 | 37 | static String checkVersionHost() { 38 | return '${_isUseProxy ? proxy : ''}https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/main/versions.json'; 39 | } 40 | 41 | static String fontLink() { 42 | return '${_isUseProxy ? proxy : ''}https://raw.githubusercontent.com/aiyakuaile/easy_tv_font/main'; 43 | } 44 | 45 | static String rewardLink() { 46 | return '${_isUseProxy ? proxy : ''}https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/main/reward.txt'; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/util/font_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:ui'; 3 | 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:path_provider/path_provider.dart'; 6 | 7 | import 'http_util.dart'; 8 | import 'log_util.dart'; 9 | 10 | class FontUtil { 11 | FontUtil._(); 12 | 13 | static final FontUtil _instance = FontUtil._(); 14 | 15 | factory FontUtil() { 16 | return _instance; 17 | } 18 | 19 | Future getFontPath() async { 20 | final path = (await getApplicationSupportDirectory()).path; 21 | return '$path/fonts'; 22 | } 23 | 24 | Future downloadFont(String url, {bool overwrite = false, ValueChanged? progressCallback}) async { 25 | final uri = Uri.parse(url); 26 | final filename = uri.pathSegments.last; 27 | final dir = await getFontPath(); 28 | final fontPath = '$dir/$filename'; 29 | final file = File(fontPath); 30 | if (await file.exists() && !overwrite) { 31 | LogUtil.v('****** font $filename already exists *****${await file.length()}'); 32 | return file.readAsBytes(); 33 | } 34 | final bytes = await downloadBytes(url, filename, fontPath, progressCallback: progressCallback); 35 | return bytes; 36 | } 37 | 38 | Future downloadBytes(String url, String filename, String savePath, {ValueChanged? progressCallback}) async { 39 | final code = await HttpUtil().downloadFile(url, savePath, progressCallback: progressCallback); 40 | if (code == 200) { 41 | return File(savePath).readAsBytes(); 42 | } else { 43 | return null; 44 | } 45 | } 46 | 47 | Future deleteFont(String url) async { 48 | final uri = Uri.parse(url); 49 | final filename = uri.pathSegments.last; 50 | final dir = (await getApplicationSupportDirectory()).path; 51 | final file = File('$dir/$filename'); 52 | if (await file.exists()) { 53 | await file.delete(); 54 | } 55 | } 56 | 57 | Future loadFont(String url, String fontFamily, {ValueChanged? progressCallback}) async { 58 | final fontByte = await downloadFont(url, progressCallback: progressCallback); 59 | if (fontByte == null) return false; 60 | try { 61 | await loadFontFromList(fontByte, fontFamily: fontFamily); 62 | return true; 63 | } catch (e, s) { 64 | debugPrint(e.toString()); 65 | debugPrint(s.toString()); 66 | return false; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/util/http_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:dio/dio.dart'; 5 | import 'package:easy_tv_live/util/log_util.dart'; 6 | import 'package:flutter/cupertino.dart'; 7 | import 'package:flutter_easyloading/flutter_easyloading.dart'; 8 | 9 | import '../generated/l10n.dart'; 10 | 11 | class HttpUtil { 12 | static final HttpUtil _instance = HttpUtil._(); 13 | late Dio _dio; 14 | BaseOptions options = BaseOptions(connectTimeout: const Duration(seconds: 10), receiveTimeout: const Duration(seconds: 10)); 15 | 16 | CancelToken cancelToken = CancelToken(); 17 | 18 | factory HttpUtil() { 19 | return _instance; 20 | } 21 | 22 | HttpUtil._() { 23 | _dio = Dio(options)..interceptors.add(LogInterceptor(requestBody: true, responseBody: true, logPrint: LogUtil.v)); 24 | } 25 | 26 | Future postRequest(String path, 27 | {Object? data, Options? options, CancelToken? cancelToken, ProgressCallback? onReceiveProgress, bool isShowLoading = true}) async { 28 | LogUtil.v('PostRequest::::::$path'); 29 | if (isShowLoading) EasyLoading.show(); 30 | Response? response; 31 | try { 32 | response = await _dio.post(path, data: data, options: options, cancelToken: cancelToken, onReceiveProgress: onReceiveProgress); 33 | if (isShowLoading) EasyLoading.dismiss(); 34 | } on DioException catch (e) { 35 | if (isShowLoading) EasyLoading.dismiss(); 36 | formatError(e, isShowLoading); 37 | } 38 | return response?.data; 39 | } 40 | 41 | Future getRequest(String path, 42 | {Map? queryParameters, 43 | Options? options, 44 | CancelToken? cancelToken, 45 | ProgressCallback? onReceiveProgress, 46 | bool isShowLoading = true}) async { 47 | path = extractCredentials(path, options); 48 | if (isShowLoading) EasyLoading.show(); 49 | Response? response; 50 | try { 51 | response = 52 | await _dio.get(path, queryParameters: queryParameters, options: options, cancelToken: cancelToken, onReceiveProgress: onReceiveProgress); 53 | if (isShowLoading) EasyLoading.dismiss(); 54 | } on DioException catch (e) { 55 | if (isShowLoading) EasyLoading.dismiss(); 56 | formatError(e, isShowLoading); 57 | } 58 | return response?.data; 59 | } 60 | 61 | Future downloadFile(String url, String savePath, {ValueChanged? progressCallback}) async { 62 | Response? response; 63 | try { 64 | // await _dio.head(url); 65 | response = await _dio.download( 66 | url, 67 | savePath, 68 | options: Options( 69 | receiveTimeout: const Duration(seconds: 60), 70 | headers: { 71 | HttpHeaders.acceptEncodingHeader: '*', 72 | HttpHeaders.userAgentHeader: 73 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36', 74 | }, 75 | ), 76 | onReceiveProgress: (received, total) { 77 | if (total <= 0) return; 78 | progressCallback?.call((received / total)); 79 | }, 80 | ); 81 | if (response.statusCode != 200) { 82 | throw DioException(requestOptions: response.requestOptions, error: 'status code ${response.statusCode}'); 83 | } 84 | } on DioException catch (e) { 85 | formatError(e, true); 86 | } 87 | return response?.statusCode ?? 500; 88 | } 89 | } 90 | 91 | String extractCredentials(String url, Options? options) { 92 | if (!url.contains('@')) return url; 93 | final regex = RegExp(r'^(https?):\/\/([^:\/]+:[^@]+)@'); 94 | final match = regex.firstMatch(url); 95 | final us = match?.group(2); 96 | if (us != null && !us.contains('/')) { 97 | final base64Us = base64Encode(utf8.encode(us)); 98 | if (us != null) { 99 | if (options == null) { 100 | options = Options(headers: { 101 | HttpHeaders.authorizationHeader: 'Basic $base64Us', 102 | }); 103 | } else { 104 | options.headers ??= {}; 105 | options.headers![HttpHeaders.authorizationHeader] = 'Basic $base64Us'; 106 | } 107 | } 108 | } 109 | return url; 110 | } 111 | 112 | void formatError(DioException e, bool isShowLoading) { 113 | LogUtil.v('DioException>>>>>$e'); 114 | if (!isShowLoading) return; 115 | if (e.type == DioExceptionType.connectionTimeout) { 116 | EasyLoading.showToast(S.current.netTimeOut); 117 | } else if (e.type == DioExceptionType.sendTimeout) { 118 | EasyLoading.showToast(S.current.netSendTimeout); 119 | } else if (e.type == DioExceptionType.receiveTimeout) { 120 | EasyLoading.showToast(S.current.netReceiveTimeout); 121 | } else if (e.type == DioExceptionType.badResponse) { 122 | EasyLoading.showToast(S.current.netBadResponse(e.response?.statusCode ?? '')); 123 | } else if (e.type == DioExceptionType.cancel) { 124 | EasyLoading.showToast(S.current.netCancel); 125 | } else { 126 | EasyLoading.showToast(e.message.toString()); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /lib/util/latency_checker_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:easy_tv_live/util/log_util.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:http/http.dart' as http; 6 | 7 | class LatencyCheckerUtil { 8 | static const int goodThreshold = 1000; 9 | static const int averageThreshold = 2000; 10 | static const int poorThreshold = 3000; 11 | static const Duration timeoutDuration = Duration(seconds: 5); // 设置超时时间为5秒 12 | static Map latencies = {}; 13 | static Future checkLatencies(String url) async { 14 | if (latencies.containsKey(url)) { 15 | return latencies[url] ?? Colors.transparent; 16 | } 17 | final client = http.Client(); 18 | try { 19 | final stopwatch = Stopwatch()..start(); 20 | await client.get( 21 | Uri.parse(url), 22 | headers: {'Connection': 'close', 'Range': 'bytes=0-99'}, 23 | ).timeout(timeoutDuration); 24 | stopwatch.stop(); 25 | final duration = stopwatch.elapsed.inMilliseconds; 26 | LogUtil.v('url:::成功:::$url::\n:::>>>>$duration'); 27 | latencies[url] = classifyLatency(duration); 28 | } on TimeoutException catch (e) { 29 | LogUtil.v('url:::超时:::$url::\n:::$e'); 30 | latencies[url] = Colors.blueGrey; 31 | } catch (e) { 32 | LogUtil.v('url:::报错:::$url::\n:::$e'); 33 | latencies[url] = Colors.redAccent; 34 | } finally { 35 | client.close(); 36 | } 37 | return latencies[url] ?? Colors.transparent; 38 | } 39 | 40 | static Color classifyLatency(int duration) { 41 | if (duration < goodThreshold) { 42 | return Colors.greenAccent; 43 | } else if (duration < averageThreshold) { 44 | return Colors.blueAccent; 45 | } else { 46 | return Colors.orangeAccent; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/util/log_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | 3 | /// Log Util. 4 | class LogUtil { 5 | static const String _defTag = 'common_utils'; 6 | static bool _debugMode = false; //是否是debug模式,true: log v 不输出. 7 | static int _maxLen = 128; 8 | static String _tagValue = _defTag; 9 | 10 | static void init({ 11 | String tag = _defTag, 12 | bool isDebug = false, 13 | int maxLen = 128, 14 | }) { 15 | _tagValue = tag; 16 | _debugMode = isDebug; 17 | _maxLen = maxLen; 18 | } 19 | 20 | static void d(Object? object, {String? tag}) { 21 | if (_debugMode) { 22 | log('$tag d | ${object?.toString()}'); 23 | } 24 | } 25 | 26 | static void e(Object? object, {String? tag}) { 27 | _printLog(tag, ' e ', object); 28 | } 29 | 30 | static void v(Object? object, {String? tag}) { 31 | if (_debugMode) { 32 | _printLog(tag, ' v ', object); 33 | } 34 | } 35 | 36 | static void _printLog(String? tag, String stag, Object? object) { 37 | String da = object?.toString() ?? 'null'; 38 | tag = tag ?? _tagValue; 39 | if (da.length <= _maxLen) { 40 | print('$tag$stag $da'); 41 | return; 42 | } 43 | print( 44 | '$tag$stag — — — — — — — — — — — — — — — — st — — — — — — — — — — — — — — — —'); 45 | while (da.isNotEmpty) { 46 | if (da.length > _maxLen) { 47 | print('$tag$stag| ${da.substring(0, _maxLen)}'); 48 | da = da.substring(_maxLen, da.length); 49 | } else { 50 | print('$tag$stag| $da'); 51 | da = ''; 52 | } 53 | } 54 | print( 55 | '$tag$stag — — — — — — — — — — — — — — — — ed — — — — — — — — — — — — — — — —'); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/widget/date_position_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../util/date_util.dart'; 4 | 5 | class DatePositionWidget extends StatelessWidget { 6 | const DatePositionWidget({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Positioned( 11 | top: 10, 12 | right: 10, 13 | child: IgnorePointer( 14 | child: Card( 15 | color: Colors.black12, 16 | child: Padding( 17 | padding: const EdgeInsets.all(12.0), 18 | child: Column( 19 | crossAxisAlignment: CrossAxisAlignment.end, 20 | children: [ 21 | Text( 22 | DateUtil.formatDate(DateTime.now(), format: 'HH:mm'), 23 | style: const TextStyle(fontSize: 50), 24 | ), 25 | Text(DateUtil.formatDate(DateTime.now(), format: 'yyyy/MM/dd'), style: const TextStyle(fontSize: 20)), 26 | Text(DateUtil.getWeekday(DateTime.now(), languageCode: 'zh'), style: const TextStyle(fontSize: 20)), 27 | ], 28 | ), 29 | ), 30 | ), 31 | ), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/widget/empty_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../generated/l10n.dart'; 4 | import '../util/env_util.dart'; 5 | 6 | class EmptyPage extends StatelessWidget { 7 | final GestureTapCallback onRefresh; 8 | final GestureTapCallback? onEnterSetting; 9 | const EmptyPage({super.key, required this.onRefresh, this.onEnterSetting}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Center( 14 | child: Column( 15 | crossAxisAlignment: CrossAxisAlignment.center, 16 | mainAxisAlignment: MainAxisAlignment.center, 17 | mainAxisSize: MainAxisSize.min, 18 | children: [ 19 | const Text( 20 | '⚠️', 21 | style: TextStyle(fontSize: 50), 22 | ), 23 | const Text( 24 | '╮(╯▽╰)╭', 25 | textAlign: TextAlign.center, 26 | style: TextStyle(fontSize: 20, color: Colors.white), 27 | ), 28 | ElevatedButton( 29 | style: ElevatedButton.styleFrom( 30 | backgroundColor: Colors.redAccent, 31 | ), 32 | onPressed: onRefresh, 33 | child: Text(' ${EnvUtil.isTV() ? S.current.okRefresh : S.current.refresh} ', style: const TextStyle(color: Colors.white)), 34 | ), 35 | if (!EnvUtil.isMobile) 36 | Padding( 37 | padding: const EdgeInsets.only(top: 20.0), 38 | child: TextButton( 39 | onPressed: onEnterSetting, 40 | child: const Text(' 切换源 ', style: TextStyle(color: Colors.white)), 41 | ), 42 | ), 43 | ], 44 | ), 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/widget/focus_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | class FocusButton extends StatefulWidget { 5 | final String title; 6 | final bool selected; 7 | final GestureTapCallback? onTap; 8 | final ValueChanged? onFocusChange; 9 | final bool autofocus; 10 | final double? fontSize; 11 | const FocusButton({super.key, this.title = '', this.selected = false, this.onTap, this.autofocus = false, this.onFocusChange, this.fontSize}); 12 | 13 | @override 14 | State createState() => _FocusButtonState(); 15 | } 16 | 17 | class _FocusButtonState extends State { 18 | late bool _isFocus = widget.autofocus; 19 | late bool _isSelected = widget.selected; 20 | 21 | @override 22 | void didUpdateWidget(covariant FocusButton oldWidget) { 23 | if (oldWidget.selected != widget.selected) { 24 | setState(() { 25 | _isSelected = widget.selected; 26 | }); 27 | } 28 | super.didUpdateWidget(oldWidget); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return Focus( 34 | onFocusChange: (bool isFocus) { 35 | setState(() { 36 | _isFocus = isFocus; 37 | }); 38 | widget.onFocusChange?.call(isFocus); 39 | }, 40 | onKeyEvent: (FocusNode node, KeyEvent event) { 41 | final isUpKey = event is KeyUpEvent; 42 | if (!isUpKey || _isSelected) return KeyEventResult.ignored; 43 | if (event.logicalKey == LogicalKeyboardKey.select) { 44 | _isSelected = true; 45 | setState(() {}); 46 | widget.onTap?.call(); 47 | } 48 | return KeyEventResult.ignored; 49 | }, 50 | autofocus: widget.autofocus, 51 | child: PhysicalModel( 52 | shadowColor: _isFocus ? Colors.redAccent : Colors.transparent, 53 | elevation: _isFocus ? 30 : 0, 54 | color: Colors.transparent, 55 | shape: BoxShape.rectangle, 56 | child: GestureDetector( 57 | onTap: () { 58 | setState(() { 59 | _isSelected = true; 60 | }); 61 | widget.onTap?.call(); 62 | }, 63 | child: AnimatedContainer( 64 | duration: const Duration(milliseconds: 300), 65 | constraints: const BoxConstraints(minWidth: 60), 66 | padding: _isFocus ? const EdgeInsets.symmetric(horizontal: 30, vertical: 10) : const EdgeInsets.symmetric(horizontal: 15, vertical: 10), 67 | decoration: BoxDecoration( 68 | color: buttonBackgroundColor(), 69 | border: Border.all( 70 | width: 1, 71 | color: buttonBorderColor(), 72 | ), 73 | borderRadius: BorderRadius.circular(100), 74 | ), 75 | child: Text( 76 | widget.title, 77 | style: TextStyle(color: buttonTextColor(), fontSize: widget.fontSize), 78 | ), 79 | ), 80 | ), 81 | )); 82 | } 83 | 84 | Color buttonBackgroundColor() { 85 | if (_isSelected) { 86 | return Colors.transparent; 87 | } 88 | if (_isFocus) { 89 | return Colors.redAccent; 90 | } 91 | return const Color(0xFF393B40); 92 | } 93 | 94 | Color buttonBorderColor() { 95 | if (_isSelected) { 96 | return Colors.redAccent; 97 | } 98 | return const Color(0xFF393B40); 99 | } 100 | 101 | Color buttonTextColor() { 102 | if (_isSelected) { 103 | return Colors.redAccent; 104 | } 105 | if (_isFocus) { 106 | return Colors.white; 107 | } 108 | return Colors.white54; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /lib/widget/focus_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../entity/subScribe_model.dart'; 4 | import '../generated/l10n.dart'; 5 | 6 | class FocusCard extends StatefulWidget { 7 | final SubScribeModel model; 8 | final GestureTapCallback? onDelete; 9 | final GestureTapCallback? onUse; 10 | const FocusCard({super.key, required this.model, required this.onDelete, this.onUse}); 11 | 12 | @override 13 | State createState() => _FocusCardState(); 14 | } 15 | 16 | class _FocusCardState extends State { 17 | _onFocusChange(bool isFocus) { 18 | if (isFocus) { 19 | Scrollable.ensureVisible(context, alignment: 0.5, duration: const Duration(milliseconds: 300), curve: Curves.linear); 20 | } 21 | } 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return Container( 26 | padding: const EdgeInsets.only(top: 20, left: 20, right: 10, bottom: 12), 27 | decoration: BoxDecoration( 28 | color: widget.model.selected == true ? Colors.redAccent.withOpacity(0.5) : const Color(0xFF2B2D30), 29 | borderRadius: BorderRadius.circular(12)), 30 | child: Column( 31 | mainAxisSize: MainAxisSize.min, 32 | crossAxisAlignment: CrossAxisAlignment.start, 33 | children: [ 34 | Text( 35 | widget.model.link == 'default' ? widget.model.link! : widget.model.link!.split('?').first.split('/').last.toString(), 36 | style: const TextStyle(fontSize: 20), 37 | ), 38 | const SizedBox(height: 12), 39 | Text( 40 | '${S.current.createTime}:${widget.model.time}', 41 | style: TextStyle(color: Colors.white.withOpacity(0.5), fontSize: 14), 42 | ), 43 | const SizedBox(height: 12), 44 | Row( 45 | mainAxisSize: MainAxisSize.min, 46 | children: [ 47 | const Spacer(), 48 | if (widget.model.selected != true && widget.model.link != 'default') 49 | TextButton( 50 | onFocusChange: _onFocusChange, 51 | onPressed: () async { 52 | final isDelete = await showDialog( 53 | context: context, 54 | builder: (context) { 55 | return AlertDialog( 56 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), 57 | backgroundColor: const Color(0xFF393B40), 58 | content: Text( 59 | S.current.dialogDeleteContent, 60 | style: const TextStyle(color: Colors.white, fontSize: 20), 61 | ), 62 | actions: [ 63 | TextButton( 64 | autofocus: true, 65 | onPressed: () { 66 | Navigator.pop(context, false); 67 | }, 68 | child: Text( 69 | S.current.dialogCancel, 70 | style: const TextStyle(fontSize: 17), 71 | ), 72 | ), 73 | TextButton( 74 | onPressed: () { 75 | Navigator.pop(context, true); 76 | }, 77 | child: Text( 78 | S.current.dialogConfirm, 79 | style: const TextStyle(fontSize: 17), 80 | ), 81 | ), 82 | ], 83 | ); 84 | }, 85 | ); 86 | if (isDelete == true) { 87 | widget.onDelete?.call(); 88 | } 89 | }, 90 | child: Text(S.current.delete)), 91 | TextButton( 92 | onFocusChange: _onFocusChange, 93 | onPressed: widget.model.selected != true ? widget.onUse : () {}, 94 | child: Text(widget.model.selected != true ? S.current.setDefault : S.current.inUse), 95 | ), 96 | ], 97 | ) 98 | ], 99 | ), 100 | ); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/widget/no_scroller_behavior.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | class NoScrollBehavior extends ScrollBehavior { 4 | @override 5 | Widget buildViewportChrome( 6 | BuildContext context, Widget child, ScrollableDetails details) { 7 | return child; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/widget/update_download_btn.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:easy_tv_live/provider/download_provider.dart'; 4 | import 'package:easy_tv_live/util/env_util.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:provider/provider.dart'; 7 | 8 | import '../generated/l10n.dart'; 9 | 10 | class UpdateDownloadBtn extends StatefulWidget { 11 | final String apkUrl; 12 | 13 | const UpdateDownloadBtn({super.key, this.apkUrl = ''}); 14 | 15 | @override 16 | State createState() => _UpdateDownloadBtnState(); 17 | } 18 | 19 | class _UpdateDownloadBtnState extends State { 20 | bool _isFocusDownload = true; 21 | double btnWidth = EnvUtil.isTV() ? 400 : 260; 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return Consumer( 26 | builder: (BuildContext context, DownloadProvider provider, Widget? child) { 27 | return provider.isDownloading 28 | ? ClipRRect( 29 | borderRadius: BorderRadius.circular(44), 30 | child: SizedBox( 31 | height: 44, 32 | width: btnWidth, 33 | child: Stack( 34 | clipBehavior: Clip.hardEdge, 35 | alignment: Alignment.center, 36 | children: [ 37 | Positioned.fill( 38 | child: LinearProgressIndicator( 39 | value: provider.progress, 40 | backgroundColor: Colors.redAccent.withOpacity(0.2), 41 | color: Colors.redAccent, 42 | ), 43 | ), 44 | Text( 45 | '下载中...${(provider.progress * 100).toStringAsFixed(1)}%', 46 | style: const TextStyle(color: Colors.white), 47 | ) 48 | ], 49 | ), 50 | ), 51 | ) 52 | : child!; 53 | }, 54 | child: ElevatedButton( 55 | style: ElevatedButton.styleFrom( 56 | fixedSize: Size(btnWidth, 44), 57 | backgroundColor: _isFocusDownload ? Colors.redAccent : Colors.redAccent.withOpacity(0.3), 58 | elevation: _isFocusDownload ? 10 : 0, 59 | overlayColor: Colors.transparent), 60 | autofocus: true, 61 | onFocusChange: (bool isFocus) { 62 | setState(() { 63 | _isFocusDownload = isFocus; 64 | }); 65 | }, 66 | onPressed: () { 67 | if (Platform.isAndroid) { 68 | context.read().downloadApk(widget.apkUrl); 69 | } else { 70 | Navigator.of(context).pop(true); 71 | } 72 | }, 73 | child: Text( 74 | S.current.update, 75 | style: const TextStyle(color: Colors.white), 76 | ), 77 | ), 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/widget/video_hold_bg.dart: -------------------------------------------------------------------------------- 1 | import 'package:easy_tv_live/util/bing_util.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_spinkit/src/spinning_lines.dart'; 4 | import 'package:provider/provider.dart'; 5 | 6 | import '../generated/l10n.dart'; 7 | import '../provider/theme_provider.dart'; 8 | 9 | class VideoHoldBg extends StatelessWidget { 10 | final String? toastString; 11 | 12 | const VideoHoldBg({Key? key, required this.toastString}) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Selector( 17 | selector: (_, provider) => provider.isBingBg, 18 | builder: (BuildContext context, bool isBingBg, Widget? child) { 19 | if (isBingBg) { 20 | return FutureBuilder( 21 | future: BingUtil.getBingImgUrl(), 22 | builder: (BuildContext context, AsyncSnapshot snapshot) { 23 | late ImageProvider image; 24 | if (snapshot.hasData && snapshot.data != null) { 25 | image = NetworkImage(snapshot.data!); 26 | } else { 27 | image = const AssetImage('assets/images/video_bg.png'); 28 | } 29 | return Container( 30 | padding: const EdgeInsets.only(top: 30, bottom: 30), 31 | decoration: BoxDecoration( 32 | image: DecorationImage( 33 | fit: BoxFit.cover, 34 | image: image, 35 | ), 36 | ), 37 | child: child, 38 | ); 39 | }, 40 | ); 41 | } 42 | return Container( 43 | padding: const EdgeInsets.only(top: 30, bottom: 30), 44 | decoration: const BoxDecoration( 45 | image: DecorationImage( 46 | fit: BoxFit.cover, 47 | image: AssetImage('assets/images/video_bg.png'), 48 | ), 49 | ), 50 | child: child, 51 | ); 52 | }, 53 | child: Column( 54 | children: [ 55 | SpinKitSpinningLines(color: toastString == '' ? Colors.transparent : Colors.white, size: 40), 56 | const Spacer(), 57 | FittedBox( 58 | child: Padding( 59 | padding: const EdgeInsets.symmetric(horizontal: 20.0), 60 | child: Text(toastString ?? S.current.loading, style: const TextStyle(color: Colors.white, fontSize: 16)), 61 | ), 62 | ), 63 | ], 64 | ), 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/widget/volume_brightness_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_volume_controller/flutter_volume_controller.dart'; 3 | import 'package:screen_brightness/screen_brightness.dart'; 4 | 5 | class VolumeBrightnessWidget extends StatefulWidget { 6 | const VolumeBrightnessWidget({super.key}); 7 | 8 | @override 9 | State createState() => _VolumeBrightnessWidgetState(); 10 | } 11 | 12 | class _VolumeBrightnessWidgetState extends State { 13 | double _volume = 0.5; 14 | double _brightness = 0.5; 15 | 16 | // 1.brightness 17 | // 2.volume 18 | int _controlType = 0; 19 | 20 | @override 21 | void initState() { 22 | _loadSystemData(); 23 | super.initState(); 24 | } 25 | 26 | _loadSystemData() async { 27 | _brightness = await ScreenBrightness().current; 28 | _volume = await FlutterVolumeController.getVolume() ?? 0.5; 29 | await FlutterVolumeController.updateShowSystemUI(false); 30 | setState(() {}); 31 | } 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return Padding( 36 | padding: const EdgeInsets.all(44), 37 | child: GestureDetector( 38 | behavior: HitTestBehavior.translucent, 39 | onVerticalDragStart: (DragStartDetails details) { 40 | final width = MediaQuery.of(context).size.width; 41 | if (details.localPosition.dx > width / 2) { 42 | _controlType = 2; 43 | } else { 44 | _controlType = 1; 45 | } 46 | }, 47 | onVerticalDragUpdate: (DragUpdateDetails details) { 48 | if (_controlType == 2) { 49 | _volume = (_volume + (-details.delta.dy / 500)).clamp(0.0, 1.0); 50 | FlutterVolumeController.setVolume(_volume); 51 | } else { 52 | _brightness = 53 | (_brightness + (-details.delta.dy / 500)).clamp(0.0, 1.0); 54 | ScreenBrightness().setScreenBrightness(_brightness); 55 | } 56 | setState(() {}); 57 | }, 58 | onVerticalDragEnd: (DragEndDetails details) { 59 | setState(() { 60 | _controlType = 0; 61 | }); 62 | }, 63 | onVerticalDragCancel: () { 64 | setState(() { 65 | _controlType = 0; 66 | }); 67 | }, 68 | child: Container( 69 | alignment: Alignment.topCenter, 70 | padding: const EdgeInsets.only(top: 20), 71 | child: _controlType == 0 72 | ? null 73 | : Container( 74 | width: 150, 75 | height: 30, 76 | padding: 77 | const EdgeInsets.symmetric(vertical: 5, horizontal: 12), 78 | decoration: BoxDecoration( 79 | color: Colors.black.withOpacity(0.5), 80 | borderRadius: BorderRadius.circular(15), 81 | ), 82 | child: Row( 83 | mainAxisAlignment: MainAxisAlignment.center, 84 | children: [ 85 | Icon( 86 | _controlType == 1 87 | ? Icons.light_mode 88 | : Icons.volume_up_outlined, 89 | color: Colors.white, 90 | size: 20, 91 | ), 92 | const SizedBox( 93 | width: 5, 94 | ), 95 | Expanded( 96 | child: LinearProgressIndicator( 97 | value: _controlType == 1 ? _brightness : _volume, 98 | backgroundColor: Colors.white.withOpacity(0.5), 99 | color: Colors.redAccent, 100 | borderRadius: BorderRadius.circular(10), 101 | ), 102 | ) 103 | ], 104 | ), 105 | ), 106 | ), 107 | ), 108 | ); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.10) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | 12 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 13 | # which isn't available in 3.10. 14 | function(list_prepend LIST_NAME PREFIX) 15 | set(NEW_LIST "") 16 | foreach(element ${${LIST_NAME}}) 17 | list(APPEND NEW_LIST "${PREFIX}${element}") 18 | endforeach(element) 19 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 20 | endfunction() 21 | 22 | # === Flutter Library === 23 | # System-level dependencies. 24 | find_package(PkgConfig REQUIRED) 25 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 26 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 27 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 28 | 29 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 30 | 31 | # Published to parent scope for install step. 32 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 33 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 34 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 35 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 36 | 37 | list(APPEND FLUTTER_LIBRARY_HEADERS 38 | "fl_basic_message_channel.h" 39 | "fl_binary_codec.h" 40 | "fl_binary_messenger.h" 41 | "fl_dart_project.h" 42 | "fl_engine.h" 43 | "fl_json_message_codec.h" 44 | "fl_json_method_codec.h" 45 | "fl_message_codec.h" 46 | "fl_method_call.h" 47 | "fl_method_channel.h" 48 | "fl_method_codec.h" 49 | "fl_method_response.h" 50 | "fl_plugin_registrar.h" 51 | "fl_plugin_registry.h" 52 | "fl_standard_message_codec.h" 53 | "fl_standard_method_codec.h" 54 | "fl_string_codec.h" 55 | "fl_value.h" 56 | "fl_view.h" 57 | "flutter_linux.h" 58 | ) 59 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 60 | add_library(flutter INTERFACE) 61 | target_include_directories(flutter INTERFACE 62 | "${EPHEMERAL_DIR}" 63 | ) 64 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 65 | target_link_libraries(flutter INTERFACE 66 | PkgConfig::GTK 67 | PkgConfig::GLIB 68 | PkgConfig::GIO 69 | ) 70 | add_dependencies(flutter flutter_assemble) 71 | 72 | # === Flutter tool backend === 73 | # _phony_ is a non-existent file to force this command to run every time, 74 | # since currently there's no way to get a full input/output list from the 75 | # flutter tool. 76 | add_custom_command( 77 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 78 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 79 | COMMAND ${CMAKE_COMMAND} -E env 80 | ${FLUTTER_TOOL_ENVIRONMENT} 81 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 82 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 83 | VERBATIM 84 | ) 85 | add_custom_target(flutter_assemble DEPENDS 86 | "${FLUTTER_LIBRARY}" 87 | ${FLUTTER_LIBRARY_HEADERS} 88 | ) 89 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | void fl_register_plugins(FlPluginRegistry* registry) { 16 | g_autoptr(FlPluginRegistrar) flutter_volume_controller_registrar = 17 | fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterVolumeControllerPlugin"); 18 | flutter_volume_controller_plugin_register_with_registrar(flutter_volume_controller_registrar); 19 | g_autoptr(FlPluginRegistrar) fvp_registrar = 20 | fl_plugin_registry_get_registrar_for_plugin(registry, "FvpPlugin"); 21 | fvp_plugin_register_with_registrar(fvp_registrar); 22 | g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar = 23 | fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin"); 24 | screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar); 25 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 26 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 27 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 28 | g_autoptr(FlPluginRegistrar) window_manager_registrar = 29 | fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); 30 | window_manager_plugin_register_with_registrar(window_manager_registrar); 31 | } 32 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | flutter_volume_controller 7 | fvp 8 | screen_retriever_linux 9 | url_launcher_linux 10 | window_manager 11 | ) 12 | 13 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 14 | ) 15 | 16 | set(PLUGIN_BUNDLED_LIBRARIES) 17 | 18 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 19 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 20 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 23 | endforeach(plugin) 24 | 25 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 26 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 27 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 28 | endforeach(ffi_plugin) 29 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /linux/my_application.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | #include 4 | #ifdef GDK_WINDOWING_X11 5 | #include 6 | #endif 7 | 8 | #include "flutter/generated_plugin_registrant.h" 9 | 10 | struct _MyApplication { 11 | GtkApplication parent_instance; 12 | char** dart_entrypoint_arguments; 13 | }; 14 | 15 | G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) 16 | 17 | // Implements GApplication::activate. 18 | static void my_application_activate(GApplication* application) { 19 | MyApplication* self = MY_APPLICATION(application); 20 | GtkWindow* window = 21 | GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); 22 | 23 | // Use a header bar when running in GNOME as this is the common style used 24 | // by applications and is the setup most users will be using (e.g. Ubuntu 25 | // desktop). 26 | // If running on X and not using GNOME then just use a traditional title bar 27 | // in case the window manager does more exotic layout, e.g. tiling. 28 | // If running on Wayland assume the header bar will work (may need changing 29 | // if future cases occur). 30 | gboolean use_header_bar = TRUE; 31 | #ifdef GDK_WINDOWING_X11 32 | GdkScreen* screen = gtk_window_get_screen(window); 33 | if (GDK_IS_X11_SCREEN(screen)) { 34 | const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); 35 | if (g_strcmp0(wm_name, "GNOME Shell") != 0) { 36 | use_header_bar = FALSE; 37 | } 38 | } 39 | #endif 40 | if (use_header_bar) { 41 | GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); 42 | gtk_widget_show(GTK_WIDGET(header_bar)); 43 | gtk_header_bar_set_title(header_bar, "easy_tv_live"); 44 | gtk_header_bar_set_show_close_button(header_bar, TRUE); 45 | gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); 46 | } else { 47 | gtk_window_set_title(window, "easy_tv_live"); 48 | } 49 | 50 | gtk_window_set_default_size(window, 1280, 720); 51 | gtk_widget_show(GTK_WIDGET(window)); 52 | 53 | g_autoptr(FlDartProject) project = fl_dart_project_new(); 54 | fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); 55 | 56 | FlView* view = fl_view_new(project); 57 | gtk_widget_show(GTK_WIDGET(view)); 58 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); 59 | 60 | fl_register_plugins(FL_PLUGIN_REGISTRY(view)); 61 | 62 | gtk_widget_grab_focus(GTK_WIDGET(view)); 63 | } 64 | 65 | // Implements GApplication::local_command_line. 66 | static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { 67 | MyApplication* self = MY_APPLICATION(application); 68 | // Strip out the first argument as it is the binary name. 69 | self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); 70 | 71 | g_autoptr(GError) error = nullptr; 72 | if (!g_application_register(application, nullptr, &error)) { 73 | g_warning("Failed to register: %s", error->message); 74 | *exit_status = 1; 75 | return TRUE; 76 | } 77 | 78 | g_application_activate(application); 79 | *exit_status = 0; 80 | 81 | return TRUE; 82 | } 83 | 84 | // Implements GApplication::startup. 85 | static void my_application_startup(GApplication* application) { 86 | //MyApplication* self = MY_APPLICATION(object); 87 | 88 | // Perform any actions required at application startup. 89 | 90 | G_APPLICATION_CLASS(my_application_parent_class)->startup(application); 91 | } 92 | 93 | // Implements GApplication::shutdown. 94 | static void my_application_shutdown(GApplication* application) { 95 | //MyApplication* self = MY_APPLICATION(object); 96 | 97 | // Perform any actions required at application shutdown. 98 | 99 | G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); 100 | } 101 | 102 | // Implements GObject::dispose. 103 | static void my_application_dispose(GObject* object) { 104 | MyApplication* self = MY_APPLICATION(object); 105 | g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); 106 | G_OBJECT_CLASS(my_application_parent_class)->dispose(object); 107 | } 108 | 109 | static void my_application_class_init(MyApplicationClass* klass) { 110 | G_APPLICATION_CLASS(klass)->activate = my_application_activate; 111 | G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; 112 | G_APPLICATION_CLASS(klass)->startup = my_application_startup; 113 | G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; 114 | G_OBJECT_CLASS(klass)->dispose = my_application_dispose; 115 | } 116 | 117 | static void my_application_init(MyApplication* self) {} 118 | 119 | MyApplication* my_application_new() { 120 | return MY_APPLICATION(g_object_new(my_application_get_type(), 121 | "application-id", APPLICATION_ID, 122 | "flags", G_APPLICATION_NON_UNIQUE, 123 | nullptr)); 124 | } 125 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import flutter_volume_controller 9 | import fvp 10 | import mobile_scanner 11 | import network_info_plus 12 | import package_info_plus 13 | import path_provider_foundation 14 | import screen_brightness_macos 15 | import screen_retriever_macos 16 | import shared_preferences_foundation 17 | import url_launcher_macos 18 | import video_player_avfoundation 19 | import wakelock_plus 20 | import window_manager 21 | 22 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 23 | FlutterVolumeControllerPlugin.register(with: registry.registrar(forPlugin: "FlutterVolumeControllerPlugin")) 24 | FvpPlugin.register(with: registry.registrar(forPlugin: "FvpPlugin")) 25 | MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) 26 | NetworkInfoPlusPlugin.register(with: registry.registrar(forPlugin: "NetworkInfoPlusPlugin")) 27 | FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) 28 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 29 | ScreenBrightnessMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenBrightnessMacosPlugin")) 30 | ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) 31 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) 32 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 33 | FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) 34 | WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) 35 | WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) 36 | } 37 | -------------------------------------------------------------------------------- /macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.14' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | target 'RunnerTests' do 35 | inherit! :search_paths 36 | end 37 | end 38 | 39 | post_install do |installer| 40 | installer.pods_project.targets.each do |target| 41 | flutter_additional_macos_build_settings(target) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /macos/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - flutter_volume_controller (0.0.1): 3 | - FlutterMacOS 4 | - FlutterMacOS (1.0.0) 5 | - fvp (0.29.0): 6 | - Flutter 7 | - FlutterMacOS 8 | - mdk (~> 0.30.1) 9 | - mdk (0.30.1) 10 | - mobile_scanner (6.0.2): 11 | - FlutterMacOS 12 | - network_info_plus (0.0.1): 13 | - FlutterMacOS 14 | - package_info_plus (0.0.1): 15 | - FlutterMacOS 16 | - path_provider_foundation (0.0.1): 17 | - Flutter 18 | - FlutterMacOS 19 | - screen_brightness_macos (0.1.0): 20 | - FlutterMacOS 21 | - screen_retriever_macos (0.0.1): 22 | - FlutterMacOS 23 | - shared_preferences_foundation (0.0.1): 24 | - Flutter 25 | - FlutterMacOS 26 | - url_launcher_macos (0.0.1): 27 | - FlutterMacOS 28 | - video_player_avfoundation (0.0.1): 29 | - Flutter 30 | - FlutterMacOS 31 | - wakelock_plus (0.0.1): 32 | - FlutterMacOS 33 | - window_manager (0.2.0): 34 | - FlutterMacOS 35 | 36 | DEPENDENCIES: 37 | - flutter_volume_controller (from `Flutter/ephemeral/.symlinks/plugins/flutter_volume_controller/macos`) 38 | - FlutterMacOS (from `Flutter/ephemeral`) 39 | - fvp (from `Flutter/ephemeral/.symlinks/plugins/fvp/darwin`) 40 | - mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos`) 41 | - network_info_plus (from `Flutter/ephemeral/.symlinks/plugins/network_info_plus/macos`) 42 | - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) 43 | - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) 44 | - screen_brightness_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos`) 45 | - screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`) 46 | - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) 47 | - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) 48 | - video_player_avfoundation (from `Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin`) 49 | - wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`) 50 | - window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`) 51 | 52 | SPEC REPOS: 53 | trunk: 54 | - mdk 55 | 56 | EXTERNAL SOURCES: 57 | flutter_volume_controller: 58 | :path: Flutter/ephemeral/.symlinks/plugins/flutter_volume_controller/macos 59 | FlutterMacOS: 60 | :path: Flutter/ephemeral 61 | fvp: 62 | :path: Flutter/ephemeral/.symlinks/plugins/fvp/darwin 63 | mobile_scanner: 64 | :path: Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos 65 | network_info_plus: 66 | :path: Flutter/ephemeral/.symlinks/plugins/network_info_plus/macos 67 | package_info_plus: 68 | :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos 69 | path_provider_foundation: 70 | :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin 71 | screen_brightness_macos: 72 | :path: Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos 73 | screen_retriever_macos: 74 | :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos 75 | shared_preferences_foundation: 76 | :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin 77 | url_launcher_macos: 78 | :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos 79 | video_player_avfoundation: 80 | :path: Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin 81 | wakelock_plus: 82 | :path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos 83 | window_manager: 84 | :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos 85 | 86 | SPEC CHECKSUMS: 87 | flutter_volume_controller: 25d09126b0d695560f11c80b1311d5063fed882f 88 | FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 89 | fvp: 3dd6ca00575184c8fb7675426e54b964c991b91c 90 | mdk: 5b3a56ae10c666309c1796bd72968e91db4d670a 91 | mobile_scanner: 07710d6b9b2c220ae899de2d7ecf5d77ffa56333 92 | network_info_plus: 2cb02d8435635eae13b3b79279681985121cf30c 93 | package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b 94 | path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 95 | screen_brightness_macos: 2d6d3af2165592d9a55ffcd95b7550970e41ebda 96 | screen_retriever_macos: 776e0fa5d42c6163d2bf772d22478df4b302b161 97 | shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 98 | url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 99 | video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3 100 | wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269 101 | window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8 102 | 103 | PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 104 | 105 | COCOAPODS: 1.15.2 106 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @main 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | 10 | override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { 11 | return true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "size": "16x16", 5 | "idiom": "mac", 6 | "filename": "icon-16.png", 7 | "scale": "1x" 8 | }, 9 | { 10 | "size": "16x16", 11 | "idiom": "mac", 12 | "filename": "icon-16@2x.png", 13 | "scale": "2x" 14 | }, 15 | { 16 | "size": "32x32", 17 | "idiom": "mac", 18 | "filename": "icon-32.png", 19 | "scale": "1x" 20 | }, 21 | { 22 | "size": "32x32", 23 | "idiom": "mac", 24 | "filename": "icon-32@2x.png", 25 | "scale": "2x" 26 | }, 27 | { 28 | "size": "128x128", 29 | "idiom": "mac", 30 | "filename": "icon-128.png", 31 | "scale": "1x" 32 | }, 33 | { 34 | "size": "128x128", 35 | "idiom": "mac", 36 | "filename": "icon-128@2x.png", 37 | "scale": "2x" 38 | }, 39 | { 40 | "size": "256x256", 41 | "idiom": "mac", 42 | "filename": "icon-256.png", 43 | "scale": "1x" 44 | }, 45 | { 46 | "size": "256x256", 47 | "idiom": "mac", 48 | "filename": "icon-256@2x.png", 49 | "scale": "2x" 50 | }, 51 | { 52 | "size": "512x512", 53 | "idiom": "mac", 54 | "filename": "icon-512.png", 55 | "scale": "1x" 56 | }, 57 | { 58 | "size": "512x512", 59 | "idiom": "mac", 60 | "filename": "icon-512@2x.png", 61 | "scale": "2x" 62 | } 63 | ], 64 | "info": { 65 | "version": 1, 66 | "author": "icon.wuruihong.com" 67 | } 68 | } -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-128@2x.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-16@2x.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-256@2x.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-32@2x.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/macos/Runner/Assets.xcassets/AppIcon.appiconset/icon-512@2x.png -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = 极简TV 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.suntengfei.easyTvLive 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2024 aiyakuaile. All rights reserved. 15 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | NSHumanReadableCopyright 31 | $(PRODUCT_COPYRIGHT) 32 | NSMainNibFile 33 | MainMenu 34 | NSPrincipalClass 35 | NSApplication 36 | 37 | 38 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.network.server 6 | 7 | com.apple.security.app-sandbox 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /macos/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: easy_tv_live 2 | description: 极简电视直播App,简单易用的IPTV播放器,支持Android、iOS 、电视大屏欢迎下载体验! 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 6 | publish_to: "none" # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 2.9.1+10260206 19 | 20 | environment: 21 | sdk: ">=3.4.0 <4.0.0" 22 | 23 | # Dependencies specify other packages that your package needs in order to work. 24 | # To automatically upgrade your package dependencies to the latest versions 25 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 26 | # dependencies can be manually updated by changing the version numbers below to 27 | # the latest version available on pub.dev. To see which dependencies have newer 28 | # versions available, run `flutter pub outdated`. 29 | dependencies: 30 | flutter: 31 | sdk: flutter 32 | flutter_localizations: 33 | sdk: flutter 34 | 35 | fvp: ^0.29.0 36 | video_player: ^2.9.2 37 | responsive_builder: ^0.7.1 38 | flutter_easyloading: ^3.0.3 39 | wakelock_plus: ^1.2.10 40 | flutter_spinkit: ^5.1.0 41 | dio: ^5.5.0+1 42 | sp_util: ^2.0.3 43 | url_launcher: ^6.3.0 44 | flutter_volume_controller: ^1.3.2 45 | screen_brightness: ^2.0.1 46 | pretty_qr_code: ^3.3.0 47 | window_manager: ^0.4.3 48 | intl: ^0.19.0 49 | scrollable_positioned_list: ^0.3.8 50 | path_provider: ^2.1.4 51 | # file_picker: ^8.1.2 52 | provider: ^6.1.2 53 | xml: ^6.5.0 54 | apk_installer: ^0.0.4 55 | http: ^1.2.2 56 | network_info_plus: ^6.1.2 57 | mobile_scanner: ^6.0.2 58 | 59 | dev_dependencies: 60 | flutter_test: 61 | sdk: flutter 62 | 63 | # The "flutter_lints" package below contains a set of recommended lints to 64 | # encourage good coding practices. The lint set provided by the package is 65 | # activated in the `analysis_options.yaml` file located at the root of your 66 | # package. See that file for information about deactivating specific lint 67 | # rules and activating additional ones. 68 | flutter_lints: ^5.0.0 69 | 70 | # For information on the generic Dart part of this file, see the 71 | # following page: https://dart.dev/tools/pub/pubspec 72 | 73 | # The following section is specific to Flutter. 74 | flutter: 75 | # The following line ensures that the Material Icons font is 76 | # included with your application, so that you can use the icons in 77 | # the material Icons class. 78 | uses-material-design: true 79 | # To add assets to your application, add an assets section, like this: 80 | assets: 81 | - assets/images/ 82 | # An image asset can refer to one or more resolution-specific "variants", see 83 | # https://flutter.dev/assets-and-images/#resolution-aware. 84 | # For details regarding adding assets from package dependencies, see 85 | # https://flutter.dev/assets-and-images/#from-packages 86 | # To add custom fonts to your application, add a fonts section here, 87 | # in this "flutter" section. Each entry in this list should have a 88 | # "family" key with the font family name, and a "fonts" key with a 89 | # list giving the asset and other descriptors for the font. For 90 | # example: 91 | # fonts: 92 | # - family: Kaiti 93 | # fonts: 94 | # - asset: assets/resources/Kaiti.ttf 95 | # - family: Trajan Pro 96 | # fonts: 97 | # - asset: fonts/TrajanPro.ttf 98 | # - asset: fonts/TrajanPro_Bold.ttf 99 | # weight: 700 100 | # 101 | # For details regarding fonts from package dependencies, 102 | # see https://flutter.dev/custom-fonts/#from-packages 103 | flutter_intl: 104 | enabled: true 105 | arb_dir: lib/l10n 106 | class_name: S 107 | main_locale: zh_CN 108 | 109 | 110 | -------------------------------------------------------------------------------- /reward.txt: -------------------------------------------------------------------------------- 1 | 付款留言提出的应用问题 无法进行回应! 2 | 如遇问题请前往Github提交Issues 方便解答问题! 3 | 4 | SunkistSu 20元 5 | Joysen 20元 6 | 王龙峰 20元 7 | 李肖瑜 10元 8 | 不吃鱼的猫 10元 9 | 一览江晨 10元 10 | 老张 10元 11 | **良 10元 12 | 上帝欺骗了我 10元 13 | 匿名(22043) 10元 14 | 无冥 10元 15 | 木逸萧 10元 16 | Jerry盧 10元 17 | 匿名(18739) 10元 18 | Mu 10元 19 | Terral 10元 20 | 三里湾废品回收陈浩楠 10元 21 | 刘杰 10元 22 | 佚名(39730) 10元 23 | 李华东 10元 24 | 战火 8元 25 | 陈时君同志 6.66元 26 | 匿名(58634) 6.66元 27 | **新 5元 28 | zjm355 5元 29 | 归零 5元 30 | 远 5元 31 | 庞贝 5元 32 | 成 5元 33 | 勇敢的心 5元 34 | 匿名(38556) 5元 35 | 一览众山小 5元 36 | xinyue 5元 37 | 匿名(04549) 5元 38 | 4KM 5元 39 | 宏宇陶瓷\羅馬磁磚 5元 40 | 萧芠桉 5元 41 | YoungWu 5元 42 | ZYQ 5元 43 | Joly 5元 44 | 忍着不死的码农 5元 45 | 永不言弃 5元 46 | 安郝 5元 47 | 平安 5元 48 | 草草不恭先生 5元 49 | 悟道 5元 50 | 涛哥 5元 51 | 匿名(71222) 5元 52 | 三文鱼有几条纹 5元 53 | OrangeOcean 5元 54 | lr 5元 55 | 军辉 5元 56 | 温振权 5元 57 | 周佳乾 5元 58 | allen 5元 59 | 张玉新 5元 60 | 林永彬 5元 61 | 查无此人 5元 62 | Amos 3元 63 | **阳 1元 64 | 陈承屹 1元 65 | 无悔 1元 66 | 伟胜 1元 67 | Camellia 1元 68 | 稳汉 1元 69 | 夏至c 1元 70 | 匿名(75601) 1元 71 | 一两wind 1元 72 | 皆因没文化,卧槽走天下 1元 73 | Tony 1元 74 | 季维栋 1元 75 | Tung 1元 76 | 陈典福 1元 77 | 转角 1元 78 | 劉 1元 79 | 养殖大户老王 1元 80 | Sam.Yang 1元 81 | 小虾🦐 1元 82 | 马犇 1元 83 | Mr.J 1元 84 | soul 1元 85 | 李三 1元 86 | 阿大先生 1元 87 | 封印 1元 88 | BeliveYourSelf 1元 89 | 杨圣明 1元 90 | 留住八 1元 91 | 心向往之 1元 92 | lixiangzhen 1元 93 | 三剪桃花梦故里…… 1元 94 | 匿名(69997) 1元 95 | 钛 1元 96 | 佚名(20442) 1元 97 | 杨少平 1元 98 | 孤枕难眠 1元 99 | 苏世瑜 1元 100 | Yx.0.0 1元 101 | 乐乐 1元 102 | 健旺 1元 103 | Young 1元 104 | 日用百货批发 1元 105 | 风 1元 106 | 匿名(32935) 1元 107 | 劉~~ 1元 108 | ~HB~ 1元 109 | 马潇 1元 110 | 芳华已逝 1元 111 | 匿名(81714) 1元 112 | Xiexie 1元 113 | 江巴斯 1元 114 | 火龙女 1元 115 | 斜杠 1元 116 | 捷安快修 1元 117 | 贵州丰禾 1元 118 | 明志之王 1元 119 | Fan 1元 120 | 匿名(95843) 1元 121 | 陈聪 1元 122 | 江南有晨曦 1元 123 | 米颠 1元 124 | 开摆 1元 125 | 雨过天晴 1元 126 | 海洋🇨🇳ONE 1元 127 | 银河边际 1元 128 | 匿名(93031) 1元 129 | E(...) 1元 130 | 雷雨是本名不是笔名 1元 131 | 保刚 1元 132 | 胡 1元 133 | 豆包 1元 134 | 沄泹渢清 0.88元 135 | 匿名(98470) 0.6元 136 | 躺着赚钱 0.1元 137 | 淅淅沥沥.Eason 0.1元 138 | 天河水 0.1元 139 | 匿名(21459) 0.1元 140 | 渺星 0.01元 141 | 刑天 0.01元 142 | 匿名(59809) 0.01元 143 | 岁岁平岁岁按 0.01元 144 | 强壮书生 0.01元 145 | 卡卡 0.01元 146 | 罗*松 0.01元 147 | XiaoHe_ 0.01元 148 | 149 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter_test/flutter_test.dart'; 9 | 10 | void main() { 11 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 12 | String ipLink = 'http://192.168.1.127:8848'; 13 | final host = Uri.parse(ipLink).host; 14 | print('host::::$host'); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "latest_version": "2.9.0", 3 | "update_log": ["❤️感谢各位老铁的打赏,已购买代理服务,方便国内用户访问Github数据,也从此版本开始码云将不在同步更新,全面回归到Github\n", 4 | "1.针对安卓平台,优化检测版本的更新提示,支持手动更新和自动更新,可前往‘设置->实验设置’查看具体设置", 5 | "2.TV版增加数字选台功能", 6 | "3.TV版支持上下键换台('设置->实验设置->上下键切换频道'手动打开)"] 7 | 8 | } -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.14) 3 | project(easy_tv_live LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "easy_tv_live") 8 | 9 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 10 | # versions of CMake. 11 | cmake_policy(VERSION 3.14...3.25) 12 | 13 | # Define build configuration option. 14 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 15 | if(IS_MULTICONFIG) 16 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 17 | CACHE STRING "" FORCE) 18 | else() 19 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 20 | set(CMAKE_BUILD_TYPE "Debug" CACHE 21 | STRING "Flutter build mode" FORCE) 22 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 23 | "Debug" "Profile" "Release") 24 | endif() 25 | endif() 26 | # Define settings for the Profile build mode. 27 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 28 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 29 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 30 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 31 | 32 | # Use Unicode for all projects. 33 | add_definitions(-DUNICODE -D_UNICODE) 34 | 35 | # Compilation settings that should be applied to most targets. 36 | # 37 | # Be cautious about adding new options here, as plugins use this function by 38 | # default. In most cases, you should add new options to specific targets instead 39 | # of modifying this function. 40 | function(APPLY_STANDARD_SETTINGS TARGET) 41 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 42 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 43 | target_compile_options(${TARGET} PRIVATE /EHsc) 44 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 45 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 46 | endfunction() 47 | 48 | # Flutter library and tool build rules. 49 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 50 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 51 | 52 | # Application build; see runner/CMakeLists.txt. 53 | add_subdirectory("runner") 54 | 55 | 56 | # Generated plugin build rules, which manage building the plugins and adding 57 | # them to the application. 58 | include(flutter/generated_plugins.cmake) 59 | 60 | 61 | # === Installation === 62 | # Support files are copied into place next to the executable, so that it can 63 | # run in place. This is done instead of making a separate bundle (as on Linux) 64 | # so that building and running from within Visual Studio will work. 65 | set(BUILD_BUNDLE_DIR "$") 66 | # Make the "install" step default, as it's required to run. 67 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 68 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 69 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 70 | endif() 71 | 72 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 73 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 74 | 75 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 76 | COMPONENT Runtime) 77 | 78 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 79 | COMPONENT Runtime) 80 | 81 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 82 | COMPONENT Runtime) 83 | 84 | if(PLUGIN_BUNDLED_LIBRARIES) 85 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 86 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 87 | COMPONENT Runtime) 88 | endif() 89 | 90 | # Copy the native assets provided by the build.dart from all packages. 91 | set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") 92 | install(DIRECTORY "${NATIVE_ASSETS_DIR}" 93 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 94 | COMPONENT Runtime) 95 | 96 | # Fully re-copy the assets directory on each build to avoid having stale files 97 | # from a previous install. 98 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 99 | install(CODE " 100 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 101 | " COMPONENT Runtime) 102 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 103 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 104 | 105 | # Install the AOT library on non-Debug builds only. 106 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 107 | CONFIGURATIONS Profile;Release 108 | COMPONENT Runtime) 109 | -------------------------------------------------------------------------------- /windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.14) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 12 | 13 | # Set fallback configurations for older versions of the flutter tool. 14 | if (NOT DEFINED FLUTTER_TARGET_PLATFORM) 15 | set(FLUTTER_TARGET_PLATFORM "windows-x64") 16 | endif() 17 | 18 | # === Flutter Library === 19 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 20 | 21 | # Published to parent scope for install step. 22 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 23 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 24 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 25 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 26 | 27 | list(APPEND FLUTTER_LIBRARY_HEADERS 28 | "flutter_export.h" 29 | "flutter_windows.h" 30 | "flutter_messenger.h" 31 | "flutter_plugin_registrar.h" 32 | "flutter_texture_registrar.h" 33 | ) 34 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 35 | add_library(flutter INTERFACE) 36 | target_include_directories(flutter INTERFACE 37 | "${EPHEMERAL_DIR}" 38 | ) 39 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 40 | add_dependencies(flutter flutter_assemble) 41 | 42 | # === Wrapper === 43 | list(APPEND CPP_WRAPPER_SOURCES_CORE 44 | "core_implementations.cc" 45 | "standard_codec.cc" 46 | ) 47 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 48 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 49 | "plugin_registrar.cc" 50 | ) 51 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 52 | list(APPEND CPP_WRAPPER_SOURCES_APP 53 | "flutter_engine.cc" 54 | "flutter_view_controller.cc" 55 | ) 56 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 57 | 58 | # Wrapper sources needed for a plugin. 59 | add_library(flutter_wrapper_plugin STATIC 60 | ${CPP_WRAPPER_SOURCES_CORE} 61 | ${CPP_WRAPPER_SOURCES_PLUGIN} 62 | ) 63 | apply_standard_settings(flutter_wrapper_plugin) 64 | set_target_properties(flutter_wrapper_plugin PROPERTIES 65 | POSITION_INDEPENDENT_CODE ON) 66 | set_target_properties(flutter_wrapper_plugin PROPERTIES 67 | CXX_VISIBILITY_PRESET hidden) 68 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 69 | target_include_directories(flutter_wrapper_plugin PUBLIC 70 | "${WRAPPER_ROOT}/include" 71 | ) 72 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 73 | 74 | # Wrapper sources needed for the runner. 75 | add_library(flutter_wrapper_app STATIC 76 | ${CPP_WRAPPER_SOURCES_CORE} 77 | ${CPP_WRAPPER_SOURCES_APP} 78 | ) 79 | apply_standard_settings(flutter_wrapper_app) 80 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 81 | target_include_directories(flutter_wrapper_app PUBLIC 82 | "${WRAPPER_ROOT}/include" 83 | ) 84 | add_dependencies(flutter_wrapper_app flutter_assemble) 85 | 86 | # === Flutter tool backend === 87 | # _phony_ is a non-existent file to force this command to run every time, 88 | # since currently there's no way to get a full input/output list from the 89 | # flutter tool. 90 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 91 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 92 | add_custom_command( 93 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 94 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 95 | ${CPP_WRAPPER_SOURCES_APP} 96 | ${PHONY_OUTPUT} 97 | COMMAND ${CMAKE_COMMAND} -E env 98 | ${FLUTTER_TOOL_ENVIRONMENT} 99 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 100 | ${FLUTTER_TARGET_PLATFORM} $ 101 | VERBATIM 102 | ) 103 | add_custom_target(flutter_assemble DEPENDS 104 | "${FLUTTER_LIBRARY}" 105 | ${FLUTTER_LIBRARY_HEADERS} 106 | ${CPP_WRAPPER_SOURCES_CORE} 107 | ${CPP_WRAPPER_SOURCES_PLUGIN} 108 | ${CPP_WRAPPER_SOURCES_APP} 109 | ) 110 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | void RegisterPlugins(flutter::PluginRegistry* registry) { 17 | FlutterVolumeControllerPluginCApiRegisterWithRegistrar( 18 | registry->GetRegistrarForPlugin("FlutterVolumeControllerPluginCApi")); 19 | FvpPluginCApiRegisterWithRegistrar( 20 | registry->GetRegistrarForPlugin("FvpPluginCApi")); 21 | ScreenBrightnessWindowsPluginRegisterWithRegistrar( 22 | registry->GetRegistrarForPlugin("ScreenBrightnessWindowsPlugin")); 23 | ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( 24 | registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); 25 | UrlLauncherWindowsRegisterWithRegistrar( 26 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 27 | WindowManagerPluginRegisterWithRegistrar( 28 | registry->GetRegistrarForPlugin("WindowManagerPlugin")); 29 | } 30 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | flutter_volume_controller 7 | fvp 8 | screen_brightness_windows 9 | screen_retriever_windows 10 | url_launcher_windows 11 | window_manager 12 | ) 13 | 14 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 15 | ) 16 | 17 | set(PLUGIN_BUNDLED_LIBRARIES) 18 | 19 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 20 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 21 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 23 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 24 | endforeach(plugin) 25 | 26 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 27 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 28 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 29 | endforeach(ffi_plugin) 30 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 38 | 39 | # Run the Flutter tool portions of the build. This must not be removed. 40 | add_dependencies(${BINARY_NAME} flutter_assemble) 41 | -------------------------------------------------------------------------------- /windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) 64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0,0 67 | #endif 68 | 69 | #if defined(FLUTTER_VERSION) 70 | #define VERSION_AS_STRING FLUTTER_VERSION 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "com.example" "\0" 93 | VALUE "FileDescription", "easy_tv_live" "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "easy_tv_live" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2024 com.example. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "easy_tv_live.exe" "\0" 98 | VALUE "ProductName", "easy_tv_live" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | 30 | flutter_controller_->engine()->SetNextFrameCallback([&]() { 31 | this->Show(); 32 | }); 33 | 34 | // Flutter can complete the first frame before the "show window" callback is 35 | // registered. The following call ensures a frame is pending to ensure the 36 | // window is shown. It is a no-op if the first frame hasn't completed yet. 37 | flutter_controller_->ForceRedraw(); 38 | 39 | return true; 40 | } 41 | 42 | void FlutterWindow::OnDestroy() { 43 | if (flutter_controller_) { 44 | flutter_controller_ = nullptr; 45 | } 46 | 47 | Win32Window::OnDestroy(); 48 | } 49 | 50 | LRESULT 51 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 52 | WPARAM const wparam, 53 | LPARAM const lparam) noexcept { 54 | // Give Flutter, including plugins, an opportunity to handle window messages. 55 | if (flutter_controller_) { 56 | std::optional result = 57 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 58 | lparam); 59 | if (result) { 60 | return *result; 61 | } 62 | } 63 | 64 | switch (message) { 65 | case WM_FONTCHANGE: 66 | flutter_controller_->engine()->ReloadSystemFonts(); 67 | break; 68 | } 69 | 70 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 71 | } 72 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.Create(L"easy_tv_live", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | unsigned int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr) 51 | -1; // remove the trailing null character 52 | int input_length = (int)wcslen(utf16_string); 53 | std::string utf8_string; 54 | if (target_length == 0 || target_length > utf8_string.max_size()) { 55 | return utf8_string; 56 | } 57 | utf8_string.resize(target_length); 58 | int converted_length = ::WideCharToMultiByte( 59 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 60 | input_length, utf8_string.data(), target_length, nullptr, nullptr); 61 | if (converted_length == 0) { 62 | return std::string(); 63 | } 64 | return utf8_string; 65 | } 66 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates a win32 window with |title| that is positioned and sized using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size this function will scale the inputted width and height as 35 | // as appropriate for the default monitor. The window is invisible until 36 | // |Show| is called. Returns true if the window was created successfully. 37 | bool Create(const std::wstring& title, const Point& origin, const Size& size); 38 | 39 | // Show the current window. Returns true if the window was successfully shown. 40 | bool Show(); 41 | 42 | // Release OS resources associated with window. 43 | void Destroy(); 44 | 45 | // Inserts |content| into the window tree. 46 | void SetChildContent(HWND content); 47 | 48 | // Returns the backing Window handle to enable clients to set icon and other 49 | // window properties. Returns nullptr if the window has been destroyed. 50 | HWND GetHandle(); 51 | 52 | // If true, closing this window will quit the application. 53 | void SetQuitOnClose(bool quit_on_close); 54 | 55 | // Return a RECT representing the bounds of the current client area. 56 | RECT GetClientArea(); 57 | 58 | protected: 59 | // Processes and route salient window messages for mouse handling, 60 | // size change and DPI. Delegates handling of these to member overloads that 61 | // inheriting classes can handle. 62 | virtual LRESULT MessageHandler(HWND window, 63 | UINT const message, 64 | WPARAM const wparam, 65 | LPARAM const lparam) noexcept; 66 | 67 | // Called when CreateAndShow is called, allowing subclass window-related 68 | // setup. Subclasses should return false if setup fails. 69 | virtual bool OnCreate(); 70 | 71 | // Called when Destroy is called. 72 | virtual void OnDestroy(); 73 | 74 | private: 75 | friend class WindowClassRegistrar; 76 | 77 | // OS callback called by message pump. Handles the WM_NCCREATE message which 78 | // is passed when the non-client area is being created and enables automatic 79 | // non-client DPI scaling so that the non-client area automatically 80 | // responds to changes in DPI. All other messages are handled by 81 | // MessageHandler. 82 | static LRESULT CALLBACK WndProc(HWND const window, 83 | UINT const message, 84 | WPARAM const wparam, 85 | LPARAM const lparam) noexcept; 86 | 87 | // Retrieves a class instance pointer for |window| 88 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 89 | 90 | // Update the window frame's theme to match the system theme. 91 | static void UpdateTheme(HWND const window); 92 | 93 | bool quit_on_close_ = false; 94 | 95 | // window handle for top level window. 96 | HWND window_handle_ = nullptr; 97 | 98 | // window handle for hosted content. 99 | HWND child_content_ = nullptr; 100 | }; 101 | 102 | #endif // RUNNER_WIN32_WINDOW_H_ 103 | -------------------------------------------------------------------------------- /windows/system32/msvcp140.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/windows/system32/msvcp140.dll -------------------------------------------------------------------------------- /windows/system32/vcruntime140.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/windows/system32/vcruntime140.dll -------------------------------------------------------------------------------- /windows/system32/vcruntime140_1.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiyakuaile/easy_tv_live/968be6198437f8fb0c9dab9e6f0faec31a63c114/windows/system32/vcruntime140_1.dll --------------------------------------------------------------------------------