├── .github
├── ISSUE_TEMPLATE
│ ├── bug.md
│ └── request.md
└── workflows
│ └── main.yml
├── .gitignore
├── .metadata
├── License
├── README.md
├── analysis_options.yaml
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── mystyle
│ │ │ │ └── purelive
│ │ │ │ └── MainActivity.kt
│ │ └── res
│ │ │ ├── drawable-hdpi
│ │ │ └── ic_launcher_foreground.png
│ │ │ ├── drawable-mdpi
│ │ │ └── ic_launcher_foreground.png
│ │ │ ├── drawable-v21
│ │ │ └── launch_background.xml
│ │ │ ├── drawable-xhdpi
│ │ │ └── ic_launcher_foreground.png
│ │ │ ├── drawable-xxhdpi
│ │ │ └── ic_launcher_foreground.png
│ │ │ ├── drawable-xxxhdpi
│ │ │ └── ic_launcher_foreground.png
│ │ │ ├── drawable
│ │ │ └── launch_background.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ └── ic_launcher.xml
│ │ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── values-en
│ │ │ └── strings.xml
│ │ │ ├── values-night
│ │ │ └── styles.xml
│ │ │ └── values
│ │ │ ├── colors.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
└── settings.gradle
├── assets
├── PingFangSC.ttf
├── crypto-js.js
├── icons
│ ├── CustomIcons.ttf
│ ├── icon.png
│ └── icon_foreground.png
└── images
│ ├── alipay.jpg
│ └── wechat.png
├── ios
├── .gitignore
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── 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-App-1024x1024@1x.png
│ │ ├── Icon-App-20x20@1x.png
│ │ ├── Icon-App-20x20@2x.png
│ │ ├── Icon-App-20x20@3x.png
│ │ ├── Icon-App-29x29@1x.png
│ │ ├── Icon-App-29x29@2x.png
│ │ ├── Icon-App-29x29@3x.png
│ │ ├── Icon-App-40x40@1x.png
│ │ ├── Icon-App-40x40@2x.png
│ │ ├── Icon-App-40x40@3x.png
│ │ ├── Icon-App-50x50@1x.png
│ │ ├── Icon-App-50x50@2x.png
│ │ ├── Icon-App-57x57@1x.png
│ │ ├── Icon-App-57x57@2x.png
│ │ ├── Icon-App-60x60@2x.png
│ │ ├── Icon-App-60x60@3x.png
│ │ ├── Icon-App-72x72@1x.png
│ │ ├── Icon-App-72x72@2x.png
│ │ ├── Icon-App-76x76@1x.png
│ │ ├── Icon-App-76x76@2x.png
│ │ └── Icon-App-83.5x83.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
├── lib
├── common
│ ├── base
│ │ └── base_controller.dart
│ ├── index.dart
│ ├── l10n
│ │ ├── generated
│ │ │ ├── intl
│ │ │ │ ├── messages_all.dart
│ │ │ │ ├── messages_en.dart
│ │ │ │ └── messages_zh_CN.dart
│ │ │ └── l10n.dart
│ │ ├── intl_en.arb
│ │ └── intl_zh_CN.arb
│ ├── models
│ │ ├── index.dart
│ │ ├── live_area.dart
│ │ ├── live_message.dart
│ │ └── live_room.dart
│ ├── services
│ │ ├── index.dart
│ │ └── settings_service.dart
│ ├── style
│ │ ├── index.dart
│ │ └── theme.dart
│ ├── utils
│ │ ├── cache_manager.dart
│ │ ├── index.dart
│ │ ├── js_engine.dart
│ │ ├── pref_util.dart
│ │ ├── snackbar_util.dart
│ │ ├── text_util.dart
│ │ └── version_util.dart
│ └── widgets
│ │ ├── custom_icons.dart
│ │ ├── empty_view.dart
│ │ ├── index.dart
│ │ ├── menu_button.dart
│ │ ├── room_card.dart
│ │ ├── search_button.dart
│ │ └── section_listtile.dart
├── core
│ ├── common
│ │ ├── binary_writer.dart
│ │ └── websocket_utils.dart
│ ├── danmaku
│ │ ├── bilibili_danmaku.dart
│ │ ├── douyu_danmaku.dart
│ │ └── huya_danmaku.dart
│ ├── index.dart
│ ├── interface
│ │ ├── live_danmaku.dart
│ │ └── live_site.dart
│ ├── site
│ │ ├── bilibili_site.dart
│ │ ├── douyu_site.dart
│ │ └── huya_site.dart
│ └── sites.dart
├── main.dart
├── modules
│ ├── about
│ │ ├── about_page.dart
│ │ ├── donate_page.dart
│ │ └── widgets
│ │ │ └── version_dialog.dart
│ ├── area_rooms
│ │ ├── area_rooms_controller.dart
│ │ └── area_rooms_page.dart
│ ├── areas
│ │ ├── areas_controller.dart
│ │ ├── areas_grid_view.dart
│ │ ├── areas_page.dart
│ │ ├── favorite_areas_page.dart
│ │ └── widgets
│ │ │ └── area_card.dart
│ ├── backup
│ │ └── backup_page.dart
│ ├── contact
│ │ └── contact_page.dart
│ ├── favorite
│ │ ├── favorite_controller.dart
│ │ └── favorite_page.dart
│ ├── history
│ │ └── history_page.dart
│ ├── home
│ │ ├── home_page.dart
│ │ ├── mobile_view.dart
│ │ └── tablet_view.dart
│ ├── live_play
│ │ ├── live_play_controller.dart
│ │ ├── live_play_page.dart
│ │ └── widgets
│ │ │ ├── danmaku_list_view.dart
│ │ │ ├── index.dart
│ │ │ ├── live_dlna_dialog.dart
│ │ │ └── video_player
│ │ │ ├── danmaku_text.dart
│ │ │ ├── video_controller.dart
│ │ │ ├── video_controller_panel.dart
│ │ │ └── video_player.dart
│ ├── popular
│ │ ├── popular_controller.dart
│ │ ├── popular_grid_controller.dart
│ │ ├── popular_grid_view.dart
│ │ └── popular_page.dart
│ ├── search
│ │ ├── search_binding.dart
│ │ ├── search_controller.dart
│ │ ├── search_list_controller.dart
│ │ ├── search_list_view.dart
│ │ └── search_page.dart
│ └── settings
│ │ ├── settings_binding.dart
│ │ └── settings_page.dart
└── routes
│ └── app_pages.dart
├── pubspec.yaml
├── screenshots
├── areas_page.jpg
├── desktop_favorite.png
├── desktop_live_play.png
├── desktop_popular.png
├── favorite_page.jpg
├── live_play_page.jpg
├── popular_page.jpg
└── search_page.jpg
├── test
└── widget_test.dart
└── 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
/.github/ISSUE_TEMPLATE/bug.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 错误报告
3 | about: 创建报告以帮助我们改进
4 | title: "[bug]"
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 | **在提出问题时,请确保您已经阅读了以下内容**
10 | - [README](README.md)
11 |
12 | - [ISSUE](https://github.com/Jackiu1997/pure_live/issues?q=)
13 |
14 | - [如何有效地报告 Bug](https://www.chiark.greenend.org.uk/~sgtatham/bugs-cn.html)
15 |
16 | - [提问的智慧](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)
17 |
18 | 在阅读完以上内容后,如果您仍然有问题,请删除自第一行加粗内容起至下一行加粗内容的所有文字,在最后一行的[]中填写x,然后描述问题。
19 |
20 | **请勿删除自此行之后加粗的模板!**
21 |
22 | **描述错误**
23 | 简明扼要地描述该错误是什么。
24 |
25 | **重现**
26 | 重现该行为的步骤。
27 | 1. 转到 '....'
28 | 2. 点击 '....'
29 | 3. 向下滚动到 '....'
30 | 4. 看到错误
31 |
32 | **预期的行为**
33 | 简明扼要地描述你期望发生的情况。
34 |
35 | **屏幕截图**
36 | 如果适用,添加屏幕截图以帮助解释你的问题。
37 |
38 | **设备信息(请填写以下信息):**
39 | - 操作系统: [例如:Windows]
40 | - 版本: [例如:Windows 11 21H2]
41 |
42 | **额外的背景**
43 | 在这里添加关于问题的任何其他背景。
44 |
45 | [] 我已经阅读了相关内容并且描述准确
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 功能请求
3 | about: 为这个项目提出一个想法
4 | title: "[request]"
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 | **在提出问题时,请确保您已经阅读了以下内容**
10 | - [README](README.md)
11 |
12 | - [ISSUE](https://github.com/Jackiu1997/pure_live/issues?q=)
13 |
14 | - [如何有效地报告 Bug](https://www.chiark.greenend.org.uk/~sgtatham/bugs-cn.html)
15 |
16 | - [提问的智慧](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)
17 |
18 | 在阅读完以上内容后,如果您仍然有问题,请删除自第一行加粗内容起至下一行加粗内容的所有文字,在最后一行的[]中填写x,然后描述问题。
19 |
20 | **请勿删除自此行之后加粗的模板!**
21 |
22 | **你的功能请求是否与一个问题有关?请描述。**
23 | 清楚而简洁地描述问题是什么。例如:当[......]时,我感到[......]。
24 |
25 | **描述你想要的解决方案**
26 | 对你希望发生的事情进行清晰、简明的描述。
27 |
28 | **描述你所考虑的替代方案**
29 | 对你考虑过的任何替代性解决方案或功能进行清晰、简明的描述。
30 |
31 | **补充**
32 | 在此添加关于该功能请求的任何其他背景或屏幕截图。
33 |
34 | [] 我已经阅读了相关内容并且描述准确
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to help you get started with Actions
2 |
3 | name: Build
4 |
5 | # Controls when the workflow will run
6 | on:
7 | push:
8 | tags:
9 | - "v*"
10 |
11 | # Allows you to run this workflow manually from the Actions tab
12 | workflow_dispatch:
13 |
14 |
15 | env:
16 | # APP name
17 | APP_NAME: IceLiveViewer
18 |
19 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
20 | jobs:
21 | build-windows:
22 | # The type of runner that the job will run on
23 | runs-on: windows-latest
24 |
25 | # Steps represent a sequence of tasks that will be executed as part of the job
26 | steps:
27 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
28 | - uses: actions/checkout@v2
29 | - uses: subosito/flutter-action@v2
30 | with:
31 | channel: 'stable'
32 |
33 | - name: Build
34 | run: |
35 | flutter config --enable-windows-desktop
36 | flutter pub get
37 | flutter build windows
38 |
39 | - name: Archive Release
40 | uses: thedoctor0/zip-release@master
41 | with:
42 | type: 'zip'
43 | filename: IceLiveViewer-${{github.ref_name}}-windows.zip
44 | directory: build/windows/runner/Release
45 |
46 | - name: Release
47 | uses: softprops/action-gh-release@v1
48 | with:
49 | tag_name: ${{github.ref_name}}
50 | draft: true
51 | prerelease: true
52 | token: ${{ secrets.GITHUB_TOKEN }}
53 | files: |
54 | build/windows/runner/Release/IceLiveViewer-${{github.ref_name}}-windows.zip
55 |
56 | - name: Upload Release Asset
57 | uses: actions/upload-artifact@v3
58 | with:
59 | name: artifact-windows
60 | path: build/windows/runner/Release/IceLiveViewer-${{github.ref_name}}-windows.zip
61 |
62 |
63 | build-android:
64 | # The type of runner that the job will run on
65 | runs-on: ubuntu-latest
66 |
67 | # Steps represent a sequence of tasks that will be executed as part of the job
68 | steps:
69 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
70 | - uses: actions/checkout@v2
71 |
72 | - name: Setup Java to compile Android project
73 | uses: actions/setup-java@v1
74 | with:
75 | java-version: '12.x'
76 |
77 | - name: Setup Flutter
78 | uses: subosito/flutter-action@v2
79 | with:
80 | channel: 'stable'
81 |
82 | - name: Create the Keystore file
83 | env:
84 | KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
85 | KEY_PROPERTIES: ${{ secrets.KEY_PROPERTIES }}
86 | run: |
87 | # import keystore from secrets
88 | echo $KEYSTORE_BASE64 | base64 -di > android/app/key.jks
89 | echo $KEY_PROPERTIES | base64 -di > android/key.properties
90 |
91 | - name: Build
92 | run: |
93 | flutter pub get
94 | flutter build apk
95 |
96 | - name: Rename APK
97 | run: |
98 | mv build/app/outputs/flutter-apk/app-release.apk build/app/outputs/flutter-apk/IceLiveViewer-${{github.ref_name}}-android.apk
99 |
100 |
101 | - name: Release
102 | uses: softprops/action-gh-release@v1
103 | with:
104 | tag_name: ${{github.ref_name}}
105 | draft: true
106 | prerelease: true
107 | token: ${{ secrets.GITHUB_TOKEN }}
108 | files: |
109 | build/app/outputs/flutter-apk/IceLiveViewer-${{github.ref_name}}-android.apk
110 |
111 | - name: Upload Release Asset
112 | uses: actions/upload-artifact@v3
113 | with:
114 | name: artifact-android
115 | path: build/app/outputs/flutter-apk/IceLiveViewer-${{github.ref_name}}-android.apk
116 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.lock
4 | *.log
5 | *.pyc
6 | *.swp
7 | .DS_Store
8 | .atom/
9 | .buildlog/
10 | .history
11 | .svn/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # Visual Studio Code related
20 | .classpath
21 | .project
22 | .settings/
23 | .vscode/
24 |
25 | # VS related
26 | .vs/
27 |
28 | # Flutter repo-specific
29 | /bin/cache/
30 | /bin/internal/bootstrap.bat
31 | /bin/internal/bootstrap.sh
32 | /bin/mingit/
33 | /dev/benchmarks/mega_gallery/
34 | /dev/bots/.recipe_deps
35 | /dev/bots/android_tools/
36 | /dev/devicelab/ABresults*.json
37 | /dev/docs/doc/
38 | /dev/docs/flutter.docs.zip
39 | /dev/docs/lib/
40 | /dev/docs/pubspec.yaml
41 | /dev/integration_tests/**/xcuserdata
42 | /dev/integration_tests/**/Pods
43 | /packages/flutter/coverage/
44 | version
45 | analysis_benchmark.json
46 |
47 | # packages file containing multi-root paths
48 | .packages.generated
49 |
50 | # Flutter/Dart/Pub related
51 | **/doc/api/
52 | .dart_tool/
53 | .flutter-plugins
54 | .flutter-plugins-dependencies
55 | **/generated_plugin_registrant.dart
56 | .packages
57 | .pub-cache/
58 | .pub/
59 | build/
60 | flutter_*.png
61 | linked_*.ds
62 | unlinked.ds
63 | unlinked_spec.ds
64 |
65 | # Android related
66 | **/android/**/gradle-wrapper.jar
67 | .gradle/
68 | **/android/captures/
69 | **/android/gradlew
70 | **/android/gradlew.bat
71 | **/android/local.properties
72 | **/android/**/GeneratedPluginRegistrant.java
73 | **/android/key.properties
74 | *.jks
75 |
76 | # iOS/XCode related
77 | **/ios/**/*.mode1v3
78 | **/ios/**/*.mode2v3
79 | **/ios/**/*.moved-aside
80 | **/ios/**/*.pbxuser
81 | **/ios/**/*.perspectivev3
82 | **/ios/**/*sync/
83 | **/ios/**/.sconsign.dblite
84 | **/ios/**/.tags*
85 | **/ios/**/.vagrant/
86 | **/ios/**/DerivedData/
87 | **/ios/**/Icon?
88 | **/ios/**/Pods/
89 | **/ios/**/.symlinks/
90 | **/ios/**/profile
91 | **/ios/**/xcuserdata
92 | **/ios/.generated/
93 | **/ios/Flutter/.last_build_id
94 | **/ios/Flutter/App.framework
95 | **/ios/Flutter/Flutter.framework
96 | **/ios/Flutter/Flutter.podspec
97 | **/ios/Flutter/Generated.xcconfig
98 | **/ios/Flutter/ephemeral
99 | **/ios/Flutter/app.flx
100 | **/ios/Flutter/app.zip
101 | **/ios/Flutter/flutter_assets/
102 | **/ios/Flutter/flutter_export_environment.sh
103 | **/ios/ServiceDefinitions.json
104 | **/ios/Runner/GeneratedPluginRegistrant.*
105 |
106 | # macOS
107 | **/Flutter/ephemeral/
108 | **/Pods/
109 | **/macos/Flutter/GeneratedPluginRegistrant.swift
110 | **/macos/Flutter/ephemeral
111 | **/xcuserdata/
112 |
113 | # Coverage
114 | coverage/
115 |
116 | # Symbols
117 | app.*.symbols
118 |
119 | # Exceptions to above rules.
120 | !**/ios/**/default.mode1v3
121 | !**/ios/**/default.mode2v3
122 | !**/ios/**/default.pbxuser
123 | !**/ios/**/default.perspectivev3
124 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
125 | !/dev/ci/**/Gemfile.lock
126 |
127 | # npm packages
128 | node_modules/
--------------------------------------------------------------------------------
/.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.
5 |
6 | version:
7 | revision: 135454af32477f815a7525073027a3ff9eff1bfd
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: 135454af32477f815a7525073027a3ff9eff1bfd
17 | base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
18 | - platform: android
19 | create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
20 | base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
21 | - platform: ios
22 | create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
23 | base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
24 | - platform: windows
25 | create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
26 | base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
27 |
28 | # User provided section
29 |
30 | # List of Local paths (relative to this file) that should be
31 | # ignored by the migrate tool.
32 | #
33 | # Files that are not part of the templates will be ignored by default.
34 | unmanaged_files:
35 | - 'lib/main.dart'
36 | - 'ios/Runner.xcodeproj/project.pbxproj'
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Pure Live
2 |
3 |
4 |
5 | 
6 | 
7 | [](https://github.com/Jackiu1997/pure_live/releases)
8 | 
9 | 
10 | 
11 |
12 | Pure Live is a live stream transcoding application based on Flutter for android and windows, which can make you watch lives with ease. All data fetched by local machine, no cloud save, all live data and video belongs to original platform.
13 |
14 | Pure Live是一款款平台基于Flutter的直播转码软件,轻松看直播。所有数据均由本地机器获取,不存储在云端,直播数据、视频版权归原平台所有。
15 |
16 | 目前支持设备:
17 | - Android
18 | - Windows
19 |
20 | ## Windows 安装
21 | 使用 msix 安装请删除 .zip 后缀名。
22 | 
23 |
24 |
25 | ## 开发进度看板[link](https://jackiu-notes.notion.site/50bc0d3d377445eea029c6e3d4195671?v=663125e639b047cea5e69d8264926b8b)
26 |
27 | ## Screenshots
28 |
29 | ### Mobile UI
30 |
51 |
52 | ### Tablet/Desktop UI
53 |
54 |
55 |
56 |
57 |
58 | |
59 |
60 |
61 | |
62 |
63 |
64 | |
65 |
66 |
67 |
68 |
69 | ## Platforms
70 |
71 | - [x] [哔哩哔哩](https://app.bilibili.com/)
72 |
73 | - [x] [虎牙APP](https://www.huya.com/download/)
74 |
75 | - [x] [斗鱼APP](https://www.douyu.com/client)
76 |
77 | ## Donate
78 |
79 | 如果你觉得该项目对您有所帮助,可以打赏一杯咖啡给我,支持我继续开发维护PureLive。
80 | 感谢您的支持~
81 |
82 |
83 |
84 |
85 |
86 |
87 | |
88 |
89 |
90 | |
91 |
92 |
93 |
94 |
95 | ## Problems
96 |
97 | ### 问题反馈
98 |
99 | - 如果需要反馈问题,请在Github发布[issue](https://github.com/Jackiu1997/pure_live/issues/new/choose)
100 |
101 | ### 部分链接无法播放
102 |
103 | - 对于部分IP,哔哩哔哩的`.flv`格式的直播流无法播放,尝试使用`.m3u8`格式的直播流
104 |
105 | ### 搜索哔哩哔哩直播间不工作
106 |
107 | - 哔哩哔哩官方搜索接口需要使用cookie,请在设置中自行设置自己的cookie
108 |
109 | ### 不定时更新(随缘开发)
110 | 如果你想要更好的用户体验,更人性化的交互设计,更稳定的使用,可以使用[哔哩哔哩APP](https://app.bilibili.com/),[斗鱼APP](https://www.douyu.com/client),[虎牙APP](https://www.huya.com/download/)
111 |
112 | ## Statement
113 | This project is only for learning and communication. Please do not use it for commercial purposes. The copyright of related resources is owned by the original company.
114 |
115 | 这个项目仅作为个人兴趣业余开发,不用于商业用途。相关资源的版权归原公司所有。
116 |
117 | No user privacy is ever collected, the app directly requests the official interface except for detection updates, and the data generated by all operations is kept locally by the user.
118 |
119 | 本项目是一个纯本地直播转码应用,不会收集任何用户隐私,应用程序直接请求直播官方接口,所有操作生成的数据由用户本地保留。
120 |
121 | ## Thanks
122 | - [ice_live_viewer](https://github.com/iiijam/ice_live_viewer)
123 | - [JustLive-Api](https://github.com/guyijie1211/JustLive-Api)
124 | - [real-url](https://github.com/wbt5/real-url)
125 | - [dart_tars_protocol](https://github.com/xiaoyaocz/dart_tars_protocol)
126 | - [bilibili-API-collect](https://github.com/SocialSisterYi/bilibili-API-collect)
127 | - [alltv_flutter](https://github.com/Ha2ryZhang/alltv_flutter)
128 |
--------------------------------------------------------------------------------
/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 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply plugin: 'kotlin-android'
26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27 |
28 | def keystorePropertiesFile = rootProject.file("key.properties")
29 | def keystoreProperties = new Properties()
30 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
31 |
32 | android {
33 | compileSdkVersion 33 //flutter.compileSdkVersion
34 | ndkVersion "25.1.8937393" //flutter.ndkVersion
35 |
36 | compileOptions {
37 | sourceCompatibility JavaVersion.VERSION_1_8
38 | targetCompatibility JavaVersion.VERSION_1_8
39 | }
40 |
41 | kotlinOptions {
42 | jvmTarget = '1.8'
43 | }
44 |
45 | sourceSets {
46 | main.java.srcDirs += 'src/main/kotlin'
47 | }
48 |
49 | defaultConfig {
50 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
51 | applicationId "com.mystyle.purelive"
52 | // You can update the following values to match your application needs.
53 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
54 | minSdkVersion 21 //flutter.minSdkVersion
55 | targetSdkVersion flutter.targetSdkVersion
56 | versionCode flutterVersionCode.toInteger()
57 | versionName flutterVersionName
58 | // Enabling-multidex-support
59 | multiDexEnabled true
60 | }
61 |
62 | signingConfigs {
63 | release {
64 | keyAlias keystoreProperties['keyAlias']
65 | keyPassword keystoreProperties['keyPassword']
66 | storeFile file(keystoreProperties['storeFile'])
67 | storePassword keystoreProperties['storePassword']
68 | }
69 | }
70 | buildTypes {
71 | release {
72 | // TODO: Add your own signing config for the release build.
73 | // Signing with the debug keys for now, so `flutter run --release` works.
74 | signingConfig signingConfigs.release
75 | minifyEnabled true
76 | proguardFiles getDefaultProguardFile(
77 | 'proguard-android-optimize.txt'),
78 | 'proguard-rules.pro'
79 | }
80 | debug {
81 | signingConfig signingConfigs.release
82 | }
83 | }
84 | }
85 |
86 | flutter {
87 | source '../..'
88 | }
89 |
90 | dependencies {
91 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
92 | }
93 |
--------------------------------------------------------------------------------
/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | #Flutter Wrapper
2 | -keep class io.flutter.app.** { *; }
3 | -keep class io.flutter.plugin.** { *; }
4 | -keep class io.flutter.util.** { *; }
5 | -keep class io.flutter.view.** { *; }
6 | -keep class io.flutter.** { *; }
7 | -keep class io.flutter.plugins.** { *; }
8 | -keep class de.prosiebensat1digital.** { *; }
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
19 |
29 |
33 |
37 |
38 |
39 |
40 |
41 |
42 |
44 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/mystyle/purelive/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.mystyle.purelive
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values-en/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | PureLive
4 |
5 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
19 |
20 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 纯粹直播
4 |
5 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
19 |
20 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.6.10'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:7.1.2'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | mavenCentral()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
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-7.4-all.zip
6 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
4 | def properties = new Properties()
5 |
6 | assert localPropertiesFile.exists()
7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
8 |
9 | def flutterSdkPath = properties.getProperty("flutter.sdk")
10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
12 |
--------------------------------------------------------------------------------
/assets/PingFangSC.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/assets/PingFangSC.ttf
--------------------------------------------------------------------------------
/assets/icons/CustomIcons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/assets/icons/CustomIcons.ttf
--------------------------------------------------------------------------------
/assets/icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/assets/icons/icon.png
--------------------------------------------------------------------------------
/assets/icons/icon_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/assets/icons/icon_foreground.png
--------------------------------------------------------------------------------
/assets/images/alipay.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/assets/images/alipay.jpg
--------------------------------------------------------------------------------
/assets/images/wechat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/assets/images/wechat.png
--------------------------------------------------------------------------------
/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 | 9.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/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 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/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 UIKit
2 | import Flutter
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" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.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/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jackiu1997/pure_live/e226afe20c7a174311d9ee6aa2ea5c66fa212810/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 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | PureLive
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | pure_live
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(FLUTTER_BUILD_NUMBER)
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIMainStoryboardFile
30 | Main
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 | UIViewControllerBasedStatusBarAppearance
45 |
46 | CADisableMinimumFrameDurationOnPhone
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/lib/common/base/base_controller.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:developer';
3 |
4 | import 'package:flutter/widgets.dart';
5 |
6 | import 'package:get/get.dart';
7 | import 'package:pull_to_refresh/pull_to_refresh.dart';
8 |
9 | class BaseController extends GetxController {
10 | /// 加载中,更新页面
11 | var pageLoadding = false.obs;
12 |
13 | /// 加载中,不会更新页面
14 | var loadding = false;
15 |
16 | /// 空白页面
17 | var pageEmpty = false.obs;
18 |
19 | /// 页面错误
20 | var pageError = false.obs;
21 |
22 | /// 未登录
23 | var notLogin = false.obs;
24 |
25 | /// 错误信息
26 | var errorMsg = "".obs;
27 |
28 | /// 显示错误
29 | /// * [msg] 错误信息
30 | /// * [showPageError] 显示页面错误
31 | /// * 只在第一页加载错误时showPageError=true,后续页加载错误时使用Toast弹出通知
32 | void handleError(Object exception, {bool showPageError = false}) {
33 | log(exception.toString(), stackTrace: StackTrace.current);
34 | var msg = exceptionToString(exception);
35 |
36 | if (showPageError) {
37 | pageError.value = true;
38 | errorMsg.value = msg;
39 | } else {
40 | Get.rawSnackbar(message: exceptionToString(msg));
41 | }
42 | }
43 |
44 | String exceptionToString(Object exception) {
45 | return exception.toString().replaceAll("Exception:", "");
46 | }
47 |
48 | void onLogin() {}
49 | void onLogout() {}
50 | }
51 |
52 | class BaseListController extends BaseController {
53 | final ScrollController scrollController = ScrollController();
54 | final RefreshController refreshController = RefreshController();
55 | int currentPage = 1;
56 | int count = 0;
57 | int maxPage = 0;
58 | int pageSize = 24;
59 | var canLoadMore = false.obs;
60 | var list = [].obs;
61 |
62 | @override
63 | void onInit() {
64 | super.onInit();
65 | onRefresh();
66 | }
67 |
68 | Future onRefresh() async {
69 | currentPage = 1;
70 | list.value = [];
71 |
72 | try {
73 | pageError.value = false;
74 | pageEmpty.value = false;
75 | notLogin.value = false;
76 | pageLoadding.value = currentPage == 1;
77 |
78 | var result = await getData(currentPage, pageSize);
79 | // 是否可以加载更多
80 | if (result.isNotEmpty) {
81 | currentPage++;
82 | canLoadMore.value = true;
83 | pageEmpty.value = false;
84 | refreshController.refreshCompleted();
85 | } else {
86 | pageEmpty.value = true;
87 | refreshController.refreshFailed();
88 | }
89 | // 赋值数据
90 | list.value = result;
91 | } catch (e) {
92 | handleError(e, showPageError: currentPage == 1);
93 | refreshController.refreshFailed();
94 | } finally {
95 | loadding = false;
96 | pageLoadding.value = false;
97 | }
98 | }
99 |
100 | Future onLoading() async {
101 | try {
102 | if (loadding) return;
103 | loadding = true;
104 | pageError.value = false;
105 | pageEmpty.value = false;
106 | notLogin.value = false;
107 | pageLoadding.value = currentPage == 1;
108 |
109 | var result = await getData(currentPage, pageSize);
110 | // 是否可以加载更多
111 | if (result.isNotEmpty) {
112 | currentPage++;
113 | canLoadMore.value = true;
114 | pageEmpty.value = false;
115 | refreshController.loadComplete();
116 | } else {
117 | canLoadMore.value = false;
118 | pageEmpty.value = currentPage == 1;
119 | refreshController.loadNoData();
120 | }
121 | // 赋值数据
122 | for (var room in result) {
123 | list.addIf(!list.contains(room), room);
124 | }
125 | } catch (e) {
126 | handleError(e, showPageError: currentPage == 1);
127 | refreshController.loadFailed();
128 | } finally {
129 | loadding = false;
130 | pageLoadding.value = false;
131 | }
132 | }
133 |
134 | Future> getData(int page, int pageSize) async {
135 | return [];
136 | }
137 |
138 | void scrollToTopOrRefresh() {
139 | if (scrollController.offset > 0) {
140 | scrollController.animateTo(
141 | 0,
142 | duration: const Duration(milliseconds: 200),
143 | curve: Curves.linear,
144 | );
145 | } else {
146 | refreshController.requestRefresh();
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/lib/common/index.dart:
--------------------------------------------------------------------------------
1 | library common;
2 |
3 | export '../core/index.dart';
4 | export 'l10n/generated/l10n.dart';
5 | export 'models/index.dart';
6 | export 'services/index.dart';
7 | export 'style/index.dart';
8 | export 'utils/index.dart';
9 | export 'widgets/index.dart';
10 |
11 | export 'package:flutter/material.dart';
12 |
--------------------------------------------------------------------------------
/lib/common/l10n/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.dart' as messages_en;
20 | import 'messages_zh_CN.dart' as messages_zh_cn;
21 |
22 | typedef Future LibraryLoader();
23 | Map _deferredLibraries = {
24 | 'en': () => new SynchronousFuture(null),
25 | 'zh_CN': () => new SynchronousFuture(null),
26 | };
27 |
28 | MessageLookupByLibrary? _findExact(String localeName) {
29 | switch (localeName) {
30 | case 'en':
31 | return messages_en.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/common/l10n/intl_zh_CN.arb:
--------------------------------------------------------------------------------
1 | {
2 | "app_name": "纯粹直播",
3 | "app_legalese": "本项目是一个纯本地直播转码应用,不会收集任何用户隐私,应用程序直接请求直播官方接口,所有操作生成的数据由用户本地保留。",
4 |
5 | "cancel": "取消",
6 | "confirm": "确认",
7 | "update": "更新",
8 | "remove": "删除",
9 | "move_to_top": "移到顶部",
10 |
11 | "favorites_title": "关注",
12 | "empty_favorite_title": "无关注直播",
13 | "empty_favorite_subtitle": "请先关注其他直播间",
14 | "empty_favorite_offline_title": "无未开播直播间",
15 | "empty_favorite_offline_subtitle": "请先关注其他直播间",
16 | "empty_favorite_online_title": "无已开播直播间",
17 | "empty_favorite_online_subtitle": "请先关注其他直播间",
18 | "show_offline_rooms": "显示未直播的直播间",
19 | "hide_offline_rooms": "隐藏未直播的直播间",
20 | "room_info_content": "房间号: {roomid}\n平台: {platform}\n昵称: {nickname}\n标题: {title}\n状态: {livestatus}",
21 | "online_room_title": "已开播",
22 | "offline_room_title": "未开播",
23 |
24 | "popular_title": "热门",
25 | "empty_live_title": "未发现直播",
26 | "empty_live_subtitle": "请点击下方按钮切换平台",
27 | "switch_platform": "切换直播平台",
28 |
29 | "areas_title": "分区",
30 | "empty_areas_title": "未发现分区",
31 | "empty_areas_subtitle": "请点击下方按钮切换平台",
32 | "empty_areas_room_title": "未发现直播",
33 | "empty_areas_room_subtitle": "下滑/上滑刷新数据",
34 | "favorite_areas": "关注分区",
35 |
36 | "search_input_hint": "输入直播关键字",
37 | "only_living": "只搜索直播中",
38 | "empty_search_title": "未发现直播",
39 | "empty_search_subtitle": "请输入其他关键字搜索",
40 |
41 | "settings_title": "设置",
42 | "general": "通用",
43 | "video": "视频",
44 | "custom": "定制",
45 | "experiment": "实验",
46 |
47 | "change_theme_color": "主题颜色",
48 | "change_theme_color_subtitle": "切换软件的主题颜色",
49 |
50 | "change_theme_mode": "主题模式",
51 | "change_theme_mode_subtitle": "切换系统/亮色/暗色模式",
52 |
53 | "change_language": "切换语言",
54 | "change_language_subtitle": "切换软件的显示语言",
55 |
56 | "backup_recover": "备份与恢复",
57 | "backup_recover_subtitle": "创建备份与恢复",
58 | "create_backup" : "创建备份",
59 | "create_backup_subtitle" : "可用于恢复当前数据",
60 | "recover_backup" : "恢复备份",
61 | "recover_backup_subtitle" : "从备份文件中恢复",
62 | "auto_backup" : "自动备份",
63 | "backup_directory" : "备份目录",
64 | "create_backup_success": "创建备份成功",
65 | "create_backup_failed": "创建备份失败",
66 | "select_recover_file": "选择备份文件",
67 | "recover_backup_success": "恢复备份成功,请重启",
68 | "recover_backup_failed": "恢复备份失败",
69 |
70 | "enable_dynamic_color": "动态取色",
71 | "enable_dynamic_color_subtitle": "启用Monet壁纸动态取色",
72 |
73 | "enable_dense_favorites_mode": "紧凑模式",
74 | "enable_dense_favorites_mode_subtitle": "关注页面可显示更多直播间",
75 |
76 | "enable_background_play": "后台播放",
77 | "enable_background_play_subtitle": "当暂时切出APP时,允许后台播放",
78 |
79 | "enable_screen_keep_on": "屏幕常亮",
80 | "enable_screen_keep_on_subtitle": "当处于直播播放页,屏幕保持常亮",
81 |
82 | "enable_fullscreen_default": "自动全屏",
83 | "enable_fullscreen_default_subtitle": "当进入直播播放页,自动进入全屏",
84 |
85 | "enable_auto_check_update": "自动检查更新",
86 | "enable_auto_check_update_subtitle": "在每次进入软件时,自动检查更新",
87 |
88 | "float_overlay_ratio": "悬浮窗尺寸",
89 | "float_overlay_ratio_subtitle": "视频小窗播放时,悬浮窗横向相对比例",
90 |
91 | "prefer_platform": "首选直播平台",
92 | "prefer_platform_subtitle": "当进入热门/分区,首选的直播平台",
93 |
94 | "prefer_resolution": "首选清晰度",
95 | "prefer_resolution_subtitle": "当进入直播播放页,首选的视频清晰度",
96 |
97 | "auto_refresh_time": "定时刷新时间",
98 | "auto_refresh_time_subtitle": "定时刷新关注直播间状态",
99 |
100 | "about": "关于",
101 | "version": "版本",
102 | "what_is_new": "最新特性",
103 | "check_update": "检查更新",
104 | "new_version_info": "发现新版本: v{version}",
105 | "no_new_version_info": "已在使用最新版本",
106 | "license": "开源许可证",
107 | "project": "项目",
108 | "support_donate": "捐赠支持",
109 | "issue_feedback": "问题反馈",
110 | "develop_progress": "开发进度",
111 | "project_page": "项目主页",
112 | "project_alert" : "项目声明",
113 | "contact": "联系",
114 | "qq_group": "QQ群",
115 | "qq_group_num": "群号: {number}",
116 | "telegram": "Telegram",
117 | "email": "邮件",
118 | "github": "Github",
119 |
120 | "help": "帮助",
121 |
122 | "settings_timedclose_title": "定时关闭",
123 | "timedclose_time": "{time} 分钟",
124 |
125 | "settings_videofit_title": "比例设置",
126 | "videofit_contain": "默认比例",
127 | "videofit_fill": "填充屏幕",
128 | "videofit_cover": "居中裁剪",
129 | "videofit_fitwidth": "适应宽度",
130 | "videofit_fitheight": "适应高度",
131 |
132 | "settings_danmaku_title": "弹幕设置",
133 | "settings_danmaku_area": "弹幕区域",
134 | "settings_danmaku_opacity": "不透明度",
135 | "settings_danmaku_speed": "弹幕速度",
136 | "settings_danmaku_fontsize": "弹幕字号",
137 | "settings_danmaku_fontBorder": "描边宽度",
138 | "settings_danmaku_amount": "弹幕数量",
139 |
140 | "follow": "关注",
141 | "unfollow": "取消关注",
142 | "unfollow_message": "确定要取消关注{name}吗?",
143 | "followed": "已关注",
144 | "offline": "未直播",
145 | "info_is_offline": "{name}未开始直播.",
146 | "info_is_replay": "{name}轮播视频中.",
147 |
148 | "float_window_play": "小窗播放",
149 | "dlan_button_info": "DLNA投屏",
150 | "dlan_title": "DLNA投屏",
151 | "dlan_device_not_found": "未发现DLNA设备",
152 |
153 | "play_video_failed": "无法播放直播",
154 | "retry": "重试",
155 | "replay": "录播",
156 |
157 |
158 | "history": "历史记录",
159 | "empty_history": "无观看历史记录"
160 | }
--------------------------------------------------------------------------------
/lib/common/models/index.dart:
--------------------------------------------------------------------------------
1 | library models;
2 |
3 | export 'live_room.dart';
4 | export 'live_area.dart';
5 | export './live_message.dart';
6 |
--------------------------------------------------------------------------------
/lib/common/models/live_area.dart:
--------------------------------------------------------------------------------
1 | class LiveArea {
2 | String platform = '';
3 | String areaType = '';
4 | String typeName = '';
5 | String areaId = '';
6 | String areaName = '';
7 | String areaPic = '';
8 | String shortName = '';
9 |
10 | LiveArea();
11 |
12 | LiveArea.fromJson(Map json)
13 | : platform = json['platform'] ?? '',
14 | areaType = json['areaType'] ?? '',
15 | typeName = json['typeName'] ?? '',
16 | areaId = json['areaId'] ?? '',
17 | areaName = json['areaName'] ?? '',
18 | areaPic = json['areaPic'] ?? '',
19 | shortName = json['shortName'] ?? '';
20 |
21 | Map toJson() => {
22 | 'platform': platform,
23 | 'areaType': areaType,
24 | 'typeName': typeName,
25 | 'areaId': areaId,
26 | 'areaName': areaName,
27 | 'areaPic': areaPic,
28 | 'shortName': shortName,
29 | };
30 | }
31 |
--------------------------------------------------------------------------------
/lib/common/models/live_message.dart:
--------------------------------------------------------------------------------
1 | enum LiveMessageType {
2 | /// 聊天
3 | chat,
4 |
5 | /// 礼物,暂时不支持
6 | gift,
7 |
8 | /// 在线人数
9 | online,
10 |
11 | /// 醒目留言
12 | superChat,
13 | }
14 |
15 | class LiveMessage {
16 | /// 消息类型
17 | final LiveMessageType type;
18 |
19 | /// 用户名
20 | final String userName;
21 |
22 | /// 信息
23 | final String message;
24 |
25 | /// 数据
26 | /// 单Type=Online时,Data为人气值(long)
27 | final dynamic data;
28 |
29 | /// 弹幕颜色
30 | final LiveMessageColor color;
31 | LiveMessage({
32 | required this.type,
33 | required this.userName,
34 | required this.message,
35 | this.data,
36 | required this.color,
37 | });
38 | }
39 |
40 | class LiveMessageColor {
41 | final int r, g, b;
42 | LiveMessageColor(this.r, this.g, this.b);
43 | static LiveMessageColor get white => LiveMessageColor(255, 255, 255);
44 | static LiveMessageColor numberToColor(int intColor) {
45 | var obj = intColor.toRadixString(16);
46 |
47 | LiveMessageColor color = LiveMessageColor.white;
48 | if (obj.length == 4) {
49 | obj = "00$obj";
50 | }
51 | if (obj.length == 6) {
52 | var R = int.parse(obj.substring(0, 2), radix: 16);
53 | var G = int.parse(obj.substring(2, 4), radix: 16);
54 | var B = int.parse(obj.substring(4, 6), radix: 16);
55 |
56 | color = LiveMessageColor(R, G, B);
57 | }
58 | if (obj.length == 8) {
59 | var R = int.parse(obj.substring(2, 4), radix: 16);
60 | var G = int.parse(obj.substring(4, 6), radix: 16);
61 | var B = int.parse(obj.substring(6, 8), radix: 16);
62 | //var A = int.parse(obj.substring(0, 2), radix: 16);
63 | color = LiveMessageColor(R, G, B);
64 | }
65 |
66 | return color;
67 | }
68 |
69 | @override
70 | String toString() {
71 | return "#${r.toRadixString(16).padLeft(2, '0')}${g.toRadixString(16).padLeft(2, '0')}${b.toRadixString(16).padLeft(2, '0')}";
72 | }
73 | }
74 |
75 | class LiveSuperChatMessage {
76 | final String userName;
77 | final String face;
78 | final String message;
79 | final int price;
80 | final DateTime startTime;
81 | final DateTime endTime;
82 | final String backgroundColor;
83 | final String backgroundBottomColor;
84 | LiveSuperChatMessage({
85 | required this.backgroundBottomColor,
86 | required this.backgroundColor,
87 | required this.endTime,
88 | required this.face,
89 | required this.message,
90 | required this.price,
91 | required this.startTime,
92 | required this.userName,
93 | });
94 | }
95 |
--------------------------------------------------------------------------------
/lib/common/models/live_room.dart:
--------------------------------------------------------------------------------
1 | enum LiveStatus { live, offline, replay, unknown }
2 |
3 | enum Platforms { huya, bilibili, douyu, unknown }
4 |
5 | class LiveRoom {
6 | String roomId;
7 | String userId = '';
8 | String link = '';
9 | String title = '';
10 | String nick = '';
11 | String avatar = '';
12 | String cover = '';
13 | String area = '';
14 | String watching = '';
15 | String followers = '';
16 | String platform = 'UNKNOWN';
17 | LiveStatus liveStatus = LiveStatus.unknown;
18 |
19 | LiveRoom(this.roomId);
20 |
21 | LiveRoom.fromJson(Map json)
22 | : roomId = json['roomId'] ?? '',
23 | userId = json['userId'] ?? '',
24 | title = json['title'] ?? '',
25 | link = json['link'] ?? '',
26 | nick = json['nick'] ?? '',
27 | avatar = json['avatar'] ?? '',
28 | cover = json['cover'] ?? '',
29 | area = json['area'] ?? '',
30 | watching = json['watching'] ?? '',
31 | followers = json['followers'] ?? '',
32 | platform = json['platform'] ?? '',
33 | liveStatus = LiveStatus.values[json['liveStatus']];
34 |
35 | Map toJson() => {
36 | 'roomId': roomId,
37 | 'userId': userId,
38 | 'title': title,
39 | 'nick': nick,
40 | 'avatar': avatar,
41 | 'cover': cover,
42 | 'area': area,
43 | 'watching': watching,
44 | 'followers': followers,
45 | 'platform': platform,
46 | 'liveStatus': liveStatus.index
47 | };
48 |
49 | @override
50 | bool operator ==(covariant LiveRoom other) =>
51 | platform == other.platform && roomId == other.roomId;
52 |
53 | @override
54 | int get hashCode => int.parse(roomId);
55 | }
56 |
--------------------------------------------------------------------------------
/lib/common/services/index.dart:
--------------------------------------------------------------------------------
1 | library services;
2 |
3 | export './settings_service.dart';
4 |
--------------------------------------------------------------------------------
/lib/common/style/index.dart:
--------------------------------------------------------------------------------
1 | library style;
2 |
3 | export './theme.dart';
4 |
--------------------------------------------------------------------------------
/lib/common/style/theme.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/material.dart';
4 |
5 | class MyTheme {
6 | Color? primaryColor;
7 | ColorScheme? colorScheme;
8 | String? fontFamily;
9 |
10 | MyTheme({
11 | this.primaryColor,
12 | this.colorScheme,
13 | }) : assert(colorScheme == null || primaryColor == null);
14 |
15 | get lightThemeData {
16 | if (Platform.isWindows) {
17 | fontFamily = 'PingFang';
18 | }
19 | return ThemeData(
20 | useMaterial3: true,
21 | colorSchemeSeed: primaryColor,
22 | colorScheme: colorScheme,
23 | brightness: Brightness.light,
24 | fontFamily: fontFamily,
25 | );
26 | }
27 |
28 | get darkThemeData {
29 | if (Platform.isWindows) {
30 | fontFamily = 'PingFang';
31 | }
32 | return ThemeData(
33 | useMaterial3: true,
34 | colorSchemeSeed: primaryColor,
35 | colorScheme: colorScheme?.copyWith(
36 | error: const Color.fromARGB(255, 255, 99, 71),
37 | ),
38 | brightness: Brightness.dark,
39 | fontFamily: fontFamily,
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lib/common/utils/cache_manager.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: implementation_imports
2 | import 'package:path/path.dart' as p;
3 | import 'package:file/local.dart';
4 | import 'package:path_provider/path_provider.dart';
5 | import 'package:flutter_cache_manager/flutter_cache_manager.dart';
6 | import 'package:flutter_cache_manager/src/storage/file_system/file_system_io.dart';
7 |
8 | class CustomCacheManager {
9 | static const key = 'customCacheKey';
10 |
11 | static CacheManager instance = CacheManager(
12 | Config(
13 | key,
14 | stalePeriod: const Duration(days: 7),
15 | maxNrOfCacheObjects: 20,
16 | repo: JsonCacheInfoRepository(databaseName: key),
17 | fileSystem: IOFileSystem(key),
18 | fileService: HttpFileService(),
19 | ),
20 | );
21 |
22 | static Future cacheSize() async {
23 | var baseDir = await getTemporaryDirectory();
24 | var path = p.join(baseDir.path, key);
25 |
26 | var fs = const LocalFileSystem();
27 | var directory = fs.directory((path));
28 | return (await directory.stat()).size / 8 / 1000;
29 | }
30 |
31 | static Future clearCache() async {
32 | var baseDir = await getTemporaryDirectory();
33 | var path = p.join(baseDir.path, key);
34 |
35 | var fs = const LocalFileSystem();
36 | var directory = fs.directory((path));
37 | await directory.delete(recursive: true);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/common/utils/index.dart:
--------------------------------------------------------------------------------
1 | library utils;
2 |
3 | export './text_util.dart';
4 | export './pref_util.dart';
5 | export './version_util.dart';
6 | export './cache_manager.dart';
7 | export './snackbar_util.dart';
8 | export './js_engine.dart';
9 |
--------------------------------------------------------------------------------
/lib/common/utils/js_engine.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/services.dart';
2 | import 'package:flutter_js/flutter_js.dart';
3 |
4 | class JsEngine {
5 | static JavascriptRuntime? _jsRuntime;
6 | static JavascriptRuntime get jsRuntime => _jsRuntime!;
7 |
8 | static void init() {
9 | _jsRuntime ??= getJavascriptRuntime();
10 | jsRuntime.enableHandlePromises();
11 | loadPackages();
12 | }
13 |
14 | static Future loadPackages() async {
15 | final cryptojs = await rootBundle.loadString('assets/crypto-js.js');
16 | jsRuntime.evaluate(cryptojs);
17 | }
18 |
19 | static JsEvalResult evaluate(String code) {
20 | return jsRuntime.evaluate(code);
21 | }
22 |
23 | static Future evaluateAsync(String code) {
24 | return jsRuntime.evaluateAsync(code);
25 | }
26 |
27 | static dynamic onMessage(String channelName, dynamic Function(dynamic) fn) {
28 | return jsRuntime.onMessage(channelName, (args) => null);
29 | }
30 |
31 | static dynamic sendMessage({
32 | required String channelName,
33 | required List args,
34 | String? uuid,
35 | }) {
36 | return jsRuntime.sendMessage(channelName: channelName, args: args);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lib/common/utils/pref_util.dart:
--------------------------------------------------------------------------------
1 | import 'package:shared_preferences/shared_preferences.dart';
2 |
3 | ///This is the new util class for the shared preferences.
4 | ///
5 | ///And the old `storage.dart` will be deprecated.
6 | class PrefUtil {
7 | static late SharedPreferences prefs;
8 |
9 | static dynamic getAnyPref(String key) {
10 | return prefs.get(key);
11 | }
12 |
13 | static void setAnyPref(String key, dynamic value) {
14 | if (value is String) {
15 | prefs.setString(key, value);
16 | } else if (value is int) {
17 | prefs.setInt(key, value);
18 | } else if (value is bool) {
19 | prefs.setBool(key, value);
20 | } else if (value is double) {
21 | prefs.setDouble(key, value);
22 | } else if (value is List) {
23 | prefs.setStringList(key, value);
24 | }
25 | }
26 |
27 | static bool? getBool(String key) {
28 | return prefs.getBool(key);
29 | }
30 |
31 | static Future setBool(String key, bool value) {
32 | return prefs.setBool(key, value);
33 | }
34 |
35 | static int? getInt(String key) {
36 | return prefs.getInt(key);
37 | }
38 |
39 | static Future setInt(String key, int value) {
40 | return prefs.setInt(key, value);
41 | }
42 |
43 | static String? getString(String key) {
44 | return prefs.getString(key);
45 | }
46 |
47 | static Future setString(String key, String value) {
48 | return prefs.setString(key, value);
49 | }
50 |
51 | static double? getDouble(String key) {
52 | return prefs.getDouble(key);
53 | }
54 |
55 | static Future setDouble(String key, double value) {
56 | return prefs.setDouble(key, value);
57 | }
58 |
59 | static List? getStringList(String key) {
60 | return prefs.getStringList(key);
61 | }
62 |
63 | static Future setStringList(String key, List value) {
64 | return prefs.setStringList(key, value);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/lib/common/utils/snackbar_util.dart:
--------------------------------------------------------------------------------
1 | import 'package:get/get.dart';
2 |
3 | class SnackBarUtil {
4 | static void success(String text) {
5 | Get.snackbar(
6 | 'Success',
7 | text,
8 | duration: const Duration(seconds: 2),
9 | backgroundColor: Get.theme.colorScheme.surfaceVariant,
10 | colorText: Get.theme.colorScheme.onSurfaceVariant,
11 | snackPosition: SnackPosition.BOTTOM,
12 | );
13 | }
14 |
15 | static void error(String text) {
16 | Get.snackbar(
17 | 'Error',
18 | text,
19 | duration: const Duration(seconds: 2),
20 | backgroundColor: Get.theme.colorScheme.errorContainer,
21 | colorText: Get.theme.colorScheme.onErrorContainer,
22 | snackPosition: SnackPosition.BOTTOM,
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/lib/common/utils/text_util.dart:
--------------------------------------------------------------------------------
1 | String readableCount(String info) {
2 | try {
3 | int count = int.parse(info);
4 | if (count > 10000) {
5 | return '${(count / 10000).toStringAsFixed(1)}万';
6 | }
7 | } catch (e) {
8 | return info;
9 | }
10 | return info;
11 | }
12 |
--------------------------------------------------------------------------------
/lib/common/utils/version_util.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'package:http/http.dart' as http;
3 |
4 | class VersionUtil {
5 | static const String version = '1.1.0';
6 | static const String projectUrl = 'https://github.com/Jackiu1997/pure_live';
7 | static const String releaseUrl =
8 | 'https://api.github.com/repos/Jackiu1997/pure_live/releases';
9 | static const String issuesUrl =
10 | 'https://github.com/Jackiu1997/pure_live/issues';
11 | static const String kanbanUrl =
12 | 'https://jackiu-notes.notion.site/50bc0d3d377445eea029c6e3d4195671?v=663125e639b047cea5e69d8264926b8b';
13 |
14 | static const String githubUrl = 'https://github.com/Jackiu1997';
15 | static const String email = 'jackiu1997@gmail.com';
16 | static const String emailUrl =
17 | 'mailto:jackiu1997@gmail.com?subject=PureLive Feedback';
18 | static const String telegramGroup = 't.me/pure_live_channel';
19 | static const String telegramGroupUrl = 'https://t.me/pure_live_channel';
20 |
21 | static String latestVersion = version;
22 | static String latestUpdateLog = '';
23 |
24 | static Future checkUpdate() async {
25 | try {
26 | var response = await http.get(Uri.parse(releaseUrl));
27 | var latest = (await jsonDecode(response.body))[0];
28 | latestVersion = latest['tag_name'].replaceAll('v', '');
29 | latestUpdateLog = latest['body'];
30 | } catch (e) {
31 | latestUpdateLog = e.toString();
32 | }
33 | }
34 |
35 | static bool hasNewVersion() {
36 | List latestVersions = latestVersion.split('-')[0].split('.');
37 | List versions = version.split('-')[0].split('.');
38 | for (int i = 0; i < latestVersions.length; i++) {
39 | if (int.parse(latestVersions[i]) > int.parse(versions[i])) {
40 | return true;
41 | } else if (int.parse(latestVersions[i]) < int.parse(versions[i])) {
42 | return false;
43 | }
44 | }
45 | return false;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/lib/common/widgets/custom_icons.dart:
--------------------------------------------------------------------------------
1 | /// Flutter icons CustomIcons
2 | /// Copyright (C) 2023 by original authors @ fluttericon.com, fontello.com
3 | /// This font was generated by FlutterIcon.com, which is derived from Fontello.
4 | ///
5 | /// To use this font, place it in your fonts/ directory and include the
6 | /// following in your pubspec.yaml
7 | ///
8 | /// flutter:
9 | /// fonts:
10 | /// - family: CustomIcons
11 | /// fonts:
12 | /// - asset: fonts/CustomIcons.ttf
13 | ///
14 | ///
15 | /// * Font Awesome 5, Copyright (C) 2016 by Dave Gandy
16 | /// Author: Dave Gandy
17 | /// License: SIL (https://github.com/FortAwesome/Font-Awesome/blob/master/LICENSE.txt)
18 | /// Homepage: http://fortawesome.github.com/Font-Awesome/
19 | /// * Font Awesome 4, Copyright (C) 2016 by Dave Gandy
20 | /// Author: Dave Gandy
21 | /// License: SIL ()
22 | /// Homepage: http://fortawesome.github.com/Font-Awesome/
23 | ///
24 | // ignore_for_file: constant_identifier_names
25 |
26 | import 'package:flutter/widgets.dart';
27 |
28 | class CustomIcons {
29 | CustomIcons._();
30 |
31 | static const _kFontFam = 'CustomIcons';
32 | static const String? _kFontPkg = null;
33 |
34 | static const IconData danmaku_close =
35 | IconData(0xe800, fontFamily: _kFontFam, fontPackage: _kFontPkg);
36 | static const IconData danmaku_open =
37 | IconData(0xe801, fontFamily: _kFontFam, fontPackage: _kFontPkg);
38 | static const IconData danmaku_setting =
39 | IconData(0xe802, fontFamily: _kFontFam, fontPackage: _kFontPkg);
40 | static const IconData popular =
41 | IconData(0xe803, fontFamily: _kFontFam, fontPackage: _kFontPkg);
42 | static const IconData search =
43 | IconData(0xe804, fontFamily: _kFontFam, fontPackage: _kFontPkg);
44 | static const IconData qq_1 =
45 | IconData(0xe805, fontFamily: _kFontFam, fontPackage: _kFontPkg);
46 | static const IconData float_window =
47 | IconData(0xe806, fontFamily: _kFontFam, fontPackage: _kFontPkg);
48 | static const IconData cast =
49 | IconData(0xe807, fontFamily: _kFontFam, fontPackage: _kFontPkg);
50 | static const IconData github_circled =
51 | IconData(0xf09b, fontFamily: _kFontFam, fontPackage: _kFontPkg);
52 | static const IconData mail_squared =
53 | IconData(0xf199, fontFamily: _kFontFam, fontPackage: _kFontPkg);
54 | static const IconData wechat =
55 | IconData(0xf1d7, fontFamily: _kFontFam, fontPackage: _kFontPkg);
56 | static const IconData telegram =
57 | IconData(0xf2c6, fontFamily: _kFontFam, fontPackage: _kFontPkg);
58 | static const IconData alipay =
59 | IconData(0xf642, fontFamily: _kFontFam, fontPackage: _kFontPkg);
60 | }
61 |
--------------------------------------------------------------------------------
/lib/common/widgets/empty_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class EmptyView extends StatelessWidget {
4 | const EmptyView({
5 | Key? key,
6 | required this.icon,
7 | required this.title,
8 | required this.subtitle,
9 | }) : super(key: key);
10 |
11 | final IconData icon;
12 | final String title;
13 | final String subtitle;
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | final color = Theme.of(context).disabledColor;
18 | return Center(
19 | child: Column(
20 | crossAxisAlignment: CrossAxisAlignment.center,
21 | mainAxisSize: MainAxisSize.min,
22 | mainAxisAlignment: MainAxisAlignment.center,
23 | children: [
24 | Icon(icon, size: 144, color: color),
25 | const SizedBox(height: 24),
26 | Text.rich(
27 | TextSpan(children: [
28 | TextSpan(
29 | text: "$title\n",
30 | style: Theme.of(context)
31 | .textTheme
32 | .headlineMedium
33 | ?.copyWith(color: color)),
34 | TextSpan(
35 | text: "\n$subtitle",
36 | style: Theme.of(context)
37 | .textTheme
38 | .titleSmall
39 | ?.copyWith(color: color)),
40 | ]),
41 | textAlign: TextAlign.center,
42 | ),
43 | const SizedBox(height: 32),
44 | ],
45 | ),
46 | );
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/lib/common/widgets/index.dart:
--------------------------------------------------------------------------------
1 | library widgets;
2 |
3 | export './room_card.dart';
4 | export './empty_view.dart';
5 | export './custom_icons.dart';
6 | export './menu_button.dart';
7 | export './search_button.dart';
8 | export './section_listtile.dart';
9 |
--------------------------------------------------------------------------------
/lib/common/widgets/menu_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:get/get.dart';
2 | import 'package:pure_live/common/index.dart';
3 |
4 | import '../../routes/app_pages.dart';
5 |
6 | class MenuButton extends StatelessWidget {
7 | const MenuButton({Key? key}) : super(key: key);
8 |
9 | final menuRoutes = const [
10 | AppPages.settings,
11 | AppPages.about,
12 | AppPages.contact,
13 | AppPages.history,
14 | ];
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | return PopupMenuButton(
19 | tooltip: 'menu',
20 | shape: RoundedRectangleBorder(
21 | borderRadius: BorderRadius.circular(8),
22 | ),
23 | offset: const Offset(12, 0),
24 | position: PopupMenuPosition.under,
25 | icon: const Icon(Icons.menu_rounded),
26 | onSelected: (int index) => Get.toNamed(menuRoutes[index]),
27 | itemBuilder: (context) => [
28 | PopupMenuItem(
29 | value: 0,
30 | padding: const EdgeInsets.symmetric(horizontal: 12),
31 | child: MenuListTile(
32 | leading: const Icon(Icons.settings_rounded),
33 | text: S.of(context).settings_title,
34 | ),
35 | ),
36 | PopupMenuItem(
37 | value: 1,
38 | padding: const EdgeInsets.symmetric(horizontal: 12),
39 | child: MenuListTile(
40 | leading: const Icon(Icons.info_rounded),
41 | text: S.of(context).about,
42 | ),
43 | ),
44 | PopupMenuItem(
45 | value: 2,
46 | padding: const EdgeInsets.symmetric(horizontal: 12),
47 | child: MenuListTile(
48 | leading: const Icon(Icons.contact_support),
49 | text: S.of(context).contact,
50 | ),
51 | ),PopupMenuItem(
52 | value: 3,
53 | padding: const EdgeInsets.symmetric(horizontal: 12),
54 | child: MenuListTile(
55 | leading: const Icon(Icons.history),
56 | text: S.of(context).history,
57 | ),
58 | ),
59 | ],
60 | );
61 | }
62 | }
63 |
64 | class MenuListTile extends StatelessWidget {
65 | final Widget? leading;
66 | final String text;
67 | final Widget? trailing;
68 |
69 | const MenuListTile({
70 | Key? key,
71 | required this.leading,
72 | required this.text,
73 | this.trailing,
74 | }) : super(key: key);
75 |
76 | @override
77 | Widget build(BuildContext context) {
78 | return Row(
79 | children: [
80 | if (leading != null) ...[
81 | leading!,
82 | const SizedBox(width: 12),
83 | ],
84 | Text(
85 | text,
86 | style: Theme.of(context).textTheme.labelMedium,
87 | ),
88 | if (trailing != null) ...[
89 | const SizedBox(width: 24),
90 | trailing!,
91 | ],
92 | ],
93 | );
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/lib/common/widgets/search_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:get/get.dart';
2 | import 'package:pure_live/common/index.dart';
3 |
4 | import '../../routes/app_pages.dart';
5 |
6 | class SearchButton extends StatelessWidget {
7 | const SearchButton({Key? key}) : super(key: key);
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return IconButton(
12 | onPressed: () => Get.toNamed(AppPages.search),
13 | icon: const Icon(CustomIcons.search),
14 | );
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/lib/common/widgets/section_listtile.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class CupertinoSwitchListTile extends StatelessWidget {
5 | const CupertinoSwitchListTile({
6 | Key? key,
7 | required this.value,
8 | required this.onChanged,
9 | this.leading,
10 | this.title,
11 | this.subtitle,
12 | this.activeColor,
13 | }) : super(key: key);
14 |
15 | final Widget? leading;
16 | final Widget? title;
17 | final Widget? subtitle;
18 | final Color? activeColor;
19 | final bool value;
20 | final void Function(bool)? onChanged;
21 |
22 | @override
23 | Widget build(BuildContext context) {
24 | return ListTile(
25 | leading: leading,
26 | title: title,
27 | subtitle: subtitle,
28 | onTap: () {
29 | if (onChanged != null) onChanged!(!value);
30 | },
31 | trailing: CupertinoSwitch(
32 | value: value,
33 | activeColor: activeColor,
34 | onChanged: onChanged,
35 | ),
36 | );
37 | }
38 | }
39 |
40 | class SectionTitle extends StatelessWidget {
41 | final String title;
42 |
43 | const SectionTitle({
44 | required this.title,
45 | Key? key,
46 | }) : super(key: key);
47 |
48 | @override
49 | Widget build(BuildContext context) {
50 | return ListTile(
51 | contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
52 | title: Text(
53 | title,
54 | style: Theme.of(context).textTheme.headlineSmall?.copyWith(
55 | color: Theme.of(context).colorScheme.primary,
56 | fontWeight: FontWeight.w500,
57 | ),
58 | ),
59 | );
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/lib/core/common/binary_writer.dart:
--------------------------------------------------------------------------------
1 | import 'dart:typed_data';
2 |
3 | class BinaryWriter {
4 | List buffer;
5 | int position = 0;
6 | BinaryWriter(this.buffer);
7 | int get length => buffer.length;
8 |
9 | void writeBytes(List list) {
10 | buffer.addAll(list);
11 | position += list.length;
12 | }
13 |
14 | void writeInt(int value, int len, {Endian endian = Endian.big}) {
15 | var b = Uint8List(len).buffer;
16 | var bytes = ByteData.view(b);
17 | if (len == 1) {
18 | //写入byte
19 | bytes.setUint8(0, value.toUnsigned(8));
20 | }
21 | if (len == 2) {
22 | bytes.setInt16(0, value, endian);
23 | }
24 | if (len == 4) {
25 | bytes.setInt32(0, value, endian);
26 | }
27 | if (len == 8) {
28 | bytes.setInt64(0, value, endian);
29 | }
30 |
31 | buffer.addAll(bytes.buffer.asUint8List());
32 | position += len;
33 | }
34 |
35 | void writeDouble(double value, int len, {Endian endian = Endian.big}) {
36 | var b = Uint8List(len).buffer;
37 | var bytes = ByteData.view(b);
38 |
39 | if (len == 4) {
40 | bytes.setFloat32(0, value, endian);
41 | }
42 | if (len == 8) {
43 | bytes.setFloat64(0, value, endian);
44 | }
45 |
46 | buffer.addAll(bytes.buffer.asUint8List());
47 | position += len;
48 | }
49 | }
50 |
51 | class BinaryReader {
52 | Uint8List buffer;
53 | int position = 0;
54 | BinaryReader(this.buffer);
55 | int get length => buffer.length;
56 |
57 | /// 从当前流中读取下一个字节,并使流的当前位置提升 1 个字节
58 | /// 返回下一个字节(0-255)
59 | int read() {
60 | var byte = buffer[position];
61 | position += 1;
62 | return byte;
63 | }
64 |
65 | /// 从当前流中读取指定长度的字节整数,并使流的当前位置提升指定长度。
66 | /// [len] 指定长度
67 | /// len=1为int8,2为int16,4为int32,8为int64。dart中统一为int类型
68 | /// 返回整数
69 | int readInt(int len, {Endian endian = Endian.big}) {
70 | var result = 0;
71 | // if (len == 1) {
72 | // result = buffer[position];
73 | // position += len;
74 | // return result;
75 | // }
76 | var bytes =
77 | Uint8List.fromList(buffer.getRange(position, position + len).toList());
78 | var byteBuffer = bytes.buffer;
79 | var data = ByteData.view(byteBuffer);
80 | if (len == 1) {
81 | result = data.getUint8(0);
82 | }
83 | if (len == 2) {
84 | result = data.getInt16(0, endian);
85 | }
86 | if (len == 4) {
87 | result = data.getInt32(0, endian);
88 | }
89 | if (len == 8) {
90 | result = data.getInt64(0, endian);
91 | }
92 | position += len;
93 | return result;
94 | }
95 |
96 | /// 读取字节
97 | /// int长度=1
98 | int readByte({Endian endian = Endian.big}) {
99 | return readInt(1, endian: endian);
100 | }
101 |
102 | /// 读取
103 | /// int长度=2
104 | int readShort({Endian endian = Endian.big}) {
105 | return readInt(2, endian: endian);
106 | }
107 |
108 | /// 读取字节
109 | /// int长度=4
110 | int readInt32({Endian endian = Endian.big}) {
111 | return readInt(4, endian: endian);
112 | }
113 |
114 | /// 读取字节
115 | /// int长度=8
116 | int readLong({Endian endian = Endian.big}) {
117 | return readInt(8, endian: endian);
118 | }
119 |
120 | /// 从当前流中读取指定长度的字节数组,并使流的当前位置提升指定长度。
121 | /// [len] 指定长度
122 | /// 返回字节数组
123 | Uint8List readBytes(int len) {
124 | var bytes =
125 | Uint8List.fromList(buffer.getRange(position, position + len).toList());
126 | position += len;
127 | return bytes;
128 | }
129 |
130 | /// 从当前流中读取指定长度的字节浮点数,并使流的当前位置提升指定长度。
131 | /// [len] 指定长度
132 | /// len=4为float,8为double。dart中统一为double类型
133 | /// 返回浮点数
134 | double readFloat(int len, {Endian endian = Endian.big}) {
135 | var result = 0.0;
136 | var bytes =
137 | Uint8List.fromList(buffer.getRange(position, position + len).toList());
138 | var byteBuffer = bytes.buffer;
139 | var data = ByteData.view(byteBuffer);
140 | if (len == 4) {
141 | result = data.getFloat32(0, endian);
142 | }
143 | if (len == 8) {
144 | result = data.getFloat64(0, endian);
145 | }
146 | position += len;
147 | return result;
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/lib/core/common/websocket_utils.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:web_socket_channel/io.dart';
4 |
5 | enum SocketStatus {
6 | connected,
7 | failed,
8 | closed,
9 | }
10 |
11 | class WebScoketUtils {
12 | SocketStatus status = SocketStatus.closed;
13 |
14 | /// 链接
15 | final String url;
16 |
17 | /// 心跳时间
18 | final int heartBeatTime;
19 |
20 | /// 接收到信息
21 | final Function(dynamic)? onMessage;
22 |
23 | /// 连接关闭
24 | final Function(String msg)? onClose;
25 |
26 | /// 尝试重连
27 | final Function()? onReconnect;
28 |
29 | /// 准备就绪
30 | final Function()? onReady;
31 |
32 | /// 心跳
33 | final Function()? onHeartBeat;
34 |
35 | /// 请求头
36 | Map? headers;
37 | WebScoketUtils({
38 | required this.url,
39 | required this.heartBeatTime,
40 | this.onMessage,
41 | this.onClose,
42 | this.onReconnect,
43 | this.onReady,
44 | this.onHeartBeat,
45 | this.headers,
46 | });
47 | IOWebSocketChannel? webSocket;
48 | Timer? heartBeatTimer;
49 |
50 | /// 重连次数
51 | int reconnectTime = 0;
52 | Timer? reconnectTimer;
53 |
54 | /// 最大重连次数
55 | int maxReconnectTime = 5;
56 |
57 | StreamSubscription? streamSubscription;
58 |
59 | void connect() async {
60 | close();
61 | try {
62 | webSocket = IOWebSocketChannel.connect(
63 | url,
64 | connectTimeout: const Duration(seconds: 10),
65 | headers: headers,
66 | );
67 |
68 | await webSocket?.ready;
69 | reday();
70 | } catch (e) {
71 | onError(e, e);
72 | }
73 | }
74 |
75 | /// 连接完成
76 | void reday() {
77 | status = SocketStatus.connected;
78 |
79 | streamSubscription = webSocket?.stream.listen(
80 | (data) => receiveMessage(data),
81 | onError: (e, s) => onError(e, s),
82 | onDone: onDone,
83 | );
84 |
85 | onReady?.call();
86 | initHeartBeat();
87 | }
88 |
89 | void initHeartBeat() {
90 | heartBeatTimer = Timer.periodic(
91 | Duration(milliseconds: heartBeatTime),
92 | (timer) {
93 | onHeartBeat?.call();
94 | },
95 | );
96 | }
97 |
98 | void receiveMessage(dynamic data) {
99 | //接受到一条信息才算重连成功
100 | reconnectTime = 0;
101 | onMessage?.call(data);
102 | }
103 |
104 | void onError(e, s) {
105 | status = SocketStatus.failed;
106 | onClose?.call(e.toString());
107 | }
108 |
109 | void onDone() {
110 | if (status == SocketStatus.closed) {
111 | return;
112 | }
113 | onReconnect?.call();
114 | reconnect();
115 | }
116 |
117 | void sendMessage(dynamic message) {
118 | if (status == SocketStatus.connected) {
119 | webSocket?.sink.add(message);
120 | }
121 | }
122 |
123 | void close() {
124 | status = SocketStatus.closed;
125 |
126 | streamSubscription?.cancel();
127 |
128 | reconnectTimer?.cancel();
129 | reconnectTimer = null;
130 |
131 | webSocket?.sink.close();
132 |
133 | heartBeatTimer?.cancel();
134 | heartBeatTimer = null;
135 | }
136 |
137 | void reconnect() {
138 | status = SocketStatus.closed;
139 | if (reconnectTime < maxReconnectTime) {
140 | reconnectTime++;
141 | reconnectTimer ??= Timer.periodic(const Duration(seconds: 5), (timer) {
142 | connect();
143 | });
144 | } else {
145 | onClose?.call("重连超过最大次数,与服务器断开连接");
146 | reconnectTimer?.cancel();
147 | reconnectTimer = null;
148 | close();
149 | return;
150 | }
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/lib/core/index.dart:
--------------------------------------------------------------------------------
1 | library api;
2 |
3 | export 'sites.dart';
4 |
--------------------------------------------------------------------------------
/lib/core/interface/live_danmaku.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:pure_live/common/models/index.dart';
4 |
5 | class LiveDanmaku {
6 | Function(LiveMessage msg)? onMessage;
7 | Function(String msg)? onClose;
8 | Function()? onReady;
9 |
10 | /// 心跳时间
11 | int heartbeatTime = 0;
12 |
13 | /// 发生心跳
14 | void heartbeat() {}
15 |
16 | /// 开始接收信息
17 | Future start(dynamic args) {
18 | return Future.value();
19 | }
20 |
21 | /// 停止接收信息
22 | Future stop() {
23 | return Future.value();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/lib/core/interface/live_site.dart:
--------------------------------------------------------------------------------
1 | import 'package:pure_live/common/models/index.dart';
2 |
3 | import '../interface/live_danmaku.dart';
4 |
5 | class LiveSite {
6 | /// 站点唯一ID
7 | String id = "";
8 |
9 | /// 站点名称
10 | String name = "";
11 |
12 | /// 站点名称
13 | LiveDanmaku getDanmaku() => LiveDanmaku();
14 |
15 | /// 获取直播间所有清晰度的url
16 | /// @param room
17 | Future