├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md └── workflows │ ├── create_artifacts.yml │ └── create_releases.yml ├── .gitignore ├── .metadata ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── INSTALL.MD ├── LICENSE ├── README.md ├── USAGE.MD ├── analysis_options.yaml ├── assets ├── ccextractor └── ccx.svg ├── ccextractor ├── ccextractorwinfull.exe ├── flatpak └── org.ccextractor.gui.json ├── lib ├── bloc │ ├── dashboard_bloc │ │ ├── dashboard_bloc.dart │ │ ├── dashboard_event.dart │ │ └── dashboard_state.dart │ ├── process_bloc │ │ ├── process_bloc.dart │ │ ├── process_event.dart │ │ └── process_state.dart │ ├── settings_bloc │ │ ├── settings_bloc.dart │ │ ├── settings_event.dart │ │ └── settings_state.dart │ └── updater_bloc │ │ ├── updater_bloc.dart │ │ ├── updater_event.dart │ │ └── updater_state.dart ├── bloc_observer.dart ├── main.dart ├── models │ └── settings_model.dart ├── repositories │ ├── ccextractor.dart │ └── settings_repository.dart ├── screens │ ├── dashboard │ │ ├── components │ │ │ ├── add_files.dart │ │ │ ├── custom_snackbar.dart │ │ │ ├── multi_process_tile.dart │ │ │ ├── process_tile.dart │ │ │ ├── start_stop_button.dart │ │ │ └── udp_button.dart │ │ └── dashboard.dart │ ├── home.dart │ └── settings │ │ ├── basic_settings.dart │ │ ├── components │ │ ├── current_command.dart │ │ ├── custom_divider.dart │ │ ├── custom_dropdown.dart │ │ ├── custom_path_button.dart │ │ ├── custom_swtich_listTile.dart │ │ └── custom_textfield.dart │ │ ├── hardsubx_settings.dart │ │ ├── input_settings.dart │ │ ├── obscure_settings.dart │ │ └── output_settings.dart └── utils │ ├── constants.dart │ └── responsive.dart ├── linux ├── .gitignore ├── CMakeLists.txt ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake ├── main.cc ├── my_application.cc └── my_application.h ├── macos ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── app_icon_1024.png │ │ ├── app_icon_128.png │ │ ├── app_icon_16.png │ │ ├── app_icon_256.png │ │ ├── app_icon_32.png │ │ ├── app_icon_512.png │ │ └── app_icon_64.png │ ├── Base.lproj │ └── MainMenu.xib │ ├── Configs │ ├── AppInfo.xcconfig │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── Warnings.xcconfig │ ├── DebugProfile.entitlements │ ├── Info.plist │ ├── MainFlutterWindow.swift │ └── Release.entitlements ├── pubspec.lock ├── pubspec.yaml ├── snap ├── gui │ ├── ccextractor-gui.desktop │ └── ccextractor-gui.png └── snapcraft.yaml ├── test └── bloc_test.dart ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── index.html └── manifest.json └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter ├── CMakeLists.txt ├── generated_plugin_registrant.cc ├── generated_plugin_registrant.h └── generated_plugins.cmake └── runner ├── CMakeLists.txt ├── Runner.rc ├── flutter_window.cpp ├── flutter_window.h ├── main.cpp ├── resource.h ├── resources └── ccx.ico ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/create_artifacts.yml: -------------------------------------------------------------------------------- 1 | name: Upload artifact 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | tags-ignore: # ignore push via new tag 8 | - '*.*' 9 | pull_request: 10 | types: [opened, synchronize, reopened] 11 | 12 | jobs: 13 | flutter_test: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Check out repository 17 | uses: actions/checkout@v2.3.4 18 | - name: Install Flutter 19 | uses: subosito/flutter-action@v1.5.3 20 | with: 21 | channel: 'stable' 22 | - name: Flutter format 23 | run: | 24 | flutter format . --set-exit-if-changed 25 | flutter pub get 26 | flutter pub run import_sorter:main --no-comments --exit-if-changed 27 | - name: Flutter analyze 28 | run: flutter analyze . 29 | - name: Flutter test 30 | run: flutter test 31 | 32 | build_windows: 33 | needs: flutter_test 34 | runs-on: windows-latest 35 | steps: 36 | - name: Check out repository 37 | uses: actions/checkout@v2.3.4 38 | - name: Install Flutter 39 | uses: subosito/flutter-action@v1.5.3 40 | with: 41 | channel: 'stable' 42 | - name: Enable desktop support 43 | run: flutter config --enable-windows-desktop 44 | - name: Build Windows app 45 | run: flutter build windows 46 | - name: Copy VC redistributables 47 | run: | 48 | Copy-Item (vswhere -latest -find 'VC\Redist\MSVC\*\x64\*\msvcp140.dll') . 49 | Copy-Item (vswhere -latest -find 'VC\Redist\MSVC\*\x64\*\vcruntime140.dll') . 50 | Copy-Item (vswhere -latest -find 'VC\Redist\MSVC\*\x64\*\vcruntime140_1.dll') . 51 | working-directory: ./build/windows/runner/Release 52 | - name: Create artifact 53 | uses: actions/upload-artifact@v2 54 | with: 55 | name: CCExtractor Flutter GUI Windows 56 | path: | 57 | ./build/windows/runner/Release/*.exe 58 | ./build/windows/runner/Release/*.dll 59 | ./build/windows/runner/Release/data 60 | 61 | build_linux: 62 | needs: flutter_test 63 | runs-on: ubuntu-latest 64 | steps: 65 | - name: Check out repository 66 | uses: actions/checkout@v2.3.4 67 | - name: Install Flutter 68 | uses: subosito/flutter-action@v1.5.3 69 | with: 70 | channel: 'stable' 71 | - name: Enable desktop support 72 | run: | 73 | flutter config --enable-linux-desktop 74 | sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev --fix-missing 75 | - name: Build Linux app 76 | run: flutter build linux 77 | - name: Create artifact 78 | uses: actions/upload-artifact@v2 79 | with: 80 | name: CCExtractor Flutter GUI Linux 81 | path: ./build/linux/x64/release/bundle/ 82 | -------------------------------------------------------------------------------- /.github/workflows/create_releases.yml: -------------------------------------------------------------------------------- 1 | name: Upload releases 2 | 3 | on: 4 | release: 5 | types: 6 | - created 7 | 8 | jobs: 9 | build_windows: 10 | runs-on: windows-latest 11 | steps: 12 | - name: Check out repository 13 | uses: actions/checkout@v2.3.4 14 | - name: Install Flutter 15 | uses: subosito/flutter-action@v1.5.3 16 | with: 17 | channel: 'stable' 18 | - name: Enable desktop support 19 | run: flutter config --enable-windows-desktop 20 | - name: Build Windows app 21 | run: flutter build windows 22 | - name: Copy VC redistributables 23 | run: | 24 | Copy-Item (vswhere -latest -find 'VC\Redist\MSVC\*\x64\*\msvcp140.dll') . 25 | Copy-Item (vswhere -latest -find 'VC\Redist\MSVC\*\x64\*\vcruntime140.dll') . 26 | Copy-Item (vswhere -latest -find 'VC\Redist\MSVC\*\x64\*\vcruntime140_1.dll') . 27 | working-directory: ./build/windows/runner/Release 28 | - name: Create zip 29 | uses: papeloto/action-zip@v1 30 | with: 31 | files: ./build/windows/runner/Release/ 32 | dest: ./windows.zip 33 | - name: Upload as asset 34 | uses: AButler/upload-release-assets@v2.0 35 | with: 36 | files: './windows.zip' 37 | repo-token: ${{ secrets.GITHUB_TOKEN }} 38 | 39 | build_linux: 40 | runs-on: ubuntu-latest 41 | steps: 42 | - name: Check out repository 43 | uses: actions/checkout@v2.3.4 44 | - name: Install Flutter 45 | uses: subosito/flutter-action@v1.5.3 46 | with: 47 | channel: 'stable' 48 | - name: Enable desktop support 49 | run: | 50 | flutter config --enable-linux-desktop 51 | sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev 52 | - name: Build Linux app 53 | run: flutter build linux 54 | - name: Create zip 55 | uses: papeloto/action-zip@v1 56 | with: 57 | files: ./build/linux/x64/release/bundle/ 58 | dest: ./linux.zip 59 | - name: Upload as asset 60 | uses: AButler/upload-release-assets@v2.0 61 | with: 62 | files: './linux.zip' 63 | repo-token: ${{ secrets.GITHUB_TOKEN }} 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | .vscode/ 22 | 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Web related 36 | lib/generated_plugin_registrant.dart 37 | 38 | # Symbolication related 39 | app.*.symbols 40 | 41 | # Obfuscation related 42 | app.*.map.json 43 | 44 | # Android Studio will place build artifacts here 45 | /android/app/debug 46 | /android/app/profile 47 | /android/app/release 48 | 49 | /samples 50 | 51 | 52 | # flatpak 53 | 54 | flatpak/.flatpak-builder 55 | flatpak/build-dir 56 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: c5a4b4029c0798f37c4a39b479d7cb75daa7b05c 8 | channel: beta 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | slack, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 2. Update the README.md with details of changes to the interface, this includes new environment 13 | variables, exposed ports, useful file locations and container parameters. 14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this 15 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 16 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you 17 | do not have permission to do that, you may request the second reviewer to merge it for you. 18 | -------------------------------------------------------------------------------- /INSTALL.MD: -------------------------------------------------------------------------------- 1 | # Installation 2 | ## Install from releases (WINDOWS ONLY) 3 | - This is the simplest method, just download the installer form [here](https://github.com/CCExtractor/ccextractor/releases) (currently only has windows), and then install it like any other windows program. 4 | 5 | --- 6 | 7 | 8 | ##### If you run the GUI from nighly builds or source, you need to manually have the ccextractor binary in your PATH (`ccextractorwinfull.exe` if you are on windows or `ccextractor` if you are on linux/macOS) 9 | 10 | --- 11 | 12 | ## Nightly builds (WINDOWS, LINUX) 13 | - You can also get the latest files for your operating system from [here](https://nightly.link/CCExtractor/ccextractorfluttergui/workflows/create_artifacts/master). 14 | - Once you unzip it, you should see a ccxgui executable in the folder. Double click to run :D 15 | 16 | ## Building and running the GUI from source (ALL) 17 | - This method only makes sense for people who actually want to debug the app. If you want to skip the hastle of installing frameworks and stuff just go the nightly way, the CI does these below steps for you automatically on every commit. 18 | - To build the GUI from source first install flutter from [here](https://flutter.dev/docs/get-started/install) for your operating system. 19 | - Next enable the flutter platform specific flag with `flutter config --enable--desktop`, more info on this [here](https://flutter.dev/desktop) 20 | - Then clone [this](https://github.com/CCExtractor/ccextractorfluttergui) repository and run `flutter run -d --release` to run the app. 21 | - To install the Gui you will need to build it using `flutter build --release`. The release builds should be located under /build/\ 22 | 23 | ### Additional macOS steps: 24 | - If you build and install from source, you will probably get a `Process not permitted operation error` to fix remove the below lines from macos/*.entitlements files 25 | 26 | ``` 27 | com.apple.security.app-sandbox 28 | 29 | ``` 30 | --- 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 CCExtractor Development 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CCExtractor Flutter GUI 2 | [![Upload artifact](https://github.com/CCExtractor/ccextractorfluttergui/actions/workflows/create_artifacts.yml/badge.svg)](https://github.com/CCExtractor/ccextractorfluttergui/actions/workflows/create_artifacts.yml) 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | [![bloc](https://img.shields.io/badge/flutter-bloc-blue)](https://github.com/felangel/bloc) 5 | [![GitHub All Releases](https://img.shields.io/github/downloads/CCExtractor/ccextractorfluttergui/total.svg)](https://github.com/CCExtractor/ccextractorfluttergui/releases/latest) 6 | 7 | The new cross platform interface is all you need, as it includes all the options. After installing GUI you will have a shortcut in your desktop, this lets users not familiar with CLI to extract subtitles. 8 | 9 | Usually, you will never need to use all the options (and even if you do, all the settings are saved locally at `Documents/config.json` in your PC) for regular usage. 10 | 11 | The GUI basically uses dart's [process](https://api.dart.dev/stable/2.13.4/dart-io/Process-class.html) class to [start](https://api.dart.dev/stable/2.13.4/dart-io/Process/start.html) the ccextractor executable and shows the progress and live output. 12 | 13 | ## Table of contents 14 | * [Installation](#installation) 15 | * [Usage](#usage) 16 | * [Contributing](#contributing) 17 | * [License](#license) 18 | * [GSoC](#gsoc) 19 | 20 | 21 | ## Installation 22 | Depending on your OS of choice, one or multiple options are available. 23 | 24 | ### Windows 25 | - (preferred) [Download the .msi](https://github.com/CCExtractor/ccextractor/releases) to install CCExtractor and the GUI 26 | - Download the built GUI from the releases on this repository. You'll have to provide a CCExtractor binary yourself 27 | - Download this repository and build the GUI by yourself. You'll have to provide a CCExtractor binary yourself 28 | 29 | ### Linux 30 | Executables for Linux can be found [here](https://nightly.link/CCExtractor/ccextractorfluttergui/workflows/create_artifacts/master) or on the [releases page](https://github.com/CCExtractor/ccextractorfluttergui/releases). Both still require to get ccextractor manually. 31 | 32 | ### macOS 33 | MacOS requires you to build the GUI from source and get ccextractor manually. For detailed information on this please refer [INSTALL.MD](INSTALL.MD). 34 | 35 | 36 | ## Usage 37 | For users new to GUI check out the usage guide [here](USAGE.MD). You can also check all the options avaiable in ccextractor [here](https://ccextractor.org/public/general/command_line_usage/). 38 | 39 | 40 | ## Contributing 41 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. Please make sure to update tests as appropriate. For more details, see the [Contributing Page](CONTRIBUTING.md) 42 | 43 | ## License 44 | [MIT License](LICENSE) 45 | 46 | ## GSoC 47 | #### GSoC'21 48 | * Student: @Techno-Disaster ([commits](https://github.com/CCExtractor/ccextractorfluttergui/commits?author=Techno-Disaster)) 49 | * Mentor: @csfmp3 50 | 51 | -------------------------------------------------------------------------------- /USAGE.MD: -------------------------------------------------------------------------------- 1 | # Using the GUI 2 | 3 | The GUI is mainly divided into 2 parts, the home screen and the settings screen(s). 4 | 5 | - The home screen is where you can click the add files button and select one or multiple files. 6 | 7 | 8 | ## Home screen 9 | ![image](https://ccextractor.org/images/flutter_gui/addfiles.png) 10 | 11 | - Once you select your files use the start all button to start running ccextractor on the selected files one by one. 12 | - You can see the live output in the Logs container below. 13 | - You can also use the clear list button to remove all the selected files from the menu. 14 | - The progress for each file is shown with a circular green progress indicator. 15 | - You can cancel/remove any file from the selected files using the delete button. 16 | - To stop ccextractor, simply press the stop all button. 17 | 18 | ![image](https://ccextractor.org/images/flutter_gui/ccxrunning.png) 19 | 20 | ## Settings screen(S) 21 | 22 | The GUI has tons of options so they are seperated into several settings screens. All of the options are saved locally in a json file so you can save the settings you need frequently, 23 | 24 | ![image](https://ccextractor.org/images/flutter_gui/settings.png) 25 | 26 | 27 | - Each setting screen has a current command container at the top which shows you the exact command with the selected settings you will run when you click start all on the home page. 28 | - To update the sttings, simply toggle the option or select the option from the dropdown menu. 29 | - **To save settings which have a textfield press enter after you enter your setting value in the textfield.** 30 | - You can also reset the settings back to the default value with the reset settings button on the top right corner of each of the settings page 31 | 32 | 33 | 34 | To report any bugs, please file a issue on github or get in touch with us on slack. Most CCExtractor developers hang out in a slack team. You're welcome to request an invitation [here](https://ccextractor.org/public/general/support/) 35 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | linter: 2 | rules: 3 | - always_declare_return_types 4 | - always_require_non_null_named_parameters 5 | - annotate_overrides 6 | - avoid_init_to_null 7 | - avoid_null_checks_in_equality_operators 8 | - avoid_relative_lib_imports 9 | - avoid_return_types_on_setters 10 | - avoid_shadowing_type_parameters 11 | - avoid_single_cascade_in_expression_statements 12 | - avoid_types_as_parameter_names 13 | - await_only_futures 14 | - camel_case_extensions 15 | - curly_braces_in_flow_control_structures 16 | - empty_catches 17 | - empty_constructor_bodies 18 | - library_names 19 | - library_prefixes 20 | - no_duplicate_case_values 21 | - null_closures 22 | - prefer_adjacent_string_concatenation 23 | - prefer_collection_literals 24 | - prefer_conditional_assignment 25 | - prefer_contains 26 | - prefer_equal_for_default_values 27 | - prefer_final_fields 28 | - prefer_for_elements_to_map_fromIterable 29 | - prefer_generic_function_type_aliases 30 | - prefer_if_null_operators 31 | - prefer_inlined_adds 32 | - prefer_is_empty 33 | - prefer_is_not_empty 34 | - prefer_iterable_whereType 35 | - prefer_single_quotes 36 | - prefer_spread_collections 37 | - recursive_getters 38 | - slash_for_doc_comments 39 | - sort_child_properties_last 40 | - type_init_formals 41 | - unawaited_futures 42 | - unnecessary_brace_in_string_interps 43 | - unnecessary_const 44 | - unnecessary_getters_setters 45 | - unnecessary_new 46 | - unnecessary_null_in_if_null_operators 47 | - unnecessary_this 48 | - unrelated_type_equality_checks 49 | - unsafe_html 50 | - use_full_hex_values_for_flutter_colors 51 | - use_function_type_syntax_for_parameters 52 | - use_rethrow_when_possible 53 | - valid_regexps -------------------------------------------------------------------------------- /assets/ccextractor: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCExtractor/ccextractorfluttergui/0dad21f205ed4018b4fc069cd050a45186258abc/assets/ccextractor -------------------------------------------------------------------------------- /ccextractor: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCExtractor/ccextractorfluttergui/0dad21f205ed4018b4fc069cd050a45186258abc/ccextractor -------------------------------------------------------------------------------- /ccextractorwinfull.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCExtractor/ccextractorfluttergui/0dad21f205ed4018b4fc069cd050a45186258abc/ccextractorwinfull.exe -------------------------------------------------------------------------------- /flatpak/org.ccextractor.gui.json: -------------------------------------------------------------------------------- 1 | { 2 | "app-id": "com.example.ccxgui", 3 | "runtime": "org.gnome.Platform", 4 | "runtime-version": "40", 5 | "sdk": "org.gnome.Sdk", 6 | "command": "ccxgui", 7 | "separate-locales": false, 8 | "finish-args": [ 9 | "--share=ipc", 10 | "--socket=x11", 11 | "--socket=fallback-x11", 12 | "--socket=wayland", 13 | "--share=network", 14 | "--filesystem=home", 15 | "--filesystem=xdg-documents" 16 | ], 17 | "modules": [ 18 | { 19 | "name": "ccxgui", 20 | "buildsystem": "simple", 21 | "build-commands": [ 22 | "cd ccxgui && mkdir lib/ data/ && mv flutter_assets icudtl.dat data/ && mv lib*.so lib/", 23 | "ls ccxgui", 24 | "cp -r ccxgui /app/ccxgui", 25 | "chmod +x /app/ccxgui/ccxgui", 26 | "mkdir /app/bin", 27 | "ln -s /app/ccxgui/ccxgui /app/bin/ccxgui" 28 | ], 29 | "sources": [ 30 | { 31 | "type": "archive", 32 | "url": "https://github.com/CCExtractor/ccextractorfluttergui/releases/download/v0.2.0/linux.zip", 33 | "dest": "ccxgui", 34 | "sha256": "a87033f0e11572ba948765099b95b6776b2be7f34253f20885bd2e8621441013" 35 | } 36 | 37 | 38 | ] 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /lib/bloc/dashboard_bloc/dashboard_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:equatable/equatable.dart'; 5 | import 'package:file_selector/file_selector.dart'; 6 | 7 | part 'dashboard_event.dart'; 8 | part 'dashboard_state.dart'; 9 | 10 | /// Handles selecting and removing files. 11 | class DashboardBloc extends Bloc { 12 | DashboardBloc() : super(DashboardState(files: [], alreadyPresent: false)); 13 | @override 14 | Stream mapEventToState(DashboardEvent event) async* { 15 | if (event is NewFileAdded) { 16 | List finalEventList = List.from(event.files); 17 | bool alreadyPresent = false; 18 | // create your own wrapper around the third party class as use EquatableMixin 19 | for (var stateFile in state.files) { 20 | for (var eventFile in event.files) { 21 | if (eventFile.path == stateFile.path) { 22 | alreadyPresent = true; 23 | finalEventList.remove(eventFile); 24 | } 25 | } 26 | } 27 | if (alreadyPresent) { 28 | yield state.copyWith( 29 | files: List.from(state.files)..addAll(finalEventList), 30 | alreadyPresent: true); 31 | } else { 32 | yield state.copyWith( 33 | files: List.from(state.files)..addAll(event.files), 34 | alreadyPresent: false); 35 | ; 36 | } 37 | } else if (event is FileRemoved) { 38 | yield state.copyWith( 39 | files: List.from(state.files)..remove(event.file), 40 | alreadyPresent: false); 41 | } else if (event is RemoveAllFiles) { 42 | yield state.copyWith(files: [], alreadyPresent: false); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/bloc/dashboard_bloc/dashboard_event.dart: -------------------------------------------------------------------------------- 1 | part of 'dashboard_bloc.dart'; 2 | 3 | abstract class DashboardEvent extends Equatable { 4 | const DashboardEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class NewFileAdded extends DashboardEvent { 11 | final List files; 12 | 13 | NewFileAdded(this.files); 14 | 15 | @override 16 | List get props => [files]; 17 | } 18 | 19 | /// Remove file from selected files in UI only, to remove from processing, check processing_bloc 20 | class FileRemoved extends DashboardEvent { 21 | final XFile file; 22 | FileRemoved(this.file); 23 | @override 24 | List get props => [file]; 25 | } 26 | 27 | class RemoveAllFiles extends DashboardEvent {} 28 | -------------------------------------------------------------------------------- /lib/bloc/dashboard_bloc/dashboard_state.dart: -------------------------------------------------------------------------------- 1 | part of 'dashboard_bloc.dart'; 2 | 3 | class DashboardState extends Equatable { 4 | final List files; 5 | final bool alreadyPresent; 6 | DashboardState({required this.files, required this.alreadyPresent}); 7 | 8 | DashboardState copyWith({ 9 | required List? files, 10 | bool? alreadyPresent, 11 | }) => 12 | DashboardState( 13 | files: files ?? this.files, 14 | alreadyPresent: alreadyPresent ?? this.alreadyPresent, 15 | ); 16 | @override 17 | List get props => [files, alreadyPresent]; 18 | } 19 | -------------------------------------------------------------------------------- /lib/bloc/process_bloc/process_event.dart: -------------------------------------------------------------------------------- 1 | part of 'process_bloc.dart'; 2 | 3 | abstract class ProcessEvent { 4 | const ProcessEvent(); 5 | } 6 | 7 | class ProcessFilesSubmitted extends ProcessEvent { 8 | // One or more files? Your choice. 9 | final List files; 10 | 11 | const ProcessFilesSubmitted(this.files); 12 | } 13 | 14 | class StartAllProcess extends ProcessEvent {} 15 | 16 | /// ProcessStopped stops all the files from processing. 17 | class StopAllProcess extends ProcessEvent {} 18 | 19 | /// ccx runs on all the files at once 20 | class StartProcessInSplitMode extends ProcessEvent {} 21 | 22 | class SplitModeProcessComplete extends ProcessEvent {} 23 | 24 | /// Removes pending file from state queue 25 | class ProcessFileRemoved extends ProcessEvent { 26 | final XFile file; 27 | 28 | const ProcessFileRemoved(this.file); 29 | } 30 | 31 | /// Stops ccx on a file using .kill() 32 | class ProcessKill extends ProcessEvent { 33 | final XFile file; 34 | 35 | const ProcessKill(this.file); 36 | } 37 | 38 | /// Remvoes all files from selected and stops ccx on any running file 39 | class ProcessRemoveAll extends ProcessEvent {} 40 | 41 | /// Used in callback when ccx emits logs to stderr 42 | class ProcessFileExtractorOutput extends ProcessEvent { 43 | final String log; 44 | 45 | const ProcessFileExtractorOutput(this.log); 46 | } 47 | 48 | /// Used in callback when ccx emits video details to stderr 49 | class ProcessFileVideoDetails extends ProcessEvent { 50 | final List videoDetails; 51 | 52 | ProcessFileVideoDetails(this.videoDetails); 53 | } 54 | 55 | /// Used in callback when ccx emits progress details to stderr 56 | class ProcessFileExtractorProgress extends ProcessEvent { 57 | final String progress; 58 | 59 | const ProcessFileExtractorProgress(this.progress); 60 | } 61 | 62 | class ProcessFileComplete extends ProcessEvent { 63 | final XFile file; 64 | 65 | const ProcessFileComplete(this.file); 66 | } 67 | 68 | // emits a state with exit codes to show in snackbar 69 | class ProcessError extends ProcessEvent { 70 | final int exitCode; 71 | const ProcessError(this.exitCode); 72 | } 73 | 74 | // Runs tcp udp stuff in ccx 75 | class ProcessOnNetwork extends ProcessEvent { 76 | final String type; 77 | final String location; 78 | final String tcppassword; 79 | final String tcpdesc; 80 | 81 | ProcessOnNetwork( 82 | {required this.type, 83 | required this.location, 84 | required this.tcppassword, 85 | required this.tcpdesc}); 86 | } 87 | 88 | // Gets the ccextractor version using `--version` 89 | class GetCCExtractorVersion extends ProcessEvent {} 90 | 91 | // Resets exitcode to 0, ideally should be null but wont emit a new state then 92 | class ResetProcessError extends ProcessEvent {} 93 | -------------------------------------------------------------------------------- /lib/bloc/process_bloc/process_state.dart: -------------------------------------------------------------------------------- 1 | part of 'process_bloc.dart'; 2 | 3 | class ProcessState extends Equatable { 4 | final List orignalList; 5 | final List processed; 6 | final List queue; 7 | final List log; 8 | final String progress; 9 | final List videoDetails; 10 | final XFile? current; 11 | final bool started; 12 | final String? version; 13 | final int? exitCode; 14 | const ProcessState({ 15 | required this.orignalList, 16 | required this.queue, 17 | required this.processed, 18 | required this.videoDetails, 19 | required this.log, 20 | required this.started, 21 | required this.progress, 22 | required this.current, 23 | required this.version, 24 | this.exitCode, 25 | }); 26 | 27 | ProcessState copyWith({ 28 | List? orignalList, 29 | List? processed, 30 | List? queue, 31 | List? log, 32 | List? videoDetails, 33 | bool? started, 34 | String? progress, 35 | String? version, 36 | required XFile? current, 37 | int? exitCode, 38 | }) => 39 | ProcessState( 40 | orignalList: orignalList ?? this.orignalList, 41 | queue: queue ?? this.queue, 42 | processed: processed ?? this.processed, 43 | log: log ?? this.log, 44 | started: started ?? this.started, 45 | progress: progress ?? this.progress, 46 | videoDetails: videoDetails ?? this.videoDetails, 47 | version: version ?? this.version, 48 | current: current, 49 | exitCode: exitCode); 50 | 51 | @override 52 | List get props => [ 53 | queue, 54 | processed, 55 | log, 56 | current, 57 | started, 58 | progress, 59 | videoDetails, 60 | orignalList, 61 | version, 62 | exitCode, 63 | ]; 64 | } 65 | -------------------------------------------------------------------------------- /lib/bloc/settings_bloc/settings_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:equatable/equatable.dart'; 5 | 6 | import 'package:ccxgui/models/settings_model.dart'; 7 | import 'package:ccxgui/repositories/settings_repository.dart'; 8 | 9 | part 'settings_event.dart'; 10 | part 'settings_state.dart'; 11 | 12 | class SettingsBloc extends Bloc { 13 | final SettingsRepository _settingsRepository; 14 | SettingsBloc(this._settingsRepository) : super(SettingsInitial()); 15 | 16 | @override 17 | Stream mapEventToState( 18 | SettingsEvent event, 19 | ) async* { 20 | if (event is CheckSettingsEvent) { 21 | bool settingsStatus = await _settingsRepository.checkValidJSON(); 22 | if (settingsStatus) { 23 | add(GetSettingsEvent()); 24 | } else { 25 | yield SettingsErrorState("Couldn't parse json file"); 26 | } 27 | } else if (event is GetSettingsEvent) { 28 | try { 29 | final _settings = await _settingsRepository.getSettings(); 30 | yield CurrentSettingsState(_settings); 31 | } catch (e) { 32 | yield SettingsErrorState('Error getting settings.'); 33 | } 34 | } else if (event is ResetSettingsEvent) { 35 | await _settingsRepository.resetSettings(); 36 | final _settings = await _settingsRepository.getSettings(); 37 | 38 | yield CurrentSettingsState(_settings); 39 | } else if (event is SettingsUpdatedEvent) { 40 | yield CurrentSettingsState(event.settingsModel); 41 | add(SaveSettingsEvent(event.settingsModel)); 42 | // improve 43 | } else if (event is SaveSettingsEvent) { 44 | if (await _settingsRepository.checkValidJSON()) { 45 | try { 46 | await _settingsRepository.saveSettings(event.settingsModel); 47 | final _settings = await _settingsRepository.getSettings(); 48 | yield CurrentSettingsState(_settings); 49 | } catch (e) { 50 | yield SettingsErrorState('Error saving settings.'); 51 | } 52 | } else { 53 | // only possible when app is open and use manually messes up config.json. 54 | yield SettingsErrorState('Corrupted config.json detected, rewriting.'); 55 | final _settings = await _settingsRepository.getSettings(); 56 | yield CurrentSettingsState(_settings); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/bloc/settings_bloc/settings_event.dart: -------------------------------------------------------------------------------- 1 | part of 'settings_bloc.dart'; 2 | 3 | abstract class SettingsEvent extends Equatable { 4 | const SettingsEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | /// This is just to show UI updates before applying settings. 11 | /// An alternative to this would be using setState but fuck you setState 12 | class SettingsUpdatedEvent extends SettingsEvent { 13 | final SettingsModel settingsModel; 14 | 15 | SettingsUpdatedEvent(this.settingsModel); 16 | @override 17 | List get props => [settingsModel]; 18 | } 19 | 20 | class ResetSettingsEvent extends SettingsEvent { 21 | @override 22 | List get props => []; 23 | } 24 | 25 | class GetSettingsEvent extends SettingsEvent { 26 | @override 27 | List get props => []; 28 | } 29 | 30 | class SaveSettingsEvent extends SettingsEvent { 31 | final SettingsModel settingsModel; 32 | 33 | SaveSettingsEvent(this.settingsModel); 34 | @override 35 | List get props => [settingsModel]; 36 | } 37 | 38 | class CheckSettingsEvent extends SettingsEvent { 39 | @override 40 | List get props => []; 41 | } 42 | -------------------------------------------------------------------------------- /lib/bloc/settings_bloc/settings_state.dart: -------------------------------------------------------------------------------- 1 | part of 'settings_bloc.dart'; 2 | 3 | //TODO: CurrentSettingsState sometimes makes the code messy, 4 | //try to change it a single class state like other bloc states 5 | abstract class SettingsState extends Equatable { 6 | const SettingsState(); 7 | 8 | @override 9 | List get props => []; 10 | } 11 | 12 | class SettingsInitial extends SettingsState {} 13 | 14 | class CurrentSettingsState extends SettingsState { 15 | final SettingsModel settingsModel; 16 | CurrentSettingsState(this.settingsModel); 17 | @override 18 | List get props => [settingsModel]; 19 | } 20 | 21 | class SettingsErrorState extends SettingsState { 22 | final String message; 23 | 24 | SettingsErrorState(this.message); 25 | @override 26 | List get props => [message]; 27 | } 28 | -------------------------------------------------------------------------------- /lib/bloc/updater_bloc/updater_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:bloc/bloc.dart'; 5 | import 'package:equatable/equatable.dart'; 6 | import 'package:http/http.dart' as http; 7 | import 'package:url_launcher/url_launcher.dart'; 8 | 9 | part 'updater_event.dart'; 10 | part 'updater_state.dart'; 11 | 12 | class UpdaterBloc extends Bloc { 13 | UpdaterBloc() : super(UpdaterState('0.0', '0.0', false, '', '')); 14 | static const URL = 15 | 'https://api.github.com/repos/CCExtractor/ccextractor/releases'; 16 | @override 17 | Stream mapEventToState( 18 | UpdaterEvent event, 19 | ) async* { 20 | if (event is CheckForUpdates) { 21 | var url = Uri.parse(URL); 22 | String changelog = ''; 23 | int currentVersionIndex = 0; 24 | http.Response response = await http.get(url); 25 | var data = jsonDecode(response.body); 26 | for (var i = 0; i < data.length; i++) { 27 | if (event.currentVersion == 28 | data[i]['tag_name'].toString().substring(1)) { 29 | currentVersionIndex = i; 30 | } 31 | } 32 | for (var i = 0; i < currentVersionIndex; i++) { 33 | changelog += '\n' + data[i]['body'].toString(); 34 | } 35 | String latestVersion = data[0]['tag_name'].toString().substring(1); 36 | String downloadURL = 37 | data[0]['assets'][0]['browser_download_url'].toString(); 38 | 39 | bool updateAvailable = 40 | double.parse(latestVersion) > double.parse(event.currentVersion); 41 | yield state.copyWith( 42 | currentVersion: event.currentVersion, 43 | latestVersion: latestVersion, 44 | updateAvailable: updateAvailable, 45 | downloadURL: downloadURL, 46 | changelog: changelog, 47 | ); 48 | } 49 | if (event is DownloadUpdate) { 50 | await launch(event.downloadURl); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/bloc/updater_bloc/updater_event.dart: -------------------------------------------------------------------------------- 1 | part of 'updater_bloc.dart'; 2 | 3 | abstract class UpdaterEvent extends Equatable { 4 | const UpdaterEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class CheckForUpdates extends UpdaterEvent { 11 | final String currentVersion; 12 | 13 | CheckForUpdates(this.currentVersion); 14 | 15 | @override 16 | List get props => [currentVersion]; 17 | } 18 | 19 | class DownloadUpdate extends UpdaterEvent { 20 | final String downloadURl; 21 | 22 | DownloadUpdate(this.downloadURl); 23 | @override 24 | List get props => [downloadURl]; 25 | } 26 | -------------------------------------------------------------------------------- /lib/bloc/updater_bloc/updater_state.dart: -------------------------------------------------------------------------------- 1 | part of 'updater_bloc.dart'; 2 | 3 | class UpdaterState { 4 | final String currentVersion; 5 | final String latestVersion; 6 | final bool updateAvailable; 7 | final String changelog; 8 | final String downloadURL; 9 | UpdaterState(this.currentVersion, this.latestVersion, this.updateAvailable, 10 | this.changelog, this.downloadURL); 11 | 12 | UpdaterState copyWith({ 13 | String? currentVersion, 14 | String? latestVersion, 15 | bool? updateAvailable, 16 | String? changelog, 17 | String? downloadURL, 18 | }) { 19 | return UpdaterState( 20 | currentVersion ?? this.currentVersion, 21 | latestVersion ?? this.latestVersion, 22 | updateAvailable ?? this.updateAvailable, 23 | changelog ?? this.changelog, 24 | downloadURL ?? this.downloadURL, 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/bloc_observer.dart: -------------------------------------------------------------------------------- 1 | // Logs events and states during transition, needs some improvement to reduce spam 2 | // @override 3 | // String toString() => 'Event { prop: prop }'; 4 | 5 | import 'package:bloc/bloc.dart'; 6 | 7 | class SimpleBlocObserver extends BlocObserver { 8 | @override 9 | void onCreate(BlocBase bloc) { 10 | super.onCreate(bloc); 11 | print('onCreate -- ${bloc.runtimeType}'); 12 | } 13 | 14 | // @override 15 | // void onEvent(Bloc bloc, Object? event) { 16 | // super.onEvent(bloc, event); 17 | 18 | // if ({ProcessFileExtractorOutput, ProcessFileExtractorProgress} 19 | // .contains(event)) print('onEvent -- ${bloc.runtimeType}, $event'); 20 | // } 21 | 22 | // @override 23 | // void onTransition(Bloc bloc, Transition transition) { 24 | // // CurrentSettingsState and ProcessState have a lot of spam 25 | // if ({transition.nextState, transition.nextState} 26 | // .contains(CurrentSettingsState) || 27 | // {transition.nextState, transition.nextState}.contains(ProcessState)) { 28 | // print(transition); 29 | // } 30 | // super.onTransition(bloc, transition); 31 | // } 32 | 33 | @override 34 | void onError(BlocBase bloc, Object error, StackTrace stackTrace) { 35 | print('${bloc.runtimeType} $error $stackTrace'); 36 | super.onError(bloc, error, stackTrace); 37 | } 38 | 39 | @override 40 | void onClose(BlocBase bloc) { 41 | super.onClose(bloc); 42 | print('onClose -- ${bloc.runtimeType}'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | import 'package:window_size/window_size.dart'; 7 | 8 | import 'package:ccxgui/bloc/dashboard_bloc/dashboard_bloc.dart'; 9 | import 'package:ccxgui/bloc/process_bloc/process_bloc.dart'; 10 | import 'package:ccxgui/bloc/settings_bloc/settings_bloc.dart'; 11 | import 'package:ccxgui/bloc/updater_bloc/updater_bloc.dart'; 12 | import 'package:ccxgui/repositories/settings_repository.dart'; 13 | import 'package:ccxgui/screens/home.dart'; 14 | import 'package:ccxgui/utils/constants.dart'; 15 | import 'bloc_observer.dart'; 16 | 17 | void main() async { 18 | WidgetsFlutterBinding.ensureInitialized(); 19 | Bloc.observer = SimpleBlocObserver(); 20 | if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { 21 | setWindowTitle('CCExtractor'); 22 | setWindowMinSize(const Size(800, 800)); 23 | setWindowMaxSize(const Size(10000, 10000)); 24 | } 25 | runApp( 26 | MyApp(), 27 | ); 28 | } 29 | 30 | class MyApp extends StatelessWidget { 31 | final ThemeData theme = ThemeData( 32 | colorScheme: ColorScheme.dark(background: kBgDarkColor), 33 | canvasColor: kBgDarkColor, 34 | brightness: Brightness.dark, 35 | visualDensity: VisualDensity.adaptivePlatformDensity, 36 | ); 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return MultiBlocProvider( 41 | providers: [ 42 | BlocProvider( 43 | create: (context) => DashboardBloc(), 44 | ), 45 | BlocProvider( 46 | create: (context) => ProcessBloc()..add(GetCCExtractorVersion()), 47 | ), 48 | BlocProvider( 49 | create: (context) => 50 | SettingsBloc(SettingsRepository())..add(CheckSettingsEvent()), 51 | ), 52 | BlocProvider( 53 | create: (context) => UpdaterBloc(), 54 | ), 55 | ], 56 | child: MaterialApp( 57 | theme: theme.copyWith( 58 | colorScheme: theme.colorScheme.copyWith( 59 | secondary: Color(0xff01bcd6), 60 | ), 61 | ), 62 | home: Home(), 63 | ), 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/repositories/ccextractor.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:file_selector/file_selector.dart'; 5 | 6 | import 'package:ccxgui/models/settings_model.dart'; 7 | import 'package:ccxgui/repositories/settings_repository.dart'; 8 | 9 | class CCExtractor { 10 | late Process process; 11 | final RegExp progressRegx = RegExp(r'###PROGRESS#(\d+)', multiLine: true); 12 | final RegExp summaryRegx = RegExp(r'\[(.*?)\]', multiLine: true); 13 | final RegExp logsRegx = 14 | RegExp(r'###SUBTITLE###(.+)|###TIME###(.+)', multiLine: true); 15 | final RegExp videoDetailsRegx = RegExp(r'###VIDEOINFO#(.+)', multiLine: true); 16 | 17 | SettingsRepository settingsRepository = SettingsRepository(); 18 | SettingsModel settings = SettingsModel(); 19 | String get ccextractor { 20 | return Platform.isWindows ? './ccextractorwinfull.exe' : 'ccextractor'; 21 | } 22 | 23 | Future extractFile( 24 | XFile file, { 25 | required Function(String) listenProgress, 26 | required Function(String) listenOutput, 27 | required Function(List) listenVideoDetails, 28 | }) async { 29 | settings = await settingsRepository.getSettings(); 30 | List paramsList = 31 | settingsRepository.getParamsList(settings, filePath: file.path); 32 | process = await Process.start( 33 | ccextractor, 34 | [ 35 | file.path, 36 | '--gui_mode_reports', 37 | ...paramsList, 38 | ], 39 | ); 40 | // sometimes stdout and stderr have important logs like how much time 41 | // it took to process the file or some erros not captured by exitcodes, 42 | // so just print them to the logs box 43 | process.stdout.transform(latin1.decoder).listen((update) { 44 | // print(update); 45 | listenOutput(update); 46 | }); 47 | 48 | process.stderr.transform(latin1.decoder).listen((update) { 49 | // print(update); 50 | if (progressRegx.hasMatch(update)) { 51 | for (RegExpMatch i in progressRegx.allMatches(update)) { 52 | listenProgress(i[1]!); 53 | } 54 | } 55 | if (logsRegx.hasMatch(update)) { 56 | for (RegExpMatch i in logsRegx.allMatches(update)) { 57 | // 1,2 are here for regex groups, 1 corresponds to subtitle regex 58 | // match and 2 is time regex match. Later we can seperate this if 59 | // needed (no additonal benefit rn imo;td) 60 | if (i[1] != null) listenOutput(i[1]!); 61 | if (i[2] != null) listenOutput(i[2]!); 62 | } 63 | } 64 | if (videoDetailsRegx.hasMatch(update)) { 65 | for (RegExpMatch i in videoDetailsRegx.allMatches(update)) { 66 | listenVideoDetails(i[1]!.split('#')); 67 | } 68 | } 69 | update.contains('Error') ? listenOutput(update) : null; 70 | }); 71 | return process.exitCode; 72 | } 73 | 74 | Future extractFileOverNetwork({ 75 | required String type, 76 | required String location, 77 | required String tcppasswrd, 78 | required String tcpdesc, 79 | required Function(String) listenProgress, 80 | required Function(String) listenOutput, 81 | required Function(List) listenVideoDetails, 82 | }) async { 83 | settings = await settingsRepository.getSettings(); 84 | List paramsList = settingsRepository.getParamsList(settings); 85 | process = await Process.start( 86 | ccextractor, 87 | [ 88 | '-' + type, 89 | location, 90 | tcppasswrd.isNotEmpty ? '-tcppasswrd' + tcppasswrd : '', 91 | tcpdesc.isNotEmpty ? '-tcpdesc' + tcpdesc : '', 92 | '--gui_mode_reports', 93 | ...paramsList, 94 | ], 95 | ); 96 | 97 | process.stdout.transform(latin1.decoder).listen((update) { 98 | // print(update); 99 | listenOutput(update); 100 | }); 101 | 102 | process.stderr.transform(latin1.decoder).listen((update) { 103 | if (progressRegx.hasMatch(update)) { 104 | for (RegExpMatch i in progressRegx.allMatches(update)) { 105 | listenProgress(i[1]!); 106 | } 107 | } 108 | if (logsRegx.hasMatch(update)) { 109 | for (RegExpMatch i in logsRegx.allMatches(update)) { 110 | // 1,2 are here for regex groups, 1 corresponds to subtitle regex 111 | // match and 2 is time regex match. Later we can seperate this if 112 | // needed (no additonal benefit rn imo;td) 113 | if (i[1] != null) listenOutput(i[1]!); 114 | if (i[2] != null) listenOutput(i[2]!); 115 | } 116 | } 117 | if (videoDetailsRegx.hasMatch(update)) { 118 | for (RegExpMatch i in videoDetailsRegx.allMatches(update)) { 119 | listenVideoDetails(i[1]!.split('#')); 120 | } 121 | } 122 | update.contains('Error') ? listenOutput(update) : null; 123 | }); 124 | return process.exitCode; 125 | } 126 | 127 | // List allFilesPath(List files) { 128 | // tempList = []; 129 | 130 | // } 131 | 132 | Future extractFilesInSplitMode( 133 | List files, { 134 | required Function(String) listenProgress, 135 | required Function(String) listenOutput, 136 | required Function(List) listenVideoDetails, 137 | }) async { 138 | settings = await settingsRepository.getSettings(); 139 | List paramsList = settingsRepository.getParamsList(settings); 140 | process = await Process.start( 141 | ccextractor, 142 | [ 143 | ...files.map((e) => e.path).toList(), 144 | '--gui_mode_reports', 145 | ...paramsList, 146 | ], 147 | ); 148 | // sometimes stdout and stderr have important logs like how much time 149 | // it took to process the file or some erros not captured by exitcodes, 150 | // so just print them to the logs box 151 | process.stdout.transform(latin1.decoder).listen((update) { 152 | // print(update); 153 | listenOutput(update); 154 | }); 155 | 156 | process.stderr.transform(latin1.decoder).listen((update) { 157 | // print(update); 158 | if (progressRegx.hasMatch(update)) { 159 | for (RegExpMatch i in progressRegx.allMatches(update)) { 160 | listenProgress(i[1]!); 161 | } 162 | } 163 | if (logsRegx.hasMatch(update)) { 164 | for (RegExpMatch i in logsRegx.allMatches(update)) { 165 | // 1,2 are here for regex groups, 1 corresponds to subtitle regex 166 | // match and 2 is time regex match. Later we can seperate this if 167 | // needed (no additonal benefit rn imo;td) 168 | if (i[1] != null) listenOutput(i[1]!); 169 | if (i[2] != null) listenOutput(i[2]!); 170 | } 171 | } 172 | if (videoDetailsRegx.hasMatch(update)) { 173 | for (RegExpMatch i in videoDetailsRegx.allMatches(update)) { 174 | listenVideoDetails(i[1]!.split('#')); 175 | } 176 | } 177 | update.contains('Error') ? listenOutput(update) : null; 178 | }); 179 | return process.exitCode; 180 | } 181 | 182 | /// Cancels the ongoing file process 183 | void cancelRun() { 184 | process.kill(); 185 | } 186 | 187 | Future get getCCExtractorVersion async { 188 | String ccxStdOut = '0'; 189 | await Process.run(ccextractor, ['--version']).then((value) { 190 | ccxStdOut = value.stdout 191 | .toString() 192 | .substring(value.stdout.toString().indexOf('Version:') + 8, 193 | value.stdout.toString().indexOf('Version:') + 15) 194 | .trim(); 195 | }); 196 | return ccxStdOut; 197 | } 198 | 199 | static Map exitCodes = { 200 | 0: 'EXIT_OK', 201 | 2: 'EXIT_NO_INPUT_FILES', 202 | 3: 'EXIT_TOO_MANY_INPUT_FILES', 203 | 4: 'EXIT_INCOMPATIBLE_PARAMETERS', 204 | 6: 'EXIT_UNABLE_TO_DETERMINE_FILE_SIZE', 205 | 7: 'EXIT_MALFORMED_PARAMETER', 206 | 8: 'EXIT_READ_ERROR', 207 | 10: 'EXIT_NO_CAPTIONS', 208 | 300: 'EXIT_NOT_CLASSIFIED', 209 | 501: 'EXIT_ERROR_IN_CAPITALIZATION_FILE', 210 | 502: 'EXIT_BUFFER_FULL', 211 | 1001: 'EXIT_MISSING_ASF_HEADER', 212 | 1002: 'EXIT_MISSING_RCWT_HEADER', 213 | 5: 'CCX_COMMON_EXIT_FILE_CREATION_FAILED', 214 | 9: 'CCX_COMMON_EXIT_UNSUPPORTED', 215 | 500: 'EXIT_NOT_ENOUGH_MEMORY', 216 | 1000: 'CCX_COMMON_EXIT_BUG_BUG', 217 | }; 218 | } 219 | -------------------------------------------------------------------------------- /lib/repositories/settings_repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:localstorage/localstorage.dart'; 5 | import 'package:path_provider/path_provider.dart'; 6 | 7 | import 'package:ccxgui/models/settings_model.dart'; 8 | import 'package:ccxgui/utils/constants.dart'; 9 | 10 | class SettingsRepository { 11 | // final SettingsModel settingsModel; 12 | 13 | // SettingsRepository(this.settingsModel); 14 | 15 | Future get _localPath async { 16 | final directory = await getApplicationDocumentsDirectory(); 17 | return directory.path; 18 | } 19 | 20 | Future get _localFile async { 21 | final path = await _localPath; 22 | return File('$path/config.json'); 23 | } 24 | 25 | // Remember to check if json is valid before storage.ready. 26 | Future checkValidJSON() async { 27 | final file = await _localFile; 28 | try { 29 | final String settings = await file.readAsString(); 30 | // This checks if the file is in a valid json format 31 | Map data = jsonDecode(settings); 32 | for (var item in SettingsModel().toJson().keys) { 33 | // This checks if all the keys from settings mode (put in a seperate list in contants file), exists or not. 34 | // If not, exception is thrown and default settings are written. 35 | if (!data.containsKey(item)) { 36 | throw Exception('Setting key not found'); 37 | } 38 | } 39 | // This checks if all the values are of the intended datatype, by setting the current data to a settingsModel object 40 | try { 41 | // ignore: unused_local_variable 42 | final _settingsModel = SettingsModel.fromJson(data); 43 | } catch (e) { 44 | throw Exception('Settings value has mismatched datatype. Error $e'); 45 | } 46 | return true; 47 | } catch (e) { 48 | print('Rewriting config.json file. Error $e'); 49 | await file.writeAsString(jsonEncode(SettingsModel())); 50 | return false; 51 | } 52 | } 53 | 54 | List getParamsList(SettingsModel settings, {String filePath = ''}) { 55 | List paramsList = []; 56 | 57 | paramsList.addAll( 58 | settings.enabledSettings 59 | .map((param) => SettingsModel.paramsLookUpMap[param]!), 60 | ); 61 | 62 | settings.enabledtextfields.forEach((param) { 63 | if (!{'encoder', 'rollUp', 'out', 'inp'}.contains(param.keys.first)) { 64 | // no --encoder direct -latin1 or -utf8 65 | // -out=format // no space 66 | paramsList.add(SettingsModel.paramsLookUpMap[param.keys.first]!); 67 | } 68 | if (param.keys.first == 'outputfilename' && filePath.isNotEmpty) { 69 | paramsList.add( 70 | '${filePath.substring(0, filePath.lastIndexOf(RegExp(r'(\\|\/)')))}/${param.values.first}'); 71 | } else if ({'encoder', 'rollUp'}.contains(param.keys.first)) { 72 | paramsList.add('-' + param.values.first); 73 | } else if (param.keys.first == 'out' || param.keys.first == 'inp') { 74 | paramsList.add(SettingsModel.paramsLookUpMap[param.keys.first]! + 75 | param.values.first); 76 | } else if (dropdownListMap.keys.contains(param.keys.first)) { 77 | /// this part handles the dropdown menus, ccx takes in arg xmltv 3 , 78 | /// which is the same as "Both" option in the GUI, if the enabled setting 79 | /// is in dropdownListMap we search for the particular setting, xmltv in 80 | /// this case, then we get a map with the corresponding settings and the 81 | /// int ccx takes for eg: "Both": 3, then we pass this value to paramsList 82 | /// auto/default can take in anyvalue because that is filtered out during 83 | /// [settings.enabledtextfields] 84 | paramsList.add( 85 | dropdownListMap[param.keys.first]![param.values.first].toString()); 86 | } else { 87 | paramsList.add(param.values.first); 88 | } 89 | }); 90 | // print(paramsList); 91 | // [--append, -autoprogram, -out=webvtt, -utf8] 92 | return paramsList; 93 | } 94 | 95 | Future getSettings() async { 96 | SettingsModel _settings = SettingsModel(); 97 | try { 98 | LocalStorage storage = LocalStorage('config.json'); 99 | await storage.ready; 100 | _settings = SettingsModel.fromJson(storage.getData()); 101 | } catch (e) { 102 | print('GetSettings Error $e'); 103 | } 104 | return _settings; 105 | } 106 | 107 | Future resetSettings() async { 108 | final file = await _localFile; 109 | LocalStorage storage = LocalStorage('config.json'); 110 | await storage.ready; 111 | await storage.clear(); // just works 112 | await file.writeAsString(jsonEncode(SettingsModel())); 113 | } 114 | 115 | Future saveSettings(SettingsModel settingsModel) async { 116 | try { 117 | LocalStorage storage = LocalStorage('config.json'); 118 | await storage.ready; 119 | await storage.setData(settingsModel.toJson()); 120 | } catch (e) { 121 | print('Error saving settings $e'); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /lib/screens/dashboard/components/add_files.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | 6 | import 'package:ccxgui/bloc/dashboard_bloc/dashboard_bloc.dart'; 7 | import 'package:ccxgui/bloc/process_bloc/process_bloc.dart'; 8 | import 'package:ccxgui/bloc/settings_bloc/settings_bloc.dart'; 9 | import 'package:ccxgui/screens/dashboard/components/custom_snackbar.dart'; 10 | import 'package:ccxgui/utils/constants.dart'; 11 | 12 | class AddFilesButton extends StatelessWidget { 13 | void _openVideosFileSelector(BuildContext context) async { 14 | final List files = await FileSelectorPlatform.instance.openFiles( 15 | acceptedTypeGroups: [ 16 | XTypeGroup( 17 | label: 'Video', 18 | extensions: [ 19 | 'dvr-ms', 20 | 'm2v', 21 | 'mpg', 22 | 'ts', 23 | 'wtv', 24 | 'mp4', 25 | 'mpg2', 26 | 'vob', 27 | 'mkv', 28 | 'mxf', 29 | ], 30 | mimeTypes: ['video/*'], 31 | ), 32 | ], 33 | ); 34 | if (files.isEmpty) { 35 | // Operation was canceled by the user. 36 | return; 37 | } 38 | context.read().add( 39 | NewFileAdded(files), 40 | ); 41 | context.read().add( 42 | ProcessFilesSubmitted(files), 43 | ); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | return BlocBuilder( 49 | builder: (context, processState) { 50 | return BlocBuilder( 51 | builder: (context, settingsState) { 52 | if (settingsState is CurrentSettingsState) { 53 | return Padding( 54 | padding: const EdgeInsets.only(top: 20, left: 20, right: 20), 55 | child: InkWell( 56 | hoverColor: Colors.transparent, 57 | onTap: () => (settingsState.settingsModel.splitMode && 58 | processState.started) 59 | ? CustomSnackBarMessage.show(context, 60 | "Can't add files when splitMode is enabled and there is a ongoing process") 61 | : _openVideosFileSelector(context), 62 | child: Container( 63 | decoration: BoxDecoration( 64 | color: kBgLightColor, 65 | borderRadius: BorderRadius.all( 66 | Radius.circular( 67 | 10, 68 | ), 69 | ), 70 | ), 71 | height: 75, 72 | child: Center( 73 | child: Text( 74 | 'Add more files', 75 | style: TextStyle(fontSize: 20), 76 | ), 77 | ), 78 | ), 79 | ), 80 | ); 81 | } 82 | return Padding( 83 | padding: const EdgeInsets.only(top: 20, left: 20, right: 20), 84 | child: InkWell( 85 | child: Container( 86 | decoration: BoxDecoration( 87 | color: kBgLightColor, 88 | borderRadius: BorderRadius.all( 89 | Radius.circular( 90 | 10, 91 | ), 92 | ), 93 | ), 94 | height: 75, 95 | child: Center( 96 | child: Text( 97 | 'Loading...', 98 | style: TextStyle(fontSize: 20), 99 | ), 100 | ), 101 | ), 102 | ), 103 | ); 104 | }, 105 | ); 106 | }, 107 | ); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lib/screens/dashboard/components/custom_snackbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomSnackBarMessage { 4 | final String message; 5 | 6 | const CustomSnackBarMessage({ 7 | required this.message, 8 | }); 9 | 10 | // ignore: always_declare_return_types 11 | static show( 12 | BuildContext context, 13 | String message, 14 | ) { 15 | ScaffoldMessenger.of(context).showSnackBar( 16 | SnackBar( 17 | duration: Duration(seconds: 1), 18 | content: Text(message), 19 | margin: EdgeInsets.only( 20 | left: MediaQuery.of(context).size.width / 1.5, 21 | bottom: 20, 22 | right: 20, 23 | ), 24 | behavior: SnackBarBehavior.floating, 25 | backgroundColor: Theme.of(context).colorScheme.secondary, 26 | shape: RoundedRectangleBorder( 27 | borderRadius: BorderRadius.circular(10.0), 28 | ), 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/screens/dashboard/components/multi_process_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:expandable/expandable.dart'; 4 | import 'package:file_selector/file_selector.dart'; 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | 7 | import 'package:ccxgui/bloc/process_bloc/process_bloc.dart'; 8 | 9 | class MultiProcessTile extends StatefulWidget { 10 | final List files; 11 | const MultiProcessTile({ 12 | Key? key, 13 | required this.files, 14 | }) : super(key: key); 15 | @override 16 | _MultiProcessTileState createState() => _MultiProcessTileState(); 17 | } 18 | 19 | class _MultiProcessTileState extends State { 20 | bool isVisible = false; 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return Padding( 25 | padding: const EdgeInsets.only(left: 20, right: 20), 26 | child: ExpandablePanel( 27 | theme: ExpandableThemeData( 28 | tapHeaderToExpand: true, 29 | hasIcon: true, 30 | iconColor: Colors.white, 31 | tapBodyToExpand: true, 32 | tapBodyToCollapse: true, 33 | iconPadding: EdgeInsets.only(top: 20, left: 20), 34 | ), 35 | header: BlocBuilder( 36 | builder: (context, state) { 37 | return Padding( 38 | padding: EdgeInsets.only(top: 20, bottom: 10), 39 | child: Row( 40 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 41 | children: [ 42 | Text( 43 | 'Processing ${widget.files.length} files in split mode.', 44 | style: TextStyle(fontSize: 15), 45 | ), 46 | state.started && state.progress != '100' 47 | ? Container( 48 | height: 20, 49 | width: 20, 50 | child: CircularProgressIndicator( 51 | color: Colors.green, 52 | strokeWidth: 4, 53 | value: int.parse(state.progress) / 100, 54 | backgroundColor: Colors.white, 55 | ), 56 | ) 57 | : state.progress == '100' 58 | ? Icon(Icons.check) 59 | : Container(), 60 | ], 61 | ), 62 | ); 63 | }, 64 | ), 65 | collapsed: Text( 66 | 'Click to expand', 67 | style: TextStyle(color: Colors.grey, fontSize: 13), 68 | ), 69 | expanded: ListView.builder( 70 | shrinkWrap: true, 71 | itemCount: widget.files.length, 72 | itemBuilder: (context, index) { 73 | return Container( 74 | child: Text( 75 | widget.files[index].name, 76 | style: TextStyle(color: Colors.grey, fontSize: 13), 77 | ), 78 | ); 79 | }), 80 | ), 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/screens/dashboard/components/process_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:file_selector/file_selector.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | 6 | import 'package:ccxgui/bloc/dashboard_bloc/dashboard_bloc.dart'; 7 | import 'package:ccxgui/bloc/process_bloc/process_bloc.dart'; 8 | 9 | class ProcessTile extends StatefulWidget { 10 | final XFile file; 11 | const ProcessTile({ 12 | Key? key, 13 | required this.file, 14 | }) : super(key: key); 15 | @override 16 | _ProcessTileState createState() => _ProcessTileState(); 17 | } 18 | 19 | class _ProcessTileState extends State { 20 | bool isVisible = false; 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return Padding( 25 | padding: const EdgeInsets.only(left: 20, right: 20), 26 | child: Container( 27 | height: 90, 28 | child: Row( 29 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 30 | children: [ 31 | Expanded( 32 | child: ListView( 33 | scrollDirection: Axis.horizontal, 34 | padding: EdgeInsets.only(top: 20, bottom: 20), 35 | children: [ 36 | Column( 37 | crossAxisAlignment: CrossAxisAlignment.start, 38 | children: [ 39 | Text( 40 | widget.file.name, 41 | ), 42 | SizedBox( 43 | height: 12, 44 | ), 45 | Text( 46 | widget.file.path, 47 | style: TextStyle(color: Colors.grey, fontSize: 12), 48 | ), 49 | ], 50 | ), 51 | ], 52 | ), 53 | ), 54 | Row( 55 | children: [ 56 | Padding( 57 | padding: const EdgeInsets.fromLTRB(20, 10, 0, 10), 58 | child: BlocBuilder( 59 | builder: (context, state) { 60 | if (state.processed.contains(widget.file)) { 61 | return Padding( 62 | padding: const EdgeInsets.all(18.0), 63 | child: Icon(Icons.check), 64 | ); 65 | } else if (widget.file == state.current) { 66 | return Row( 67 | children: [ 68 | Padding( 69 | padding: const EdgeInsets.all(18.0), 70 | child: Container( 71 | height: 20, 72 | width: 20, 73 | child: CircularProgressIndicator( 74 | color: Colors.green, 75 | strokeWidth: 4, 76 | value: int.parse(state.progress) / 100, 77 | backgroundColor: Colors.white, 78 | ), 79 | ), 80 | ), 81 | IconButton( 82 | onPressed: () { 83 | context.read().add( 84 | FileRemoved(widget.file), 85 | ); 86 | try { 87 | context.read().add( 88 | ProcessKill(widget.file), 89 | ); 90 | } catch (e) { 91 | print( 92 | 'processing for this file never started'); 93 | } 94 | }, 95 | icon: Icon( 96 | Icons.delete_outline, 97 | color: Colors.red, 98 | ), 99 | ), 100 | ], 101 | ); 102 | } 103 | return Padding( 104 | padding: const EdgeInsets.all(8.0), 105 | child: IconButton( 106 | onPressed: () { 107 | context.read().add( 108 | FileRemoved(widget.file), 109 | ); 110 | try { 111 | context.read().add( 112 | ProcessFileRemoved(widget.file), 113 | ); 114 | } catch (e) { 115 | print('processing for this file never started'); 116 | } 117 | }, 118 | icon: Icon( 119 | Icons.delete_outline, 120 | color: Colors.red, 121 | ), 122 | ), 123 | ); 124 | }, 125 | ), 126 | ), 127 | ], 128 | ), 129 | ], 130 | ), 131 | ), 132 | ); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /lib/screens/dashboard/components/start_stop_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | 5 | import 'package:ccxgui/bloc/dashboard_bloc/dashboard_bloc.dart'; 6 | import 'package:ccxgui/bloc/process_bloc/process_bloc.dart'; 7 | import 'package:ccxgui/bloc/settings_bloc/settings_bloc.dart'; 8 | import 'package:ccxgui/screens/dashboard/components/custom_snackbar.dart'; 9 | 10 | //TODO: this file can probably be improved 11 | class StartStopButton extends StatelessWidget { 12 | @override 13 | Widget build(BuildContext context) { 14 | return BlocBuilder( 15 | builder: (context, processState) { 16 | return BlocBuilder( 17 | builder: (context, dashboardState) { 18 | return BlocBuilder( 19 | builder: (context, settingsState) { 20 | if (settingsState is CurrentSettingsState) { 21 | return MaterialButton( 22 | onPressed: () { 23 | dashboardState.files.isEmpty 24 | ? null 25 | : processState.started 26 | ? { 27 | context.read().add( 28 | StopAllProcess(), 29 | ), 30 | CustomSnackBarMessage.show( 31 | context, 32 | 'Cancelled process on all files.', 33 | ) 34 | } 35 | : { 36 | settingsState.settingsModel.splitMode && 37 | dashboardState.files.length > 1 38 | ? context.read().add( 39 | StartProcessInSplitMode(), 40 | ) 41 | : context.read().add( 42 | StartAllProcess(), 43 | ), 44 | CustomSnackBarMessage.show( 45 | context, 46 | 'Process started.', 47 | ) 48 | }; 49 | }, 50 | child: Padding( 51 | padding: const EdgeInsets.all(8.0), 52 | child: Row( 53 | children: [ 54 | Text( 55 | processState.started ? 'Stop all' : 'Start all', 56 | style: TextStyle( 57 | fontSize: 20, 58 | ), 59 | ), 60 | SizedBox( 61 | width: 5, 62 | ), 63 | processState.started 64 | ? Icon(Icons.stop, 65 | color: Colors.redAccent, size: 30) 66 | : Icon( 67 | Icons.play_arrow, 68 | color: dashboardState.files.isEmpty 69 | ? Colors.grey 70 | : Colors.greenAccent, 71 | size: 30, 72 | ), 73 | ], 74 | ), 75 | ), 76 | ); 77 | } 78 | return Padding( 79 | padding: const EdgeInsets.all(8.0), 80 | child: Row( 81 | children: [ 82 | Text( 83 | processState.started ? 'Stop all' : 'Start all', 84 | style: TextStyle( 85 | fontSize: 20, 86 | ), 87 | ), 88 | SizedBox( 89 | width: 5, 90 | ), 91 | processState.started 92 | ? Icon(Icons.stop, color: Colors.redAccent, size: 30) 93 | : Icon( 94 | Icons.play_arrow, 95 | color: dashboardState.files.isEmpty 96 | ? Colors.grey 97 | : Colors.greenAccent, 98 | size: 30, 99 | ), 100 | ], 101 | ), 102 | ); 103 | }, 104 | ); 105 | }, 106 | ); 107 | }, 108 | ); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /lib/screens/dashboard/components/udp_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | 6 | import 'package:ccxgui/bloc/process_bloc/process_bloc.dart'; 7 | import 'package:ccxgui/utils/constants.dart'; 8 | 9 | class ListenOnUDPButton extends StatefulWidget { 10 | @override 11 | _ListenOnUDPButtonState createState() => _ListenOnUDPButtonState(); 12 | } 13 | 14 | class _ListenOnUDPButtonState extends State { 15 | final List> networkTypes = [ 16 | DropdownMenuItem( 17 | value: 'udp', 18 | child: Text('udp'), 19 | ), 20 | DropdownMenuItem( 21 | value: 'tcp', 22 | child: Text('tcp'), 23 | ), 24 | ]; 25 | String type = 'udp'; 26 | final TextEditingController hostController = 27 | TextEditingController(text: '127.0.0.1'); 28 | final TextEditingController portController = TextEditingController(); 29 | final TextEditingController passwordController = TextEditingController(); 30 | final TextEditingController descController = TextEditingController(); 31 | 32 | String get compileLocationString { 33 | String _host = 34 | hostController.text.isNotEmpty ? hostController.text + ':' : ''; 35 | return _host + portController.text; 36 | } 37 | 38 | final _formKey = GlobalKey(); 39 | @override 40 | Widget build(BuildContext context) { 41 | return Padding( 42 | padding: const EdgeInsets.only(top: 20, right: 20), 43 | child: InkWell( 44 | hoverColor: Colors.transparent, 45 | onTap: () => showDialog( 46 | context: context, 47 | builder: (context) { 48 | return AlertDialog( 49 | title: Text( 50 | 'Read the input on network (listening in the specified port)'), 51 | content: StatefulBuilder( 52 | builder: (BuildContext context, StateSetter setState) { 53 | return Padding( 54 | padding: const EdgeInsets.all(8.0), 55 | child: Form( 56 | key: _formKey, 57 | child: Column( 58 | mainAxisSize: MainAxisSize.min, 59 | crossAxisAlignment: CrossAxisAlignment.start, 60 | children: [ 61 | DropdownButton( 62 | items: networkTypes, 63 | isExpanded: true, 64 | value: type, 65 | onChanged: (String? newValue) { 66 | setState(() { 67 | type = newValue!; 68 | }); 69 | }, 70 | ), 71 | TextFormField( 72 | controller: hostController, 73 | decoration: InputDecoration( 74 | hintText: 'Enter host here', 75 | labelText: 'Host: ', 76 | labelStyle: TextStyle(fontSize: 16), 77 | hintStyle: TextStyle(height: 2), 78 | ), 79 | ), 80 | TextFormField( 81 | controller: portController, 82 | inputFormatters: [ 83 | FilteringTextInputFormatter.allow( 84 | RegExp(r'^\d+(\.\d+)*$'), 85 | ), 86 | ], 87 | autofocus: true, 88 | validator: (text) { 89 | if (text == null || text.isEmpty) { 90 | return 'Port cannot be empty'; 91 | } 92 | return null; 93 | }, 94 | decoration: InputDecoration( 95 | hintText: 'Enter port here', 96 | labelText: 'Port: ', 97 | labelStyle: TextStyle(fontSize: 16), 98 | hintStyle: TextStyle(height: 2), 99 | ), 100 | ), 101 | if (type == 'tcp') 102 | TextFormField( 103 | controller: passwordController, 104 | decoration: InputDecoration( 105 | hintText: 106 | 'Server password for new connections to tcp server', 107 | labelText: 'TCP password: ', 108 | labelStyle: TextStyle(fontSize: 16), 109 | hintStyle: TextStyle(height: 2), 110 | ), 111 | ), 112 | if (type == 'tcp') 113 | TextFormField( 114 | controller: descController, 115 | decoration: InputDecoration( 116 | hintText: 117 | 'Sends to the server short description about captiobs', 118 | labelText: 'TCP description: ', 119 | labelStyle: TextStyle(fontSize: 16), 120 | hintStyle: TextStyle(height: 2), 121 | ), 122 | ), 123 | ], 124 | ), 125 | ), 126 | ); 127 | }, 128 | ), 129 | actions: [ 130 | Padding( 131 | padding: const EdgeInsets.only(bottom: 16), 132 | child: MaterialButton( 133 | onPressed: () { 134 | Navigator.pop(context); 135 | }, 136 | child: Text( 137 | 'Cancel', 138 | style: TextStyle(fontSize: 15), 139 | ), 140 | ), 141 | ), 142 | Padding( 143 | padding: const EdgeInsets.only(bottom: 16, right: 12), 144 | child: MaterialButton( 145 | onPressed: () { 146 | if (_formKey.currentState!.validate()) { 147 | context.read().add( 148 | ProcessOnNetwork( 149 | type: type, 150 | location: compileLocationString, 151 | tcpdesc: descController.text, 152 | tcppassword: passwordController.text, 153 | ), 154 | ); 155 | Navigator.pop(context); 156 | } 157 | }, 158 | child: Text( 159 | 'Start', 160 | style: TextStyle(fontSize: 15), 161 | ), 162 | ), 163 | ), 164 | ], 165 | ); 166 | }, 167 | ), 168 | child: Container( 169 | decoration: BoxDecoration( 170 | color: kBgLightColor, 171 | borderRadius: BorderRadius.all( 172 | Radius.circular( 173 | 10, 174 | ), 175 | ), 176 | ), 177 | height: 75, 178 | child: Center( 179 | child: Text( 180 | 'Listen on UDP', 181 | style: TextStyle(fontSize: 20), 182 | ), 183 | ), 184 | ), 185 | ), 186 | ); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /lib/screens/home.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | import 'package:flutter_markdown/flutter_markdown.dart'; 7 | import 'package:flutter_svg/flutter_svg.dart'; 8 | import 'package:navigation_rail/navigation_rail.dart'; 9 | 10 | import 'package:ccxgui/bloc/process_bloc/process_bloc.dart'; 11 | import 'package:ccxgui/bloc/updater_bloc/updater_bloc.dart'; 12 | import 'package:ccxgui/screens/dashboard/components/custom_snackbar.dart'; 13 | import 'package:ccxgui/screens/dashboard/dashboard.dart'; 14 | import 'package:ccxgui/screens/settings/basic_settings.dart'; 15 | import 'package:ccxgui/screens/settings/hardsubx_settings.dart'; 16 | import 'package:ccxgui/screens/settings/input_settings.dart'; 17 | import 'package:ccxgui/screens/settings/obscure_settings.dart'; 18 | import 'package:ccxgui/screens/settings/output_settings.dart'; 19 | import 'package:ccxgui/utils/constants.dart'; 20 | 21 | class Home extends StatefulWidget { 22 | @override 23 | _HomeState createState() => _HomeState(); 24 | } 25 | 26 | class _HomeState extends State { 27 | final navigatorKey = GlobalKey(); 28 | int _currentIndex = 0; 29 | final String logo = 'assets/ccx.svg'; 30 | final ScrollController scrollController = ScrollController(); 31 | @override 32 | Widget build(BuildContext context) { 33 | return BlocListener( 34 | listener: (context, state) { 35 | if (state.updateAvailable) { 36 | showDialog( 37 | context: context, 38 | builder: (context) { 39 | return AlertDialog( 40 | title: Text('Update available\n\nChangelog:'), 41 | content: Container( 42 | width: 800, 43 | child: Markdown( 44 | controller: scrollController, 45 | selectable: true, 46 | shrinkWrap: true, 47 | data: state.changelog), 48 | ), 49 | actions: [ 50 | Padding( 51 | padding: const EdgeInsets.all(8.0), 52 | child: TextButton( 53 | onPressed: () => Navigator.pop(context), 54 | child: const Text('Cancel'), 55 | ), 56 | ), 57 | Padding( 58 | padding: const EdgeInsets.all(8.0), 59 | child: TextButton( 60 | onPressed: () { 61 | Navigator.pop(context); 62 | context 63 | .read() 64 | .add(DownloadUpdate(state.downloadURL)); 65 | }, 66 | child: const Text('Download'), 67 | ), 68 | ), 69 | ], 70 | ); 71 | }, 72 | ); 73 | } else { 74 | CustomSnackBarMessage.show( 75 | context, 'You are already on the latest version'); 76 | } 77 | }, 78 | child: NavRail( 79 | desktopBreakpoint: 1150, 80 | hideTitleBar: true, 81 | drawerHeaderBuilder: (context) { 82 | return Column( 83 | mainAxisAlignment: MainAxisAlignment.center, 84 | children: [ 85 | DrawerHeader( 86 | child: SvgPicture.asset( 87 | logo, 88 | semanticsLabel: 'CCExtractor Logo', 89 | ), 90 | ), 91 | _CheckForUpdatesButton() 92 | ], 93 | ); 94 | }, 95 | currentIndex: _currentIndex, 96 | onTap: (val) { 97 | if (mounted && _currentIndex != val) { 98 | setState(() { 99 | _currentIndex = val; 100 | }); 101 | } 102 | }, 103 | body: IndexedStack( 104 | index: _currentIndex, 105 | children: [ 106 | Dashboard(), 107 | BasicSettingsScreen(), 108 | InputSettingsScreen(), 109 | OutputSettingsScreen(), 110 | HardSubxSettingsScreen(), 111 | ObscureSettingsScreen(), 112 | ], 113 | ), 114 | tabs: [ 115 | BottomNavigationBarItem( 116 | label: 'Dashboard', 117 | icon: Icon(Icons.dashboard), 118 | ), 119 | BottomNavigationBarItem( 120 | label: 'Basic Settings', 121 | icon: Icon(Icons.settings), 122 | ), 123 | BottomNavigationBarItem( 124 | label: 'Input Settings', 125 | icon: Icon(Icons.input), 126 | ), 127 | BottomNavigationBarItem( 128 | label: 'Output Settings', 129 | icon: Icon(Icons.dvr_outlined), 130 | ), 131 | BottomNavigationBarItem( 132 | label: 'HardSubx Settings', 133 | icon: Icon(Icons.search), 134 | ), 135 | BottomNavigationBarItem( 136 | label: 'Obscure Settings', 137 | icon: Icon(Icons.do_disturb_alt_rounded), 138 | ), 139 | ], 140 | ), 141 | ); 142 | } 143 | } 144 | 145 | class _CheckForUpdatesButton extends StatelessWidget { 146 | const _CheckForUpdatesButton({ 147 | Key? key, 148 | }) : super(key: key); 149 | 150 | @override 151 | Widget build(BuildContext context) { 152 | if (!Platform.isWindows) return Container(); 153 | 154 | return BlocBuilder( 155 | builder: (context, state) { 156 | return InkWell( 157 | borderRadius: BorderRadius.circular(25), 158 | hoverColor: Colors.transparent, 159 | onTap: () { 160 | context.read().add(CheckForUpdates(state.version!)); 161 | }, 162 | child: Container( 163 | margin: const EdgeInsets.all(10), 164 | padding: const EdgeInsets.all(12), 165 | decoration: BoxDecoration( 166 | borderRadius: BorderRadius.circular(25), 167 | color: kBgLightColor, 168 | ), 169 | child: Material( 170 | type: MaterialType.transparency, 171 | child: IntrinsicHeight( 172 | child: Row( 173 | children: [ 174 | Icon( 175 | Icons.update, 176 | color: Colors.white54, 177 | ), 178 | VerticalDivider(), 179 | Expanded( 180 | child: FittedBox( 181 | child: Text( 182 | 'Check for updates', 183 | style: TextStyle(color: Colors.white, fontSize: 15), 184 | textAlign: TextAlign.center, 185 | ), 186 | ), 187 | ), 188 | VerticalDivider(), 189 | Text( 190 | 'V${state.version!.trim()}', 191 | style: TextStyle( 192 | fontSize: 12, 193 | fontWeight: FontWeight.bold, 194 | color: Colors.white60, 195 | ), 196 | ), 197 | ], 198 | ), 199 | ), 200 | ), 201 | ), 202 | ); 203 | }, 204 | ); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /lib/screens/settings/basic_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | 5 | import 'package:ccxgui/bloc/settings_bloc/settings_bloc.dart'; 6 | import 'package:ccxgui/screens/dashboard/components/custom_snackbar.dart'; 7 | import 'package:ccxgui/screens/settings/components/current_command.dart'; 8 | import 'package:ccxgui/utils/constants.dart'; 9 | import 'components/custom_divider.dart'; 10 | import 'components/custom_dropdown.dart'; 11 | import 'components/custom_swtich_listTile.dart'; 12 | import 'components/custom_textfield.dart'; 13 | 14 | class BasicSettingsScreen extends StatelessWidget { 15 | final ScrollController controller = ScrollController(); 16 | @override 17 | Widget build(BuildContext context) { 18 | return BlocBuilder( 19 | builder: (context, state) { 20 | if (state is SettingsErrorState) { 21 | context.read().add(CheckSettingsEvent()); 22 | return Center( 23 | child: Container( 24 | child: Text( 25 | 'Settings file corrupted, restoring default values. \n ${state.message}', 26 | ), 27 | ), 28 | ); 29 | } 30 | if (state is CurrentSettingsState) { 31 | TextEditingController outputFileNameController = 32 | TextEditingController(text: state.settingsModel.outputfilename); 33 | TextEditingController delay = 34 | TextEditingController(text: state.settingsModel.delay); 35 | TextEditingController startat = 36 | TextEditingController(text: state.settingsModel.startat); 37 | TextEditingController endat = 38 | TextEditingController(text: state.settingsModel.endat); 39 | 40 | return Scaffold( 41 | appBar: AppBar( 42 | flexibleSpace: FlexibleSpaceBar( 43 | title: CurrentCommandContainer(), 44 | titlePadding: 45 | const EdgeInsets.symmetric(horizontal: 24, vertical: 10), 46 | ), 47 | elevation: 0, 48 | toolbarHeight: 110, 49 | backgroundColor: Colors.transparent, 50 | ), 51 | body: Padding( 52 | padding: const EdgeInsets.all(8.0), 53 | child: ListView( 54 | controller: controller, 55 | children: [ 56 | CustomTextField( 57 | title: 'Output file name (press enter to save)', 58 | subtitle: 59 | "This will define the output filename if you don't like the default ones. Each file will be appeded with a _1, _2 when needed", 60 | onEditingComplete: () => context.read().add( 61 | SaveSettingsEvent( 62 | state.settingsModel.copyWith( 63 | outputfilename: outputFileNameController.text, 64 | ), 65 | ), 66 | ), 67 | controller: outputFileNameController, 68 | ), 69 | CustomDropDown( 70 | title: 'Input file format', 71 | subtitle: 72 | 'Force the file with a specific input format, leave blank to auto-detect', 73 | value: state.settingsModel.inp, 74 | items: inputFormats, 75 | onChanged: (String newValue) => 76 | context.read().add( 77 | SettingsUpdatedEvent( 78 | state.settingsModel.copyWith( 79 | inp: newValue, 80 | ), 81 | ), 82 | ), 83 | ), 84 | CustomDropDown( 85 | title: 'Output file format', 86 | subtitle: 87 | 'This will generate the output in the selected format.', 88 | value: state.settingsModel.out, 89 | items: outputFormats, 90 | onChanged: (String newValue) => 91 | context.read().add( 92 | SettingsUpdatedEvent( 93 | state.settingsModel.copyWith( 94 | out: newValue, 95 | ), 96 | ), 97 | ), 98 | ), 99 | CustomSwitchListTile( 100 | title: 'Append', 101 | subtitle: 102 | 'This will prevent overwriting of existing files. The output will be appended instead.', 103 | value: state.settingsModel.append, 104 | onTap: (value) { 105 | context.read().add( 106 | SettingsUpdatedEvent( 107 | state.settingsModel.copyWith( 108 | append: value, 109 | ), 110 | ), 111 | ); 112 | }, 113 | ), 114 | CustomSwitchListTile( 115 | title: 'Autoprogram', 116 | subtitle: 117 | "If there's more than one program in the stream, this will the first one we find that contains a suitable stream.", 118 | value: state.settingsModel.autoprogram, 119 | onTap: (value) { 120 | context.read().add( 121 | SettingsUpdatedEvent( 122 | state.settingsModel.copyWith( 123 | autoprogram: value, 124 | ), 125 | ), 126 | ); 127 | }, 128 | ), 129 | CustomSwitchListTile( 130 | title: 'Enable split mode', 131 | subtitle: 132 | 'Output will be one single file (either raw or srt). Use this if you made your recording in several cuts (to skip commercials for example) but you want one subtitle file with continuous timing. (warning: you cannot add more files till ccextractor finishes running on the selected files when this is enabled', 133 | value: state.settingsModel.splitMode, 134 | onTap: (value) { 135 | context.read().add( 136 | SettingsUpdatedEvent( 137 | state.settingsModel.copyWith( 138 | splitMode: value, 139 | ), 140 | ), 141 | ); 142 | }, 143 | ), 144 | CustomDivider(title: 'Timing settings'), 145 | CustomTextField( 146 | title: 'Delay (ms)', 147 | intOnly: true, 148 | subtitle: 149 | 'For srt/sami/webvtt, add this number of milliseconds to all times.You can also use negative numbers to make subs appear early.', 150 | onEditingComplete: () => context.read().add( 151 | SaveSettingsEvent( 152 | state.settingsModel.copyWith( 153 | delay: delay.text, 154 | ), 155 | ), 156 | ), 157 | controller: delay, 158 | ), 159 | CustomTextField( 160 | title: 'Start at', 161 | subtitle: 162 | 'Only write caption information that starts after the given time. Format: S or MM:SS', 163 | onEditingComplete: () { 164 | RegExp(r'^(?:(?:([01]?\d|2[0-3]):)?([0-5]?\d):)?([0-5]?\d)$') 165 | .hasMatch(startat.text) || 166 | startat.text.isEmpty 167 | ? context.read().add( 168 | SaveSettingsEvent( 169 | state.settingsModel.copyWith( 170 | startat: startat.text, 171 | ), 172 | ), 173 | ) 174 | : CustomSnackBarMessage.show( 175 | context, 'Invalid time format'); 176 | }, 177 | controller: startat, 178 | ), 179 | CustomTextField( 180 | title: 'End at', 181 | subtitle: 182 | 'Stop processing after the given time (same format as start at).', 183 | onEditingComplete: () { 184 | RegExp(r'^(?:(?:([01]?\d|2[0-3]):)?([0-5]?\d):)?([0-5]?\d)$') 185 | .hasMatch(endat.text) || 186 | endat.text.isEmpty 187 | ? context.read().add( 188 | SaveSettingsEvent( 189 | state.settingsModel.copyWith( 190 | endat: endat.text, 191 | ), 192 | ), 193 | ) 194 | : CustomSnackBarMessage.show( 195 | context, 'Invalid time format'); 196 | }, 197 | controller: endat, 198 | ), 199 | ], 200 | ), 201 | ), 202 | ); 203 | } 204 | return Container(); 205 | }, 206 | ); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /lib/screens/settings/components/current_command.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | 5 | import 'package:ccxgui/bloc/settings_bloc/settings_bloc.dart'; 6 | import 'package:ccxgui/models/settings_model.dart'; 7 | import 'package:ccxgui/repositories/settings_repository.dart'; 8 | import 'package:ccxgui/utils/constants.dart'; 9 | import 'package:ccxgui/utils/responsive.dart'; 10 | 11 | class CurrentCommandContainer extends StatelessWidget { 12 | final SettingsRepository settingsRepository = SettingsRepository(); 13 | 14 | CurrentCommandContainer({Key? key}) : super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return BlocBuilder( 19 | builder: (context, state) { 20 | if (state is CurrentSettingsState) { 21 | SettingsModel settings = state.settingsModel; 22 | List paramsList = settingsRepository.getParamsList(settings); 23 | return Column( 24 | mainAxisSize: MainAxisSize.max, 25 | mainAxisAlignment: MainAxisAlignment.center, 26 | crossAxisAlignment: CrossAxisAlignment.start, 27 | children: [ 28 | Row( 29 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 30 | children: [ 31 | Text( 32 | 'Current Command: ', 33 | style: TextStyle( 34 | fontSize: 20, 35 | ), 36 | ), 37 | MaterialButton( 38 | onPressed: () { 39 | context.read().add( 40 | ResetSettingsEvent(), 41 | ); 42 | }, 43 | child: Row( 44 | children: [ 45 | Icon(Icons.restore, color: Colors.redAccent, size: 20), 46 | SizedBox( 47 | width: 5, 48 | ), 49 | Text( 50 | 'Reset settings', 51 | style: TextStyle( 52 | color: Colors.redAccent, 53 | fontSize: 15, 54 | ), 55 | ), 56 | ], 57 | ), 58 | ) 59 | ], 60 | ), 61 | SizedBox(height: 12), 62 | SingleChildScrollView( 63 | scrollDirection: Axis.vertical, 64 | child: Container( 65 | width: Responsive.isDesktop(context) 66 | ? MediaQuery.of(context).size.width - 270 67 | : MediaQuery.of(context).size.width - 56, 68 | height: Responsive.isDesktop(context) 69 | ? MediaQuery.of(context).size.height / 20 70 | : MediaQuery.of(context).size.height / 71 | 20, // remove drawer width 72 | decoration: BoxDecoration( 73 | color: kBgLightColor, 74 | ), 75 | child: Padding( 76 | padding: const EdgeInsets.only(left: 10), 77 | child: SelectableText( 78 | 'ccextractor --gui_mode_reports ${paramsList.reduce((value, element) => value + ' ' + element)} +[input files]', 79 | // maxLines: 2, 80 | style: TextStyle( 81 | fontSize: 15, 82 | ), 83 | ), 84 | ), 85 | ), 86 | ), 87 | ], 88 | ); 89 | } 90 | return Container(); 91 | }, 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/screens/settings/components/custom_divider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomDivider extends StatelessWidget { 4 | final String title; 5 | final String? description; 6 | const CustomDivider({Key? key, required this.title, this.description}) 7 | : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Column( 12 | crossAxisAlignment: CrossAxisAlignment.start, 13 | children: [ 14 | Divider(), 15 | Padding( 16 | padding: const EdgeInsets.symmetric(horizontal: 15), 17 | child: Column( 18 | crossAxisAlignment: CrossAxisAlignment.start, 19 | children: [ 20 | Text( 21 | title, 22 | style: TextStyle( 23 | fontSize: 19, 24 | color: Theme.of(context).colorScheme.secondary, 25 | ), 26 | ), 27 | if (description != null) SizedBox(height: 10), 28 | Text( 29 | description ?? '', 30 | style: TextStyle( 31 | fontSize: 13, 32 | color: Colors.grey.shade400, 33 | ), 34 | ), 35 | ], 36 | ), 37 | ), 38 | ], 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/screens/settings/components/custom_dropdown.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:ccxgui/utils/constants.dart'; 4 | import 'package:ccxgui/utils/responsive.dart'; 5 | 6 | class CustomDropDown extends StatelessWidget { 7 | final String title; 8 | final String subtitle; 9 | final String value; 10 | final Function onChanged; 11 | final List items; 12 | const CustomDropDown( 13 | {Key? key, 14 | required this.title, 15 | required this.subtitle, 16 | required this.value, 17 | required this.onChanged, 18 | required this.items}) 19 | : super(key: key); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return ListTile( 24 | title: Text(title), 25 | subtitle: Text(subtitle), 26 | trailing: MouseRegion( 27 | cursor: SystemMouseCursors.click, 28 | child: Container( 29 | width: Responsive.isDesktop(context) ? 300 : 125, 30 | color: kBgLightColor, 31 | child: DropdownButton( 32 | underline: Container(), 33 | isExpanded: true, 34 | value: value, 35 | elevation: 0, 36 | items: items.map((String value) { 37 | return DropdownMenuItem( 38 | value: value, 39 | child: Center( 40 | child: Padding( 41 | padding: 42 | const EdgeInsets.symmetric(vertical: 12, horizontal: 5), 43 | child: Text(value), 44 | ), 45 | ), 46 | ); 47 | }).toList(), 48 | onChanged: (String? newValue) => onChanged(newValue), 49 | ), 50 | ), 51 | ), 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/screens/settings/components/custom_path_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; 4 | 5 | import 'package:ccxgui/utils/constants.dart'; 6 | import 'package:ccxgui/utils/responsive.dart'; 7 | 8 | class CustomGetFilePathButton extends StatefulWidget { 9 | final String title; 10 | final String subtitle; 11 | final String currentPath; 12 | final Function(String path) saveToConfig; 13 | final VoidCallback clearField; 14 | const CustomGetFilePathButton({ 15 | Key? key, 16 | required this.title, 17 | required this.subtitle, 18 | required this.saveToConfig, 19 | required this.currentPath, 20 | required this.clearField, 21 | }) : super(key: key); 22 | 23 | @override 24 | _CustomGetFilePathButtonState createState() => 25 | _CustomGetFilePathButtonState(); 26 | } 27 | 28 | class _CustomGetFilePathButtonState extends State { 29 | String path = ''; 30 | 31 | void _getFilePath(BuildContext context) async { 32 | final XFile? file = await FileSelectorPlatform.instance.openFile(); 33 | if (file == null) { 34 | // Operation was canceled by the user. 35 | return; 36 | } 37 | widget.saveToConfig(file.path); 38 | 39 | setState(() { 40 | path = file.path; 41 | }); 42 | } 43 | 44 | final ScrollController _scrollController = ScrollController(); 45 | @override 46 | Widget build(BuildContext context) { 47 | Size screensize = MediaQuery.of(context).size; 48 | _scrollController.hasClients 49 | ? _scrollController.animateTo( 50 | 0.0, 51 | curve: Curves.easeOut, 52 | duration: const Duration(milliseconds: 300), 53 | ) 54 | : null; 55 | path = widget.currentPath; 56 | if (path.isEmpty) path = 'Browse'; 57 | return ListTile( 58 | title: Text(widget.title), 59 | subtitle: Text(widget.subtitle), 60 | trailing: MouseRegion( 61 | cursor: SystemMouseCursors.click, 62 | child: Container( 63 | width: Responsive.isDesktop(context) 64 | ? screensize.width * 0.25 65 | : screensize.width * 0.145, 66 | child: Row( 67 | children: [ 68 | Tooltip( 69 | message: 'Clear field', 70 | child: IconButton( 71 | padding: EdgeInsets.zero, 72 | onPressed: widget.clearField, 73 | icon: Icon(Icons.delete_outline), 74 | color: Colors.red, 75 | ), 76 | ), 77 | SizedBox( 78 | width: 10, 79 | ), 80 | Container( 81 | width: Responsive.isDesktop(context) 82 | ? screensize.width * 0.205 83 | : screensize.width * 0.081, 84 | height: 46, 85 | color: kBgLightColor, 86 | child: MaterialButton( 87 | onPressed: () async { 88 | _getFilePath(context); 89 | }, 90 | child: SingleChildScrollView( 91 | reverse: true, 92 | controller: _scrollController, 93 | scrollDirection: Axis.horizontal, 94 | child: Text( 95 | path, 96 | maxLines: 3, 97 | ), 98 | ), 99 | ), 100 | ), 101 | ], 102 | ), 103 | ), 104 | ), 105 | ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /lib/screens/settings/components/custom_swtich_listTile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomSwitchListTile extends StatelessWidget { 4 | final String title; 5 | final String subtitle; 6 | final bool value; 7 | final Function onTap; 8 | const CustomSwitchListTile({ 9 | Key? key, 10 | required this.title, 11 | required this.subtitle, 12 | required this.value, 13 | required this.onTap, 14 | }) : super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return ListTile( 19 | minVerticalPadding: 8, 20 | title: Text(title), 21 | subtitle: Text(subtitle), 22 | trailing: Container( 23 | child: Switch( 24 | activeColor: Theme.of(context).colorScheme.secondary, 25 | value: value, 26 | onChanged: (value) => onTap(value), 27 | ), 28 | ), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/screens/settings/components/custom_textfield.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | import 'package:ccxgui/utils/constants.dart'; 5 | import 'package:ccxgui/utils/responsive.dart'; 6 | 7 | class CustomTextField extends StatefulWidget { 8 | final String title; 9 | final String subtitle; 10 | final Function onEditingComplete; 11 | final TextEditingController controller; 12 | final bool intOnly; 13 | final bool enabled; 14 | 15 | const CustomTextField({ 16 | Key? key, 17 | required this.title, 18 | required this.subtitle, 19 | required this.onEditingComplete, 20 | required this.controller, 21 | this.intOnly = false, 22 | this.enabled = true, 23 | }) : super(key: key); 24 | 25 | @override 26 | _CustomTextFieldState createState() => _CustomTextFieldState(); 27 | } 28 | 29 | class _CustomTextFieldState extends State { 30 | @override 31 | Widget build(BuildContext context) { 32 | return Padding( 33 | padding: const EdgeInsets.symmetric(horizontal: 17, vertical: 10), 34 | child: Row( 35 | mainAxisSize: MainAxisSize.min, 36 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 37 | children: [ 38 | Expanded( 39 | child: Column( 40 | crossAxisAlignment: CrossAxisAlignment.start, 41 | children: [ 42 | Text( 43 | widget.title, 44 | style: TextStyle( 45 | color: Colors.white, 46 | fontSize: 16, 47 | ), 48 | ), 49 | Text( 50 | widget.subtitle, 51 | style: TextStyle( 52 | color: Colors.grey.shade400, 53 | fontSize: 13, 54 | ), 55 | ), 56 | ], 57 | ), 58 | ), 59 | Container( 60 | color: kBgLightColor, 61 | width: Responsive.isDesktop(context) ? 300 : 100, 62 | child: TextFormField( 63 | cursorHeight: 25, 64 | enabled: widget.enabled, 65 | inputFormatters: widget.intOnly 66 | ? [FilteringTextInputFormatter.digitsOnly] 67 | : [], 68 | onEditingComplete: () { 69 | widget.onEditingComplete(); 70 | setState(() {}); 71 | }, 72 | controller: widget.controller, 73 | decoration: InputDecoration( 74 | border: InputBorder.none, 75 | focusedBorder: InputBorder.none, 76 | enabledBorder: InputBorder.none, 77 | errorBorder: InputBorder.none, 78 | disabledBorder: InputBorder.none, 79 | contentPadding: EdgeInsets.symmetric( 80 | vertical: 18, 81 | horizontal: 6, 82 | ), 83 | isDense: true, 84 | ), 85 | cursorColor: Colors.transparent, 86 | ), 87 | ), 88 | ], 89 | ), 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/screens/settings/obscure_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | 5 | import 'package:ccxgui/bloc/settings_bloc/settings_bloc.dart'; 6 | import 'package:ccxgui/screens/settings/components/current_command.dart'; 7 | import 'package:ccxgui/screens/settings/components/custom_divider.dart'; 8 | import 'package:ccxgui/screens/settings/components/custom_textfield.dart'; 9 | import 'components/custom_swtich_listTile.dart'; 10 | 11 | class ObscureSettingsScreen extends StatelessWidget { 12 | final ScrollController controller = ScrollController(); 13 | @override 14 | Widget build(BuildContext context) { 15 | return BlocBuilder( 16 | builder: (context, state) { 17 | if (state is SettingsErrorState) { 18 | context.read().add(CheckSettingsEvent()); 19 | return Center( 20 | child: Container( 21 | child: Text( 22 | 'Settings file corrupted, restoring default values. \n ${state.message}', 23 | ), 24 | ), 25 | ); 26 | } 27 | if (state is CurrentSettingsState) { 28 | TextEditingController programNumberController = 29 | TextEditingController(text: state.settingsModel.program_number); 30 | 31 | return Scaffold( 32 | appBar: AppBar( 33 | flexibleSpace: FlexibleSpaceBar( 34 | title: CurrentCommandContainer(), 35 | titlePadding: 36 | const EdgeInsets.symmetric(horizontal: 24, vertical: 10), 37 | ), 38 | elevation: 0, 39 | toolbarHeight: 110, 40 | backgroundColor: Colors.transparent, 41 | ), 42 | body: Padding( 43 | padding: const EdgeInsets.all(8.0), 44 | child: ListView( 45 | controller: controller, 46 | children: [ 47 | CustomSwitchListTile( 48 | title: 'Panasonic DMR-ES15', 49 | subtitle: 50 | 'Use 90090 (instead of 90000) as MPEG clock frequency. (reported to be needed at least by Panasonic DMR-ES15 DVD Recorder)', 51 | value: state.settingsModel.freqEs15, 52 | onTap: (value) { 53 | context.read().add( 54 | SettingsUpdatedEvent( 55 | state.settingsModel.copyWith( 56 | freqEs15: value, 57 | ), 58 | ), 59 | ); 60 | }, 61 | ), 62 | CustomSwitchListTile( 63 | title: 'Use Picorder', 64 | subtitle: 65 | 'Use the pic_order_cnt_lsb in AVC/H.264 data streams to order the CC information. The default way is to the PTS information. Use this switch only when needed.', 66 | value: state.settingsModel.usepicorder, 67 | onTap: (value) { 68 | context.read().add( 69 | SettingsUpdatedEvent( 70 | state.settingsModel.copyWith( 71 | usepicorder: value, 72 | ), 73 | ), 74 | ); 75 | }, 76 | ), 77 | CustomSwitchListTile( 78 | title: 'WTV convert fix', 79 | subtitle: 80 | "This switch works around a bug in Windows 7's built in software to convert *.wtv to *.dvr-ms. For analog NTSC recordings the CC information is marked as digital captions. Use this switch only when needed.", 81 | value: state.settingsModel.wtvconvertfix, 82 | onTap: (value) { 83 | context.read().add( 84 | SettingsUpdatedEvent( 85 | state.settingsModel.copyWith( 86 | wtvconvertfix: value, 87 | ), 88 | ), 89 | ); 90 | }, 91 | ), 92 | CustomSwitchListTile( 93 | title: 'Hauppage', 94 | subtitle: 95 | 'If the video was recorder using a Hauppauge card, it might need special processing. This parameter will force the special treatment.', 96 | value: state.settingsModel.hauppauge, 97 | onTap: (value) { 98 | context.read().add( 99 | SettingsUpdatedEvent( 100 | state.settingsModel.copyWith( 101 | hauppauge: value, 102 | ), 103 | ), 104 | ); 105 | }, 106 | ), 107 | CustomTextField( 108 | title: 'Program number', 109 | subtitle: 110 | 'In TS mode, specifically select a program to process.', 111 | intOnly: true, 112 | onEditingComplete: () => context.read().add( 113 | SaveSettingsEvent( 114 | state.settingsModel.copyWith( 115 | program_number: programNumberController.text, 116 | ), 117 | ), 118 | ), 119 | controller: programNumberController, 120 | ), 121 | CustomSwitchListTile( 122 | title: 'Multiprogram', 123 | subtitle: 124 | 'Uses multiple programs from the same input stream.', 125 | value: state.settingsModel.multiprogram, 126 | onTap: (value) { 127 | context.read().add( 128 | SettingsUpdatedEvent( 129 | state.settingsModel.copyWith( 130 | multiprogram: value, 131 | ), 132 | ), 133 | ); 134 | }, 135 | ), 136 | CustomDivider(title: 'Myth TV'), 137 | CustomSwitchListTile( 138 | title: 'Use Myth TV ', 139 | subtitle: 'Force MythTV code branch.', 140 | value: state.settingsModel.myth, 141 | onTap: (bool value) { 142 | value == true 143 | ? context.read().add( 144 | SettingsUpdatedEvent( 145 | state.settingsModel.copyWith( 146 | myth: value, 147 | nomyth: false, 148 | ), 149 | ), 150 | ) 151 | : context.read().add( 152 | SettingsUpdatedEvent( 153 | state.settingsModel.copyWith( 154 | myth: value, 155 | ), 156 | ), 157 | ); 158 | }, 159 | ), 160 | CustomSwitchListTile( 161 | title: 'Disable MythTV code branch.', 162 | subtitle: 163 | 'The MythTV branch is needed for analog captures where the closed caption data is stored in the VBI, such as those with bttv cards (Hauppage 250 for example).', 164 | value: state.settingsModel.nomyth, 165 | onTap: (bool value) { 166 | value == true 167 | ? context.read().add( 168 | SettingsUpdatedEvent( 169 | state.settingsModel.copyWith( 170 | nomyth: value, 171 | myth: false, 172 | ), 173 | ), 174 | ) 175 | : context.read().add( 176 | SettingsUpdatedEvent( 177 | state.settingsModel.copyWith( 178 | nomyth: value, 179 | ), 180 | ), 181 | ); 182 | }, 183 | ), 184 | CustomSwitchListTile( 185 | title: 'Chapters', 186 | subtitle: 187 | '(Experimental) Produces a chapter file from MP4 files.', 188 | value: state.settingsModel.chapters, 189 | onTap: (value) { 190 | context.read().add( 191 | SettingsUpdatedEvent( 192 | state.settingsModel.copyWith( 193 | chapters: value, 194 | ), 195 | ), 196 | ); 197 | }, 198 | ), 199 | ], 200 | ), 201 | ), 202 | ); 203 | } 204 | return Container(); 205 | }, 206 | ); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /lib/utils/constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | const kBgLightColor = Color(0xFF303030); 4 | const kBgDarkColor = Color(0xFF181818); 5 | const kDefaultPadding = 20.0; 6 | 7 | Map> dropdownListMap = { 8 | 'xmltv': { 9 | 'auto/default': 69, // this wont happen so any random value for now. 10 | 'Full output': 1, 11 | 'Live output': 2, 12 | 'Both': 3, 13 | }, 14 | 'quant': { 15 | 'auto/default': 69, 16 | 'Don\'t quantize at all.': 0, 17 | 'Use CCExtractor\'s internal function (default).': 1, 18 | 'Reduce distinct color count in image for faster results.': 2, 19 | }, 20 | 'oem': { 21 | 'auto/default': 69, 22 | 'OEM_TESSERACT_ONLY - the fastest mode.': 0, 23 | 'OEM_LSTM_ONLY - use LSTM algorithm for recognition.': 1, 24 | 'OEM_TESSERACT_LSTM_COMBINED - both algorithms.': 2, 25 | }, 26 | 'streamtype': { 27 | 'auto/default': 69, 28 | 'VIDEO_MPEG1': 1, 29 | 'VIDEO_MPEG2': 2, 30 | 'AUDIO_MPEG1': 3, 31 | 'AUDIO_MPEG2': 4, 32 | 'PRIVATE_TABLE_MPEG2': 5, 33 | 'PRIVATE_MPEG2': 6, 34 | 'MHEG_PACKETS': 7, 35 | 'MPEG2_ANNEX_A_DSM_CC': 8, 36 | 'ITU_T_H222_1': 9, 37 | 'ISO_IEC_13818_6_TYPE_A': 10, 38 | 'ISO_IEC_13818_6_TYPE_B': 11, 39 | 'ISO_IEC_13818_6_TYPE_C': 12, 40 | 'ISO_IEC_13818_6_TYPE_D': 13, 41 | 'AUDIO_AAC': 15, 42 | 'VIDEO_MPEG4': 16, 43 | 'VIDEO_H264': 27, 44 | 'PRIVATE_USER_MPEG2': 128, 45 | 'AUDIO_AC3': 129, 46 | 'AUDIO_HDMV_DTS': 130, 47 | 'AUDIO_DTS': 138 48 | }, 49 | }; 50 | 51 | List ocrMode = [ 52 | 'auto/default', 53 | 'frame', 54 | 'word', 55 | 'letter', 56 | ]; 57 | 58 | List rollUp = [ 59 | 'auto/default', 60 | 'ru1', 61 | 'ru2', 62 | 'ru3', 63 | ]; 64 | 65 | //codec and nocodec have the same options 66 | List codec = [ 67 | 'auto/default', 68 | 'dvbsub', 69 | 'teletext', 70 | ]; 71 | 72 | List encoder = [ 73 | 'auto/default', 74 | 'utf8', 75 | 'unicode', 76 | 'latin1', 77 | ]; 78 | 79 | /// Input formats 80 | List inputFormats = [ 81 | 'auto/default', 82 | 'ts', 83 | 'ps', 84 | 'es', 85 | 'asf', 86 | 'wtv', 87 | 'bin', 88 | 'raw', 89 | 'mp4' 90 | ]; 91 | 92 | /// Output formats 93 | List outputFormats = [ 94 | 'auto/default', 95 | 'srt', 96 | 'ssa', 97 | 'webvtt', 98 | 'sami', 99 | 'bin', 100 | 'raw', 101 | 'dvdraw', 102 | 'txt', 103 | 'ttxt', 104 | 'smptett', 105 | 'spupng', 106 | 'null', 107 | ]; 108 | -------------------------------------------------------------------------------- /lib/utils/responsive.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Responsive extends StatelessWidget { 4 | final Widget mobile; 5 | final Widget desktop; 6 | 7 | const Responsive({ 8 | Key? key, 9 | required this.mobile, 10 | required this.desktop, 11 | }) : super(key: key); 12 | 13 | // This size work fine on my design, maybe you need some customization depends on your design 14 | 15 | // This isMobile, isTablet, isDesktop help us later 16 | static bool isMobile(BuildContext context) => 17 | MediaQuery.of(context).size.width < 1150; 18 | 19 | static bool isDesktop(BuildContext context) => 20 | MediaQuery.of(context).size.width >= 1150; 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return LayoutBuilder( 25 | // If our width is more than 1100 then we consider it a desktop 26 | builder: (context, constraints) { 27 | if (constraints.maxWidth >= 1150) { 28 | return desktop; 29 | } 30 | // Or less then that we called it mobile 31 | else { 32 | return mobile; 33 | } 34 | }, 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(runner LANGUAGES CXX) 3 | 4 | set(BINARY_NAME "ccxgui") 5 | set(APPLICATION_ID "com.example.ccxgui") 6 | 7 | cmake_policy(SET CMP0063 NEW) 8 | 9 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 10 | 11 | # Root filesystem for cross-building. 12 | if(FLUTTER_TARGET_PLATFORM_SYSROOT) 13 | set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) 14 | set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) 15 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 16 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) 17 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 18 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 19 | endif() 20 | 21 | # Configure build options. 22 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 23 | set(CMAKE_BUILD_TYPE "Debug" CACHE 24 | STRING "Flutter build mode" FORCE) 25 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 26 | "Debug" "Profile" "Release") 27 | endif() 28 | 29 | # Compilation settings that should be applied to most targets. 30 | function(APPLY_STANDARD_SETTINGS TARGET) 31 | target_compile_features(${TARGET} PUBLIC cxx_std_14) 32 | target_compile_options(${TARGET} PRIVATE -Wall -Werror) 33 | target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") 34 | target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") 35 | endfunction() 36 | 37 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 38 | 39 | # Flutter library and tool build rules. 40 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 41 | 42 | # System-level dependencies. 43 | find_package(PkgConfig REQUIRED) 44 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 45 | 46 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") 47 | 48 | # Application build 49 | add_executable(${BINARY_NAME} 50 | "main.cc" 51 | "my_application.cc" 52 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 53 | ) 54 | apply_standard_settings(${BINARY_NAME}) 55 | target_link_libraries(${BINARY_NAME} PRIVATE flutter) 56 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) 57 | add_dependencies(${BINARY_NAME} flutter_assemble) 58 | # Only the install-generated bundle's copy of the executable will launch 59 | # correctly, since the resources must in the right relative locations. To avoid 60 | # people trying to run the unbundled copy, put it in a subdirectory instead of 61 | # the default top-level location. 62 | set_target_properties(${BINARY_NAME} 63 | PROPERTIES 64 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" 65 | ) 66 | 67 | # Generated plugin build rules, which manage building the plugins and adding 68 | # them to the application. 69 | include(flutter/generated_plugins.cmake) 70 | 71 | 72 | # === Installation === 73 | # By default, "installing" just makes a relocatable bundle in the build 74 | # directory. 75 | set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") 76 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 77 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 78 | endif() 79 | 80 | # Start with a clean build bundle directory every time. 81 | install(CODE " 82 | file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") 83 | " COMPONENT Runtime) 84 | 85 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 86 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") 87 | 88 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 89 | COMPONENT Runtime) 90 | 91 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 92 | COMPONENT Runtime) 93 | 94 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 95 | COMPONENT Runtime) 96 | 97 | if(PLUGIN_BUNDLED_LIBRARIES) 98 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 99 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 100 | COMPONENT Runtime) 101 | endif() 102 | 103 | # Fully re-copy the assets directory on each build to avoid having stale files 104 | # from a previous install. 105 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 106 | install(CODE " 107 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 108 | " COMPONENT Runtime) 109 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 110 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 111 | 112 | # Install the AOT library on non-Debug builds only. 113 | if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") 114 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 115 | COMPONENT Runtime) 116 | endif() 117 | -------------------------------------------------------------------------------- /linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 4 | 5 | # Configuration provided via flutter tool. 6 | include(${EPHEMERAL_DIR}/generated_config.cmake) 7 | 8 | # TODO: Move the rest of this into files in ephemeral. See 9 | # https://github.com/flutter/flutter/issues/57146. 10 | 11 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 12 | # which isn't available in 3.10. 13 | function(list_prepend LIST_NAME PREFIX) 14 | set(NEW_LIST "") 15 | foreach(element ${${LIST_NAME}}) 16 | list(APPEND NEW_LIST "${PREFIX}${element}") 17 | endforeach(element) 18 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 19 | endfunction() 20 | 21 | # === Flutter Library === 22 | # System-level dependencies. 23 | find_package(PkgConfig REQUIRED) 24 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 25 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 26 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 27 | pkg_check_modules(BLKID REQUIRED IMPORTED_TARGET blkid) 28 | pkg_check_modules(LZMA REQUIRED IMPORTED_TARGET liblzma) 29 | 30 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 31 | 32 | # Published to parent scope for install step. 33 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 34 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 35 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 36 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 37 | 38 | list(APPEND FLUTTER_LIBRARY_HEADERS 39 | "fl_basic_message_channel.h" 40 | "fl_binary_codec.h" 41 | "fl_binary_messenger.h" 42 | "fl_dart_project.h" 43 | "fl_engine.h" 44 | "fl_json_message_codec.h" 45 | "fl_json_method_codec.h" 46 | "fl_message_codec.h" 47 | "fl_method_call.h" 48 | "fl_method_channel.h" 49 | "fl_method_codec.h" 50 | "fl_method_response.h" 51 | "fl_plugin_registrar.h" 52 | "fl_plugin_registry.h" 53 | "fl_standard_message_codec.h" 54 | "fl_standard_method_codec.h" 55 | "fl_string_codec.h" 56 | "fl_value.h" 57 | "fl_view.h" 58 | "flutter_linux.h" 59 | ) 60 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 61 | add_library(flutter INTERFACE) 62 | target_include_directories(flutter INTERFACE 63 | "${EPHEMERAL_DIR}" 64 | ) 65 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 66 | target_link_libraries(flutter INTERFACE 67 | PkgConfig::GTK 68 | PkgConfig::GLIB 69 | PkgConfig::GIO 70 | PkgConfig::BLKID 71 | PkgConfig::LZMA 72 | ) 73 | add_dependencies(flutter flutter_assemble) 74 | 75 | # === Flutter tool backend === 76 | # _phony_ is a non-existent file to force this command to run every time, 77 | # since currently there's no way to get a full input/output list from the 78 | # flutter tool. 79 | add_custom_command( 80 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 81 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 82 | COMMAND ${CMAKE_COMMAND} -E env 83 | ${FLUTTER_TOOL_ENVIRONMENT} 84 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 85 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 86 | VERBATIM 87 | ) 88 | add_custom_target(flutter_assemble DEPENDS 89 | "${FLUTTER_LIBRARY}" 90 | ${FLUTTER_LIBRARY_HEADERS} 91 | ) 92 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | void fl_register_plugins(FlPluginRegistry* registry) { 15 | g_autoptr(FlPluginRegistrar) desktop_drop_registrar = 16 | fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin"); 17 | desktop_drop_plugin_register_with_registrar(desktop_drop_registrar); 18 | g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = 19 | fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); 20 | file_selector_plugin_register_with_registrar(file_selector_linux_registrar); 21 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 22 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 23 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 24 | g_autoptr(FlPluginRegistrar) window_size_registrar = 25 | fl_plugin_registry_get_registrar_for_plugin(registry, "WindowSizePlugin"); 26 | window_size_plugin_register_with_registrar(window_size_registrar); 27 | } 28 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | desktop_drop 7 | file_selector_linux 8 | url_launcher_linux 9 | window_size 10 | ) 11 | 12 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 13 | ) 14 | 15 | set(PLUGIN_BUNDLED_LIBRARIES) 16 | 17 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 18 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 19 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 22 | endforeach(plugin) 23 | 24 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 25 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 27 | endforeach(ffi_plugin) 28 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /linux/my_application.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | #include 4 | #ifdef GDK_WINDOWING_X11 5 | #include 6 | #endif 7 | 8 | #include "flutter/generated_plugin_registrant.h" 9 | 10 | struct _MyApplication 11 | { 12 | GtkApplication parent_instance; 13 | char **dart_entrypoint_arguments; 14 | }; 15 | 16 | G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) 17 | 18 | // Implements GApplication::activate. 19 | static void my_application_activate(GApplication *application) 20 | { 21 | MyApplication *self = MY_APPLICATION(application); 22 | GtkWindow *window = 23 | GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); 24 | 25 | // Use a header bar when running in GNOME as this is the common style used 26 | // by applications and is the setup most users will be using (e.g. Ubuntu 27 | // desktop). 28 | // If running on X and not using GNOME then just use a traditional title bar 29 | // in case the window manager does more exotic layout, e.g. tiling. 30 | // If running on Wayland assume the header bar will work (may need changing 31 | // if future cases occur). 32 | gboolean use_header_bar = TRUE; 33 | #ifdef GDK_WINDOWING_X11 34 | GdkScreen *screen = gtk_window_get_screen(window); 35 | if (GDK_IS_X11_SCREEN(screen)) 36 | { 37 | const gchar *wm_name = gdk_x11_screen_get_window_manager_name(screen); 38 | if (g_strcmp0(wm_name, "GNOME Shell") != 0) 39 | { 40 | use_header_bar = FALSE; 41 | } 42 | } 43 | #endif 44 | if (use_header_bar) 45 | { 46 | GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); 47 | gtk_widget_show(GTK_WIDGET(header_bar)); 48 | gtk_header_bar_set_title(header_bar, "ccxgui"); 49 | gtk_header_bar_set_show_close_button(header_bar, TRUE); 50 | gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); 51 | } 52 | else 53 | { 54 | gtk_window_set_title(window, "ccxgui"); 55 | } 56 | 57 | gtk_window_set_default_size(window, 1280, 720); 58 | gtk_widget_show(GTK_WIDGET(window)); 59 | 60 | g_autoptr(FlDartProject) project = fl_dart_project_new(); 61 | fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); 62 | 63 | FlView *view = fl_view_new(project); 64 | gtk_widget_show(GTK_WIDGET(view)); 65 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); 66 | 67 | fl_register_plugins(FL_PLUGIN_REGISTRY(view)); 68 | 69 | gtk_widget_grab_focus(GTK_WIDGET(view)); 70 | } 71 | 72 | // Implements GApplication::local_command_line. 73 | static gboolean my_application_local_command_line(GApplication *application, gchar ***arguments, int *exit_status) 74 | { 75 | MyApplication *self = MY_APPLICATION(application); 76 | // Strip out the first argument as it is the binary name. 77 | self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); 78 | 79 | g_autoptr(GError) error = nullptr; 80 | if (!g_application_register(application, nullptr, &error)) 81 | { 82 | g_warning("Failed to register: %s", error->message); 83 | *exit_status = 1; 84 | return TRUE; 85 | } 86 | 87 | g_application_activate(application); 88 | *exit_status = 0; 89 | 90 | return TRUE; 91 | } 92 | 93 | // Implements GObject::dispose. 94 | static void my_application_dispose(GObject *object) 95 | { 96 | MyApplication *self = MY_APPLICATION(object); 97 | g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); 98 | G_OBJECT_CLASS(my_application_parent_class)->dispose(object); 99 | } 100 | 101 | static void my_application_class_init(MyApplicationClass *klass) 102 | { 103 | G_APPLICATION_CLASS(klass)->activate = my_application_activate; 104 | G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; 105 | G_OBJECT_CLASS(klass)->dispose = my_application_dispose; 106 | } 107 | 108 | static void my_application_init(MyApplication *self) {} 109 | 110 | MyApplication *my_application_new() 111 | { 112 | return MY_APPLICATION(g_object_new(my_application_get_type(), 113 | "application-id", APPLICATION_ID, 114 | "flags", G_APPLICATION_NON_UNIQUE, 115 | nullptr)); 116 | } 117 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication *my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/xcuserdata/ 7 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import desktop_drop 9 | import file_selector_macos 10 | import path_provider_macos 11 | import url_launcher_macos 12 | import window_size 13 | 14 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 15 | DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin")) 16 | FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) 17 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 18 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 19 | WindowSizePlugin.register(with: registry.registrar(forPlugin: "WindowSizePlugin")) 20 | } 21 | -------------------------------------------------------------------------------- /macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.14' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | end 35 | 36 | post_install do |installer| 37 | installer.pods_project.targets.each do |target| 38 | flutter_additional_macos_build_settings(target) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /macos/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - desktop_drop (0.0.1): 3 | - FlutterMacOS 4 | - file_selector_macos (0.0.1): 5 | - FlutterMacOS 6 | - FlutterMacOS (1.0.0) 7 | - path_provider_macos (0.0.1): 8 | - FlutterMacOS 9 | - url_launcher_macos (0.0.1): 10 | - FlutterMacOS 11 | - window_size (0.0.2): 12 | - FlutterMacOS 13 | 14 | DEPENDENCIES: 15 | - desktop_drop (from `Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos`) 16 | - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) 17 | - FlutterMacOS (from `Flutter/ephemeral`) 18 | - path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`) 19 | - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) 20 | - window_size (from `Flutter/ephemeral/.symlinks/plugins/window_size/macos`) 21 | 22 | EXTERNAL SOURCES: 23 | desktop_drop: 24 | :path: Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos 25 | file_selector_macos: 26 | :path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos 27 | FlutterMacOS: 28 | :path: Flutter/ephemeral 29 | path_provider_macos: 30 | :path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos 31 | url_launcher_macos: 32 | :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos 33 | window_size: 34 | :path: Flutter/ephemeral/.symlinks/plugins/window_size/macos 35 | 36 | SPEC CHECKSUMS: 37 | desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898 38 | file_selector_macos: ff6dc948d4ddd34e8602a1f60b7d0b4cc6051a47 39 | FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 40 | path_provider_macos: a0a3fd666cb7cd0448e936fb4abad4052961002b 41 | url_launcher_macos: 45af3d61de06997666568a7149c1be98b41c95d4 42 | window_size: 339dafa0b27a95a62a843042038fa6c3c48de195 43 | 44 | PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 45 | 46 | COCOAPODS: 1.11.3 47 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 64 | 65 | 71 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCExtractor/ccextractorfluttergui/0dad21f205ed4018b4fc069cd050a45186258abc/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCExtractor/ccextractorfluttergui/0dad21f205ed4018b4fc069cd050a45186258abc/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCExtractor/ccextractorfluttergui/0dad21f205ed4018b4fc069cd050a45186258abc/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCExtractor/ccextractorfluttergui/0dad21f205ed4018b4fc069cd050a45186258abc/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCExtractor/ccextractorfluttergui/0dad21f205ed4018b4fc069cd050a45186258abc/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCExtractor/ccextractorfluttergui/0dad21f205ed4018b4fc069cd050a45186258abc/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCExtractor/ccextractorfluttergui/0dad21f205ed4018b4fc069cd050a45186258abc/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = ccxgui 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.example.ccxgui 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. 15 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | com.apple.security.files.user-selected.read-write 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController.init() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | com.apple.security.files.user-selected.read-write 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ccxgui 2 | description: A new Flutter project. 3 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 4 | 5 | version: 0.6.0+1 6 | 7 | environment: 8 | sdk: ">=2.12.0 <3.0.0" 9 | 10 | dependencies: 11 | bloc: ^7.0.0 12 | bloc_test: ^8.1.0 13 | cupertino_icons: ^1.0.2 14 | equatable: ^2.0.3 15 | expandable: ^5.0.1 16 | file_selector: ^0.8.2 17 | file_selector_linux: ^0.0.2+1 18 | file_selector_macos: ^0.0.4+1 19 | file_selector_windows: ^0.0.2+1 20 | flutter: 21 | sdk: flutter 22 | flutter_bloc: ^7.1.0 23 | flutter_markdown: ^0.6.3 24 | flutter_svg: ^0.22.0 25 | http: ^0.13.3 26 | localstorage: 27 | git: 28 | url: https://git@github.com/Techno-Disaster/flutter_localstorage.git 29 | navigation_rail: 30 | git: 31 | url: https://git@github.com/Techno-Disaster/navigation_rail.git 32 | path_provider: ^2.0.2 33 | percent_indicator: ^3.0.1 34 | url_launcher: ^6.0.9 35 | window_size: 36 | git: 37 | url: https://git@github.com/google/flutter-desktop-embedding.git 38 | path: plugins/window_size 39 | provider: ^6.0.3 40 | platform: ^3.1.0 41 | desktop_drop: ^0.4.0 42 | file: ^6.1.4 43 | pedantic: ^1.11.1 44 | 45 | dev_dependencies: 46 | flutter_test: 47 | sdk: flutter 48 | build_runner: ^2.1.0 49 | hive_generator: ^1.1.0 50 | import_sorter: ^4.6.0 51 | 52 | flutter: 53 | uses-material-design: true 54 | assets: 55 | - assets/ 56 | -------------------------------------------------------------------------------- /snap/gui/ccextractor-gui.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=CCExtractpr GUI 3 | Comment=Extract subtitles from any media file. 4 | Exec=ccextractor-gui 5 | Icon=${SNAP}/meta/gui/ccextractor-gui.png 6 | Terminal=false 7 | Type=Application 8 | Categories=Media; -------------------------------------------------------------------------------- /snap/gui/ccextractor-gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCExtractor/ccextractorfluttergui/0dad21f205ed4018b4fc069cd050a45186258abc/snap/gui/ccextractor-gui.png -------------------------------------------------------------------------------- /snap/snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: ccxgui 2 | version: 0.1.0 3 | summary: Extract subtitles from any media file. 4 | description: CCExtractor is the de-factor open source standard for closed captions / subtitles extraction from any media file. While the software itself belong to the niche category (main users being universities, media companies and enthusiasts) its output (meaning the .srt files it generates) is used by millions. If you've ever downloaded an external subtitle file for a TV show - most likely the original file came from CCExtractor. 5 | 6 | confinement: strict 7 | base: core18 8 | grade: stable 9 | 10 | slots: 11 | dbus-ccxgui: 12 | interface: dbus 13 | bus: session 14 | name: org.ccextractor.gui 15 | 16 | parts: 17 | ccxgui: 18 | source: . 19 | plugin: flutter 20 | flutter-target: lib/main.dart # The main entry-point file of the application 21 | stage-packages: [ccextractor] 22 | 23 | 24 | apps: 25 | ccxgui: 26 | command: ccxgui 27 | extensions: [flutter-master] 28 | plugs: 29 | - home 30 | - network 31 | slots: 32 | - dbus-ccxgui 33 | -------------------------------------------------------------------------------- /test/bloc_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc_test/bloc_test.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | import 'package:ccxgui/bloc/settings_bloc/settings_bloc.dart'; 5 | import 'package:ccxgui/models/settings_model.dart'; 6 | import 'package:ccxgui/repositories/settings_repository.dart'; 7 | 8 | void main() { 9 | group('CounterBloc', () { 10 | SettingsRepository _settingsRepository = SettingsRepository(); 11 | SettingsModel settingsModel = SettingsModel(); 12 | 13 | blocTest( 14 | 'emits [] when nothing is added', 15 | build: () => SettingsBloc(_settingsRepository), 16 | expect: () => [], 17 | ); 18 | 19 | blocTest( 20 | 'emit default settings model when nothing is changed', 21 | build: () => SettingsBloc(_settingsRepository), 22 | act: (bloc) => bloc.add(SettingsUpdatedEvent(settingsModel)), 23 | // we don't care what's here so no expect. This just sets up config.json 24 | // properly if it is not created yet, example on GH actions 25 | ); 26 | blocTest( 27 | 'emits new settings model, settings saved correctly and getParamsList has the new setting', 28 | build: () => SettingsBloc(_settingsRepository), 29 | act: (bloc) { 30 | settingsModel.append = true; 31 | settingsModel.autoprogram = false; 32 | bloc.add(SettingsUpdatedEvent(settingsModel)); 33 | }, 34 | expect: () => [CurrentSettingsState(settingsModel)], 35 | verify: (_) { 36 | expect( 37 | _settingsRepository 38 | .getParamsList(settingsModel) 39 | .contains('--append'), 40 | true); 41 | expect( 42 | _settingsRepository 43 | .getParamsList(settingsModel) 44 | .contains('-autoprogram'), 45 | false); 46 | expect( 47 | _settingsRepository 48 | .getParamsList(settingsModel) 49 | .contains('some_random_nonexistent_param'), 50 | false); 51 | }); 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCExtractor/ccextractorfluttergui/0dad21f205ed4018b4fc069cd050a45186258abc/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCExtractor/ccextractorfluttergui/0dad21f205ed4018b4fc069cd050a45186258abc/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCExtractor/ccextractorfluttergui/0dad21f205ed4018b4fc069cd050a45186258abc/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCExtractor/ccextractorfluttergui/0dad21f205ed4018b4fc069cd050a45186258abc/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCExtractor/ccextractorfluttergui/0dad21f205ed4018b4fc069cd050a45186258abc/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ccxgui 30 | 31 | 32 | 33 | 36 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ccxgui", 3 | "short_name": "ccxgui", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(ccxgui LANGUAGES CXX) 3 | 4 | set(BINARY_NAME "ccxgui") 5 | 6 | cmake_policy(SET CMP0063 NEW) 7 | 8 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 9 | 10 | # Configure build options. 11 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 12 | if(IS_MULTICONFIG) 13 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 14 | CACHE STRING "" FORCE) 15 | else() 16 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 17 | set(CMAKE_BUILD_TYPE "Debug" CACHE 18 | STRING "Flutter build mode" FORCE) 19 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 20 | "Debug" "Profile" "Release") 21 | endif() 22 | endif() 23 | 24 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 25 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 26 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 27 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 28 | 29 | # Use Unicode for all projects. 30 | add_definitions(-DUNICODE -D_UNICODE) 31 | 32 | # Compilation settings that should be applied to most targets. 33 | function(APPLY_STANDARD_SETTINGS TARGET) 34 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 35 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 36 | target_compile_options(${TARGET} PRIVATE /EHsc) 37 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 38 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 39 | endfunction() 40 | 41 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 42 | 43 | # Flutter library and tool build rules. 44 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 45 | 46 | # Application build 47 | add_subdirectory("runner") 48 | 49 | # Generated plugin build rules, which manage building the plugins and adding 50 | # them to the application. 51 | include(flutter/generated_plugins.cmake) 52 | 53 | 54 | # === Installation === 55 | # Support files are copied into place next to the executable, so that it can 56 | # run in place. This is done instead of making a separate bundle (as on Linux) 57 | # so that building and running from within Visual Studio will work. 58 | set(BUILD_BUNDLE_DIR "$") 59 | # Make the "install" step default, as it's required to run. 60 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 61 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 62 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 63 | endif() 64 | 65 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 66 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 67 | 68 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 69 | COMPONENT Runtime) 70 | 71 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 72 | COMPONENT Runtime) 73 | 74 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 75 | COMPONENT Runtime) 76 | 77 | if(PLUGIN_BUNDLED_LIBRARIES) 78 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 79 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 80 | COMPONENT Runtime) 81 | endif() 82 | 83 | # Fully re-copy the assets directory on each build to avoid having stale files 84 | # from a previous install. 85 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 86 | install(CODE " 87 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 88 | " COMPONENT Runtime) 89 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 90 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 91 | 92 | # Install the AOT library on non-Debug builds only. 93 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 94 | CONFIGURATIONS Profile;Release 95 | COMPONENT Runtime) 96 | -------------------------------------------------------------------------------- /windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 4 | 5 | # Configuration provided via flutter tool. 6 | include(${EPHEMERAL_DIR}/generated_config.cmake) 7 | 8 | # TODO: Move the rest of this into files in ephemeral. See 9 | # https://github.com/flutter/flutter/issues/57146. 10 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 11 | 12 | # === Flutter Library === 13 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 14 | 15 | # Published to parent scope for install step. 16 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 17 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 18 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 19 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 20 | 21 | list(APPEND FLUTTER_LIBRARY_HEADERS 22 | "flutter_export.h" 23 | "flutter_windows.h" 24 | "flutter_messenger.h" 25 | "flutter_plugin_registrar.h" 26 | "flutter_texture_registrar.h" 27 | ) 28 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 29 | add_library(flutter INTERFACE) 30 | target_include_directories(flutter INTERFACE 31 | "${EPHEMERAL_DIR}" 32 | ) 33 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 34 | add_dependencies(flutter flutter_assemble) 35 | 36 | # === Wrapper === 37 | list(APPEND CPP_WRAPPER_SOURCES_CORE 38 | "core_implementations.cc" 39 | "standard_codec.cc" 40 | ) 41 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 42 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 43 | "plugin_registrar.cc" 44 | ) 45 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 46 | list(APPEND CPP_WRAPPER_SOURCES_APP 47 | "flutter_engine.cc" 48 | "flutter_view_controller.cc" 49 | ) 50 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 51 | 52 | # Wrapper sources needed for a plugin. 53 | add_library(flutter_wrapper_plugin STATIC 54 | ${CPP_WRAPPER_SOURCES_CORE} 55 | ${CPP_WRAPPER_SOURCES_PLUGIN} 56 | ) 57 | apply_standard_settings(flutter_wrapper_plugin) 58 | set_target_properties(flutter_wrapper_plugin PROPERTIES 59 | POSITION_INDEPENDENT_CODE ON) 60 | set_target_properties(flutter_wrapper_plugin PROPERTIES 61 | CXX_VISIBILITY_PRESET hidden) 62 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 63 | target_include_directories(flutter_wrapper_plugin PUBLIC 64 | "${WRAPPER_ROOT}/include" 65 | ) 66 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 67 | 68 | # Wrapper sources needed for the runner. 69 | add_library(flutter_wrapper_app STATIC 70 | ${CPP_WRAPPER_SOURCES_CORE} 71 | ${CPP_WRAPPER_SOURCES_APP} 72 | ) 73 | apply_standard_settings(flutter_wrapper_app) 74 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 75 | target_include_directories(flutter_wrapper_app PUBLIC 76 | "${WRAPPER_ROOT}/include" 77 | ) 78 | add_dependencies(flutter_wrapper_app flutter_assemble) 79 | 80 | # === Flutter tool backend === 81 | # _phony_ is a non-existent file to force this command to run every time, 82 | # since currently there's no way to get a full input/output list from the 83 | # flutter tool. 84 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 85 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 86 | add_custom_command( 87 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 88 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 89 | ${CPP_WRAPPER_SOURCES_APP} 90 | ${PHONY_OUTPUT} 91 | COMMAND ${CMAKE_COMMAND} -E env 92 | ${FLUTTER_TOOL_ENVIRONMENT} 93 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 94 | windows-x64 $ 95 | VERBATIM 96 | ) 97 | add_custom_target(flutter_assemble DEPENDS 98 | "${FLUTTER_LIBRARY}" 99 | ${FLUTTER_LIBRARY_HEADERS} 100 | ${CPP_WRAPPER_SOURCES_CORE} 101 | ${CPP_WRAPPER_SOURCES_PLUGIN} 102 | ${CPP_WRAPPER_SOURCES_APP} 103 | ) 104 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | void RegisterPlugins(flutter::PluginRegistry* registry) { 15 | DesktopDropPluginRegisterWithRegistrar( 16 | registry->GetRegistrarForPlugin("DesktopDropPlugin")); 17 | FileSelectorPluginRegisterWithRegistrar( 18 | registry->GetRegistrarForPlugin("FileSelectorPlugin")); 19 | UrlLauncherPluginRegisterWithRegistrar( 20 | registry->GetRegistrarForPlugin("UrlLauncherPlugin")); 21 | WindowSizePluginRegisterWithRegistrar( 22 | registry->GetRegistrarForPlugin("WindowSizePlugin")); 23 | } 24 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | desktop_drop 7 | file_selector_windows 8 | url_launcher_windows 9 | window_size 10 | ) 11 | 12 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 13 | ) 14 | 15 | set(PLUGIN_BUNDLED_LIBRARIES) 16 | 17 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 18 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 19 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 22 | endforeach(plugin) 23 | 24 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 25 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 27 | endforeach(ffi_plugin) 28 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(runner LANGUAGES CXX) 3 | 4 | add_executable(${BINARY_NAME} WIN32 5 | "flutter_window.cpp" 6 | "main.cpp" 7 | "utils.cpp" 8 | "win32_window.cpp" 9 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 10 | "Runner.rc" 11 | "runner.exe.manifest" 12 | ) 13 | apply_standard_settings(${BINARY_NAME}) 14 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 15 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 16 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 17 | add_dependencies(${BINARY_NAME} flutter_assemble) 18 | -------------------------------------------------------------------------------- /windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\ccx.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #ifdef FLUTTER_BUILD_NUMBER 64 | #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0 67 | #endif 68 | 69 | #ifdef FLUTTER_BUILD_NAME 70 | #define VERSION_AS_STRING #FLUTTER_BUILD_NAME 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "com.example" "\0" 93 | VALUE "FileDescription", "A new Flutter project." "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "ccxgui" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2021 com.example. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "ccxgui.exe" "\0" 98 | VALUE "ProductName", "ccxgui" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | return true; 30 | } 31 | 32 | void FlutterWindow::OnDestroy() { 33 | if (flutter_controller_) { 34 | flutter_controller_ = nullptr; 35 | } 36 | 37 | Win32Window::OnDestroy(); 38 | } 39 | 40 | LRESULT 41 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 42 | WPARAM const wparam, 43 | LPARAM const lparam) noexcept { 44 | // Give Flutter, including plugins, an opportunity to handle window messages. 45 | if (flutter_controller_) { 46 | std::optional result = 47 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 48 | lparam); 49 | if (result) { 50 | return *result; 51 | } 52 | } 53 | 54 | switch (message) { 55 | case WM_FONTCHANGE: 56 | flutter_controller_->engine()->ReloadSystemFonts(); 57 | break; 58 | } 59 | 60 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 61 | } 62 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } else { 15 | AllocConsole(); 16 | ShowWindow(GetConsoleWindow(), SW_HIDE); 17 | } 18 | // Initialize COM, so that it is available for use in the library and/or 19 | // plugins. 20 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 21 | 22 | flutter::DartProject project(L"data"); 23 | 24 | std::vector command_line_arguments = 25 | GetCommandLineArguments(); 26 | 27 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 28 | 29 | FlutterWindow window(project); 30 | Win32Window::Point origin(10, 10); 31 | Win32Window::Size size(1280, 720); 32 | if (!window.CreateAndShow(L"ccxgui", origin, size)) { 33 | return EXIT_FAILURE; 34 | } 35 | window.SetQuitOnClose(true); 36 | 37 | ::MSG msg; 38 | while (::GetMessage(&msg, nullptr, 0, 0)) { 39 | ::TranslateMessage(&msg); 40 | ::DispatchMessage(&msg); 41 | } 42 | 43 | ::CoUninitialize(); 44 | return EXIT_SUCCESS; 45 | } 46 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /windows/runner/resources/ccx.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCExtractor/ccextractorfluttergui/0dad21f205ed4018b4fc069cd050a45186258abc/windows/runner/resources/ccx.ico -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr); 51 | if (target_length == 0) { 52 | return std::string(); 53 | } 54 | std::string utf8_string; 55 | utf8_string.resize(target_length); 56 | int converted_length = ::WideCharToMultiByte( 57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 58 | -1, utf8_string.data(), 59 | target_length, nullptr, nullptr); 60 | if (converted_length == 0) { 61 | return std::string(); 62 | } 63 | return utf8_string; 64 | } 65 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /windows/runner/win32_window.cpp: -------------------------------------------------------------------------------- 1 | #include "win32_window.h" 2 | 3 | #include 4 | 5 | #include "resource.h" 6 | 7 | namespace { 8 | 9 | constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; 10 | 11 | // The number of Win32Window objects that currently exist. 12 | static int g_active_window_count = 0; 13 | 14 | using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); 15 | 16 | // Scale helper to convert logical scaler values to physical using passed in 17 | // scale factor 18 | int Scale(int source, double scale_factor) { 19 | return static_cast(source * scale_factor); 20 | } 21 | 22 | // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. 23 | // This API is only needed for PerMonitor V1 awareness mode. 24 | void EnableFullDpiSupportIfAvailable(HWND hwnd) { 25 | HMODULE user32_module = LoadLibraryA("User32.dll"); 26 | if (!user32_module) { 27 | return; 28 | } 29 | auto enable_non_client_dpi_scaling = 30 | reinterpret_cast( 31 | GetProcAddress(user32_module, "EnableNonClientDpiScaling")); 32 | if (enable_non_client_dpi_scaling != nullptr) { 33 | enable_non_client_dpi_scaling(hwnd); 34 | FreeLibrary(user32_module); 35 | } 36 | } 37 | 38 | } // namespace 39 | 40 | // Manages the Win32Window's window class registration. 41 | class WindowClassRegistrar { 42 | public: 43 | ~WindowClassRegistrar() = default; 44 | 45 | // Returns the singleton registar instance. 46 | static WindowClassRegistrar* GetInstance() { 47 | if (!instance_) { 48 | instance_ = new WindowClassRegistrar(); 49 | } 50 | return instance_; 51 | } 52 | 53 | // Returns the name of the window class, registering the class if it hasn't 54 | // previously been registered. 55 | const wchar_t* GetWindowClass(); 56 | 57 | // Unregisters the window class. Should only be called if there are no 58 | // instances of the window. 59 | void UnregisterWindowClass(); 60 | 61 | private: 62 | WindowClassRegistrar() = default; 63 | 64 | static WindowClassRegistrar* instance_; 65 | 66 | bool class_registered_ = false; 67 | }; 68 | 69 | WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; 70 | 71 | const wchar_t* WindowClassRegistrar::GetWindowClass() { 72 | if (!class_registered_) { 73 | WNDCLASS window_class{}; 74 | window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); 75 | window_class.lpszClassName = kWindowClassName; 76 | window_class.style = CS_HREDRAW | CS_VREDRAW; 77 | window_class.cbClsExtra = 0; 78 | window_class.cbWndExtra = 0; 79 | window_class.hInstance = GetModuleHandle(nullptr); 80 | window_class.hIcon = 81 | LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); 82 | window_class.hbrBackground = 0; 83 | window_class.lpszMenuName = nullptr; 84 | window_class.lpfnWndProc = Win32Window::WndProc; 85 | RegisterClass(&window_class); 86 | class_registered_ = true; 87 | } 88 | return kWindowClassName; 89 | } 90 | 91 | void WindowClassRegistrar::UnregisterWindowClass() { 92 | UnregisterClass(kWindowClassName, nullptr); 93 | class_registered_ = false; 94 | } 95 | 96 | Win32Window::Win32Window() { 97 | ++g_active_window_count; 98 | } 99 | 100 | Win32Window::~Win32Window() { 101 | --g_active_window_count; 102 | Destroy(); 103 | } 104 | 105 | bool Win32Window::CreateAndShow(const std::wstring& title, 106 | const Point& origin, 107 | const Size& size) { 108 | Destroy(); 109 | 110 | const wchar_t* window_class = 111 | WindowClassRegistrar::GetInstance()->GetWindowClass(); 112 | 113 | const POINT target_point = {static_cast(origin.x), 114 | static_cast(origin.y)}; 115 | HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); 116 | UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); 117 | double scale_factor = dpi / 96.0; 118 | 119 | HWND window = CreateWindow( 120 | window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 121 | Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), 122 | Scale(size.width, scale_factor), Scale(size.height, scale_factor), 123 | nullptr, nullptr, GetModuleHandle(nullptr), this); 124 | 125 | if (!window) { 126 | return false; 127 | } 128 | 129 | return OnCreate(); 130 | } 131 | 132 | // static 133 | LRESULT CALLBACK Win32Window::WndProc(HWND const window, 134 | UINT const message, 135 | WPARAM const wparam, 136 | LPARAM const lparam) noexcept { 137 | if (message == WM_NCCREATE) { 138 | auto window_struct = reinterpret_cast(lparam); 139 | SetWindowLongPtr(window, GWLP_USERDATA, 140 | reinterpret_cast(window_struct->lpCreateParams)); 141 | 142 | auto that = static_cast(window_struct->lpCreateParams); 143 | EnableFullDpiSupportIfAvailable(window); 144 | that->window_handle_ = window; 145 | } else if (Win32Window* that = GetThisFromHandle(window)) { 146 | return that->MessageHandler(window, message, wparam, lparam); 147 | } 148 | 149 | return DefWindowProc(window, message, wparam, lparam); 150 | } 151 | 152 | LRESULT 153 | Win32Window::MessageHandler(HWND hwnd, 154 | UINT const message, 155 | WPARAM const wparam, 156 | LPARAM const lparam) noexcept { 157 | switch (message) { 158 | case WM_DESTROY: 159 | window_handle_ = nullptr; 160 | Destroy(); 161 | if (quit_on_close_) { 162 | PostQuitMessage(0); 163 | } 164 | return 0; 165 | 166 | case WM_DPICHANGED: { 167 | auto newRectSize = reinterpret_cast(lparam); 168 | LONG newWidth = newRectSize->right - newRectSize->left; 169 | LONG newHeight = newRectSize->bottom - newRectSize->top; 170 | 171 | SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, 172 | newHeight, SWP_NOZORDER | SWP_NOACTIVATE); 173 | 174 | return 0; 175 | } 176 | case WM_SIZE: { 177 | RECT rect = GetClientArea(); 178 | if (child_content_ != nullptr) { 179 | // Size and position the child window. 180 | MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, 181 | rect.bottom - rect.top, TRUE); 182 | } 183 | return 0; 184 | } 185 | 186 | case WM_ACTIVATE: 187 | if (child_content_ != nullptr) { 188 | SetFocus(child_content_); 189 | } 190 | return 0; 191 | } 192 | 193 | return DefWindowProc(window_handle_, message, wparam, lparam); 194 | } 195 | 196 | void Win32Window::Destroy() { 197 | OnDestroy(); 198 | 199 | if (window_handle_) { 200 | DestroyWindow(window_handle_); 201 | window_handle_ = nullptr; 202 | } 203 | if (g_active_window_count == 0) { 204 | WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); 205 | } 206 | } 207 | 208 | Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { 209 | return reinterpret_cast( 210 | GetWindowLongPtr(window, GWLP_USERDATA)); 211 | } 212 | 213 | void Win32Window::SetChildContent(HWND content) { 214 | child_content_ = content; 215 | SetParent(content, window_handle_); 216 | RECT frame = GetClientArea(); 217 | 218 | MoveWindow(content, frame.left, frame.top, frame.right - frame.left, 219 | frame.bottom - frame.top, true); 220 | 221 | SetFocus(child_content_); 222 | } 223 | 224 | RECT Win32Window::GetClientArea() { 225 | RECT frame; 226 | GetClientRect(window_handle_, &frame); 227 | return frame; 228 | } 229 | 230 | HWND Win32Window::GetHandle() { 231 | return window_handle_; 232 | } 233 | 234 | void Win32Window::SetQuitOnClose(bool quit_on_close) { 235 | quit_on_close_ = quit_on_close; 236 | } 237 | 238 | bool Win32Window::OnCreate() { 239 | // No-op; provided for subclasses. 240 | return true; 241 | } 242 | 243 | void Win32Window::OnDestroy() { 244 | // No-op; provided for subclasses. 245 | } 246 | -------------------------------------------------------------------------------- /windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates and shows a win32 window with |title| and position and size using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size to will treat the width height passed in to this function 35 | // as logical pixels and scale to appropriate for the default monitor. Returns 36 | // true if the window was created successfully. 37 | bool CreateAndShow(const std::wstring& title, 38 | const Point& origin, 39 | const Size& size); 40 | 41 | // Release OS resources associated with window. 42 | void Destroy(); 43 | 44 | // Inserts |content| into the window tree. 45 | void SetChildContent(HWND content); 46 | 47 | // Returns the backing Window handle to enable clients to set icon and other 48 | // window properties. Returns nullptr if the window has been destroyed. 49 | HWND GetHandle(); 50 | 51 | // If true, closing this window will quit the application. 52 | void SetQuitOnClose(bool quit_on_close); 53 | 54 | // Return a RECT representing the bounds of the current client area. 55 | RECT GetClientArea(); 56 | 57 | protected: 58 | // Processes and route salient window messages for mouse handling, 59 | // size change and DPI. Delegates handling of these to member overloads that 60 | // inheriting classes can handle. 61 | virtual LRESULT MessageHandler(HWND window, 62 | UINT const message, 63 | WPARAM const wparam, 64 | LPARAM const lparam) noexcept; 65 | 66 | // Called when CreateAndShow is called, allowing subclass window-related 67 | // setup. Subclasses should return false if setup fails. 68 | virtual bool OnCreate(); 69 | 70 | // Called when Destroy is called. 71 | virtual void OnDestroy(); 72 | 73 | private: 74 | friend class WindowClassRegistrar; 75 | 76 | // OS callback called by message pump. Handles the WM_NCCREATE message which 77 | // is passed when the non-client area is being created and enables automatic 78 | // non-client DPI scaling so that the non-client area automatically 79 | // responsponds to changes in DPI. All other messages are handled by 80 | // MessageHandler. 81 | static LRESULT CALLBACK WndProc(HWND const window, 82 | UINT const message, 83 | WPARAM const wparam, 84 | LPARAM const lparam) noexcept; 85 | 86 | // Retrieves a class instance pointer for |window| 87 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 88 | 89 | bool quit_on_close_ = false; 90 | 91 | // window handle for top level window. 92 | HWND window_handle_ = nullptr; 93 | 94 | // window handle for hosted content. 95 | HWND child_content_ = nullptr; 96 | }; 97 | 98 | #endif // RUNNER_WIN32_WINDOW_H_ 99 | --------------------------------------------------------------------------------