Switch to fedora.

This commit is contained in:
stonega 2020-10-28 20:10:43 +08:00
parent aa3bbb2b4b
commit e62c222efe
137 changed files with 35613 additions and 35630 deletions

View File

@ -1,28 +1,28 @@
version: 2 version: 2
jobs: jobs:
build: build:
docker: docker:
- image: cirrusci/flutter:stable - image: cirrusci/flutter:stable
branches: branches:
only: master only: master
steps: steps:
- checkout - checkout
- run: - run:
name: Run Flutter doctor name: Run Flutter doctor
command: flutter doctor command: flutter doctor
- run: - run:
name: Update package name: Update package
command: flutter pub upgrade command: flutter pub upgrade
- run: echo $ENCODED_KEYSTORE | base64 -di > ${HOME}/keystore.jks - run: echo $ENCODED_KEYSTORE | base64 -di > ${HOME}/keystore.jks
- run: echo 'export KEYSTORE=${HOME}/keystore.jks' >> $BASH_ENV - run: echo 'export KEYSTORE=${HOME}/keystore.jks' >> $BASH_ENV
- run: dart tool/env.dart - run: dart tool/env.dart
- run: - run:
name: Build the Android apk name: Build the Android apk
command: flutter build apk --split-per-abi --obfuscate --split-debug-info=debug/ command: flutter build apk --split-per-abi --obfuscate --split-debug-info=debug/
- run: - run:
name: Build the Android version name: Build the Android version
command: flutter build appbundle --obfuscate --split-debug-info=debug/ command: flutter build appbundle --obfuscate --split-debug-info=debug/
- store_artifacts: - store_artifacts:
path: build/app/outputs/ path: build/app/outputs/

24
.github/FUNDING.yml vendored
View File

@ -1,12 +1,12 @@
# These are supported funding model platforms # These are supported funding model platforms
github: # Replace with up to 4buy-enabled usernames e.g., [user1, user2] github: # Replace with up to 4buy-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: stonegate # Replace with a single Liberapay username liberapay: stonegate # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username otechie: # Replace with a single Otechie username
custom: ["https://www.buymeacoffee.com/stonegate"] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] custom: ["https://www.buymeacoffee.com/stonegate"] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@ -1,43 +1,40 @@
name: Flutter Build name: Flutter Build
on: on:
push: push:
branches: branches:
- master - master
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- uses: actions/setup-java@v1 - uses: actions/setup-java@v1
with: with:
java-version: "12.x" java-version: "12.x"
- uses: subosito/flutter-action@v1 - uses: subosito/flutter-action@v1
with: with:
channel: "stable" # 'dev', 'alpha', default to: 'stable' channel: "stable" # 'dev', 'alpha', default to: 'stable'
- run: flutter pub get - run: flutter pub get
- run: echo $ENCODED_KEYSTORE | base64 -di > android/app/keystore.jks - run: echo $ENCODED_KEYSTORE | base64 -di > android/app/keystore.jks
env: env:
ENCODED_KEYSTORE: ${{ secrets.ENCODED_KEYSTORE }} ENCODED_KEYSTORE: ${{ secrets.ENCODED_KEYSTORE }}
- run: dart tool/env.dart - run: dart tool/env.dart
env: env:
API_KEY: ${{ secrets.API_KEY }} API_KEY: ${{ secrets.API_KEY }}
PI_API_SECRET: ${{ secrets.PI_API_SECRET}} PI_API_SECRET: ${{ secrets.PI_API_SECRET}}
PI_API_KEY: ${{ secrets.PI_API_KEY}} PI_API_KEY: ${{ secrets.PI_API_KEY}}
- run: flutter build apk --split-per-abi --obfuscate --split-debug-info=debug/ - run: flutter build apk --split-per-abi --obfuscate --split-debug-info=debug/
env: env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }} KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD}} KEY_PASSWORD: ${{ secrets.KEY_PASSWORD}}
- run: flutter build appbundle --obfuscate --split-debug-info=debug/ - run: flutter build appbundle --obfuscate --split-debug-info=debug/
env: env:
API_KEY: ${{ secrets.API_KEY }} KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }} KEY_PASSWORD: ${{ secrets.KEY_PASSWORD}}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD}} - uses: actions/upload-artifact@v2
PI_API_SECRET: ${{ secrets.PI_API_SECRET}} with:
PI_API_KEY: ${{ secrets.PI_API_KEY}} name: release-file
- uses: actions/upload-artifact@v2 path: build/app/outputs/**/release/*
with:
name: release-apk
path: build/app/outputs/**/release/*

82
.gitignore vendored
View File

@ -1,41 +1,41 @@
# Miscellaneous # Miscellaneous
*.class *.class
*.log *.log
*.pyc *.pyc
*.swp *.swp
.DS_Store .DS_Store
.atom/ .atom/
.buildlog/ .buildlog/
.history .history
.svn/ .svn/
# IntelliJ related # IntelliJ related
*.iml *.iml
*.ipr *.ipr
*.iws *.iws
.idea/ .idea/
# The .vscode folder contains launch configuration and tasks you configure in # The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line # VS Code which you may wish to be included in version control, so this line
# is commented out by default. # is commented out by default.
#.vscode/ #.vscode/
# Flutter/Dart/Pub related # Flutter/Dart/Pub related
**/doc/api/ **/doc/api/
.dart_tool/ .dart_tool/
.flutter-plugins .flutter-plugins
.flutter-plugins-dependencies .flutter-plugins-dependencies
.packages .packages
.pub-cache/ .pub-cache/
.pub/ .pub/
/lib/.env.dart /lib/.env.dart
/build/ /build/
pubspec.lock pubspec.lock
.vscode .vscode
analysis_options.yaml analysis_options.yaml
# Web related # Web related
lib/generated_plugin_registrant.dart lib/generated_plugin_registrant.dart
# Exceptions to above rules. # Exceptions to above rules.
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages

View File

@ -1,10 +1,10 @@
# This file tracks properties of this Flutter project. # This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc. # Used by Flutter tool to assess capabilities and perform upgrades etc.
# #
# This file should be version controlled and should not be manually edited. # This file should be version controlled and should not be manually edited.
version: version:
revision: 659dc8129d4edb9166e9a0d600439d135740933f revision: 659dc8129d4edb9166e9a0d600439d135740933f
channel: beta channel: beta
project_type: app project_type: app

View File

@ -1,331 +1,331 @@
# Tsacdop Changelog # Tsacdop Changelog
## 0.5.0 ## 0.5.0
Release date 2020/10/13 Release date 2020/10/13
### New fewtures ### New fewtures
* Support multi select on recent and favorite tab. * Support multi select on recent and favorite tab.
* Select all/ select before/ select after. * Select all/ select before/ select after.
* Option to delete episode download file when played. * Option to delete episode download file when played.
* OPtion to mark as listened after skipped. * OPtion to mark as listened after skipped.
### Bug fixed ### Bug fixed
* Feed pubdate parse error. * Feed pubdate parse error.
* Episodes load with initial position failed. * Episodes load with initial position failed.
### Minor changes ### Minor changes
* Single colume layout update. * Single colume layout update.
* About page UI update. * About page UI update.
* More smooth animation when open podcast detail page. * More smooth animation when open podcast detail page.
* Change sort by button style in podcast detail page. * Change sort by button style in podcast detail page.
* Auto rewind 3 seconds when resuming from paused state. * Auto rewind 3 seconds when resuming from paused state.
## 0.4.20 ## 0.4.20
Release date 2020/10/3 Release date 2020/10/3
### Bug fixed ### Bug fixed
* Rss feed parse error. * Rss feed parse error.
## 0.4.19 ## 0.4.19
Release date 2020/10/1 Release date 2020/10/1
### New features ### New features
* Set podcastindex as default search engine. * Set podcastindex as default search engine.
* Option to hide podcast discovery in search page. * Option to hide podcast discovery in search page.
* Italian translation support, thanks Edoardo. * Italian translation support, thanks Edoardo.
### Bug fixed ### Bug fixed
* Mark all listened error. * Mark all listened error.
## 0.4.18 ## 0.4.18
Release date 2020/9/27 Release date 2020/9/27
### New features ### New features
* Support gpodder.net sync. * Support gpodder.net sync.
* Portuguese translation, thanks Bruno. * Portuguese translation, thanks Bruno.
* Turn off auto update for podcast. * Turn off auto update for podcast.
* Pull to refresh in recent tab, supports group update. * Pull to refresh in recent tab, supports group update.
### Minor changes ### Minor changes
* Longpress 'see all' to open full podcast list. * Longpress 'see all' to open full podcast list.
## 0.4.17 ## 0.4.17
Release date 2020/9/16 Release date 2020/9/16
### Bug fixed ### Bug fixed
* Remove notification after app removed from recent. * Remove notification after app removed from recent.
## 0.4.16 ## 0.4.16
Release date 2020/9/15 Release date 2020/9/15
### New features ### New features
* Discovery feature in search page. * Discovery feature in search page.
* Multi select in podcast page. * Multi select in podcast page.
* Customize the speed options available. * Customize the speed options available.
### Bugs fixed ### Bugs fixed
* Fix download error when podcast name includes /. * Fix download error when podcast name includes /.
* Make the group name editable directly. * Make the group name editable directly.
* Fixed shownote timestamp click error. * Fixed shownote timestamp click error.
### Minor changes ### Minor changes
* Update donate button UI. * Update donate button UI.
## 0.4.15 ## 0.4.15
Release date 2020/8/30 Release date 2020/8/30
### New features ### New features
* Option to change notification panel layout. * Option to change notification panel layout.
* Option to change show notes font style. * Option to change show notes font style.
* Option to hide listened default. * Option to hide listened default.
* Change skip next/previous to fastForward/rewind on headset click. * Change skip next/previous to fastForward/rewind on headset click.
### Bugs fixed ### Bugs fixed
* Download error when filename too long. * Download error when filename too long.
### Minor change ### Minor change
* Update download button style and downloaded indicator style. * Update download button style and downloaded indicator style.
* Add 1.1 to speed setting. * Add 1.1 to speed setting.
* Add 5s to skip seconds setting. * Add 5s to skip seconds setting.
## 0.4.14 ## 0.4.14
Release date 2020/8/20 Release date 2020/8/20
Only for izzyonandroid. Only for izzyonandroid.
## 0.4.13 ## 0.4.13
Release date 2020/8/19 Release date 2020/8/19
### Bugs fixed ### Bugs fixed
* Downloaded episode play error, you might need to redownload the episode. * Downloaded episode play error, you might need to redownload the episode.
## 0.4.12 ## 0.4.12
Release date 2020/8/15 Release date 2020/8/15
### Bugs fixed ### Bugs fixed
* Crash when reorder episodes or podcasts. * Crash when reorder episodes or podcasts.
* Popup menu setting import bug. * Popup menu setting import bug.
* Default language failed to load. * Default language failed to load.
### Minor changes ### Minor changes
* Change language|feedback|podcast settings to button sheet. * Change language|feedback|podcast settings to button sheet.
* Add history in home playlist button. * Add history in home playlist button.
* History page UI improved. * History page UI improved.
## 0.4.11 ## 0.4.11
Release date 2020/8/12 Release date 2020/8/12
### New features ### New features
* Boost volume. You can change boost level in settings. * Boost volume. You can change boost level in settings.
* You can tap time stamp to skip instantly in shownote when the episode is playing. * You can tap time stamp to skip instantly in shownote when the episode is playing.
* Add history list in playlist page. * Add history list in playlist page.
* You can also mark not listened now. * You can also mark not listened now.
### Minor change ### Minor change
* Improved time picker UI. * Improved time picker UI.
* Add episode setting page. * Add episode setting page.
### Bugs fix ### Bugs fix
* Play record didn't saved after stop playing. * Play record didn't saved after stop playing.
* Network error message didn't disapear after skip to next. * Network error message didn't disapear after skip to next.
* Fireside avatar load error. * Fireside avatar load error.
## 0.4.10 ## 0.4.10
Release date 2020/8/6 Release date 2020/8/6
### Bugs fixed ### Bugs fixed
* Episdoe date parse error. * Episdoe date parse error.
* Play from start after interrupt. * Play from start after interrupt.
* Playlist in player unstable. * Playlist in player unstable.
* Language setting not saved after restart app. * Language setting not saved after restart app.
### Minor change ### Minor change
* Fast forward and rewind buttons UI changed. * Fast forward and rewind buttons UI changed.
## v0.4.9 ## v0.4.9
Release date 2020/8/1 Release date 2020/8/1
### New features ### New features
* Player UI redesign. * Player UI redesign.
* Added player height setting. * Added player height setting.
* Added skip silence feature. * Added skip silence feature.
### Bugs fixed ### Bugs fixed
* Language fixs. (Thanks to Atrate) * Language fixs. (Thanks to Atrate)
* Make app movable to SD card. (Thanks to Atrate) * Make app movable to SD card. (Thanks to Atrate)
### Minor change ### Minor change
* Episode page open animation improved. * Episode page open animation improved.
* Podcast page load faster than before. * Podcast page load faster than before.
* Removed unnecessary scroll overlay effect. * Removed unnecessary scroll overlay effect.
* Episode page bottom menu hide when scroll down. * Episode page bottom menu hide when scroll down.
## v0.4.8 ## v0.4.8
Release date 2020/7/25 Release date 2020/7/25
### New features ### New features
* Filter in podcast detail page, you can also hide listened episodes. * Filter in podcast detail page, you can also hide listened episodes.
* Search result ui improved, you can see more info for result. * Search result ui improved, you can see more info for result.
* Update audio service to latest version. * Update audio service to latest version.
* Support fast forward seconds and rewind seconds customize. * Support fast forward seconds and rewind seconds customize.
* Add Franch language support(beta). * Add Franch language support(beta).
* Add translators in about page. * Add translators in about page.
### Bugs fixed ### Bugs fixed
* Icon issue on below android 8 devices. * Icon issue on below android 8 devices.
### Minor change ### Minor change
* Download button ui improved. * Download button ui improved.
* Title changed to scrollable in episode detail page. * Title changed to scrollable in episode detail page.
* Real dark theme improved. * Real dark theme improved.
* Add dot indicator in popup menu. * Add dot indicator in popup menu.
* Tap logo in homepage to toggle theme. * Tap logo in homepage to toggle theme.
## v0.4.7 ## v0.4.7
Release date 2020/7/18 Release date 2020/7/18
### Bugs fixed ### Bugs fixed
* Ompl files form other platform import error. * Ompl files form other platform import error.
* Audio cache did't work. * Audio cache did't work.
## v0.4.6 ## v0.4.6
Release date 2020/7/17 Release date 2020/7/17
### Bugs fixed ### Bugs fixed
* Mark listened not work. * Mark listened not work.
* Recover subscribe wrong group. * Recover subscribe wrong group.
## v0.4.5 ## v0.4.5
Release date 2020/7/16 Release date 2020/7/16
## New features ## New features
* OPML backup file supports group. * OPML backup file supports group.
* Add settings backup and restore. * Add settings backup and restore.
* Enable R8 and dart obfuscate. * Enable R8 and dart obfuscate.
## Bugs fixed ## Bugs fixed
* OPML import not shown in group. * OPML import not shown in group.
## Minor UI change ## Minor UI change
* Tap logo in homepage to toggle theme mode. * Tap logo in homepage to toggle theme mode.
* Change subscribe button style. * Change subscribe button style.
* Improve history chart style. * Improve history chart style.
## v0.4.0 ## v0.4.0
Release date 2020/7/9 Release date 2020/7/9
### New features ### New features
* Localization, changed all UI strings in app to support locale, support languages include en & zh right now. * Localization, changed all UI strings in app to support locale, support languages include en & zh right now.
* Changed episode popup menu UI, add a switch to tap to open popup men. * Changed episode popup menu UI, add a switch to tap to open popup men.
### Minor UI change ### Minor UI change
* Improve dropdown menu UI. * Improve dropdown menu UI.
* Change icons color in setting page. * Change icons color in setting page.
* Improve player panel animation. * Improve player panel animation.
* Add scroll bar in libraries page. * Add scroll bar in libraries page.
## v0.3.6 ## v0.3.6
Release date 2020/6/30 Release date 2020/6/30
### New feature ### New feature
* Add sleep timer settings. include default time, auto start sleep timer, etc. * Add sleep timer settings. include default time, auto start sleep timer, etc.
### Bug fixed ### Bug fixed
* Crash on stop player. * Crash on stop player.
* Some download file didn't auto deleted. * Some download file didn't auto deleted.
## v0.3.5 ## v0.3.5
Release date 2020/6/20 Release date 2020/6/20
This is a energency release. This is a energency release.
### Bugs fixed ### Bugs fixed
* Crashed in download page or button after remove a podcast. Add episode check when load tasks from flutterdownloader. * Crashed in download page or button after remove a podcast. Add episode check when load tasks from flutterdownloader.
### Minor UI change ### Minor UI change
* Add buy me a coffee in about page. * Add buy me a coffee in about page.
* Remove progress number in download list in failed task, change refresh icon color to red. * Remove progress number in download list in failed task, change refresh icon color to red.
## v0.3.4 ## v0.3.4
Release date 2020/6/16 Release date 2020/6/16
### New Feature ### New Feature
* Support auto download new episodes, you can choose which podcast you want to auto download, you can also set if download using cellular data. * Support auto download new episodes, you can choose which podcast you want to auto download, you can also set if download using cellular data.
* Support auto delete downloaded episode, you can set days before delete. * Support auto delete downloaded episode, you can set days before delete.
* Support customize episode popup menu, you can add options you most want, **Like** | **Mark Listened** | **Download** newly added. * Support customize episode popup menu, you can add options you most want, **Like** | **Mark Listened** | **Download** newly added.
* Improved downloaded file manager, you can now sort downloads by date or size, you can also only view listened downloads. * Improved downloaded file manager, you can now sort downloads by date or size, you can also only view listened downloads.
### Minor UI Change ### Minor UI Change
* Removed the listened indicator, increased the color difference for listened episodes. * Removed the listened indicator, increased the color difference for listened episodes.
* Add text in podcast manage page menu. * Add text in podcast manage page menu.
* Change episode shownote font to Martel. * Change episode shownote font to Martel.
### Bugs Fixed ### Bugs Fixed
* Auto play when receive notification. * Auto play when receive notification.
* Lose podcast when import OMPL file. * Lose podcast when import OMPL file.
### Other ### Other
* Add privacy policy. * Add privacy policy.

1348
LICENSE

File diff suppressed because it is too large Load Diff

384
README.md
View File

@ -1,192 +1,192 @@
[![Tsacdop Banner][]][google play] [![Tsacdop Banner][]][google play]
[![github action][]][github action link] [![github action][]][github action link]
[![Build Status - Cirrus][]][build status] [![Build Status - Cirrus][]][build status]
[![GitHub Release][]][github release - recent] [![GitHub Release][]][github release - recent]
[![Github Downloads][]][github release - recent] [![Github Downloads][]][github release - recent]
[![Localizely][]][localizely - website] [![Localizely][]][localizely - website]
[![style: effective dart][]][effective dart pub] [![style: effective dart][]][effective dart pub]
[![License badge][]][license] [![License badge][]][license]
## About ## About
Enjoy podcasts with Tsacdop. Enjoy podcasts with Tsacdop.
Tsacdop is a podcast player developed with Flutter, a clean, simply beautiful and friendly app, which is also free and open source. Tsacdop is a podcast player developed with Flutter, a clean, simply beautiful and friendly app, which is also free and open source.
Credit to Flutter team and all involved plugins, especially [webfeed](https://github.com/witochandra/webfeed) and [Just_Audio](https://pub.dev/packages/just_audio). Credit to Flutter team and all involved plugins, especially [webfeed](https://github.com/witochandra/webfeed) and [Just_Audio](https://pub.dev/packages/just_audio).
The podcast search engine is powered by [ListenNotes](https://listennotes.com). The podcast search engine is powered by [ListenNotes](https://listennotes.com).
## Features ## Features
* Podcast group management * Podcast group management
* Playlist support * Playlist support
* Sleep timer / speed setting * Sleep timer / speed setting
* OPML file export and import * OPML file export and import
* Auto syncing in background * Auto syncing in background
* Listening and subscription history record * Listening and subscription history record
* Dark mode / accent color * Dark mode / accent color
* Download for offline play * Download for offline play
* Auto download new episodes / auto delete outdated downloads * Auto download new episodes / auto delete outdated downloads
* Settings backup * Settings backup
* Skip silence * Skip silence
* Boost volume * Boost volume
More to come... More to come...
## Preview ## Preview
| Home Page | Group | Podcast | Episode| Dark Mode | | Home Page | Group | Podcast | Episode| Dark Mode |
| ----- | ----- | ----- | ------ | ----- | | ----- | ----- | ----- | ------ | ----- |
|![][Homepage ScreenShot]|![][Group Screenshot] | ![][Podcast Screenshot] | ![][Episode Screenshot]| ![][Darkmode Screenshot] | |![][Homepage ScreenShot]|![][Group Screenshot] | ![][Podcast Screenshot] | ![][Episode Screenshot]| ![][Darkmode Screenshot] |
## Localization ## Localization
Please [Email](mailto:<tsacdop.app@gmail.com>) me you'd like to contribute to support more languages! Please [Email](mailto:<tsacdop.app@gmail.com>) me you'd like to contribute to support more languages!
Credit to [Localizely](https://localizely.com/) for kind support to open source projects. Credit to [Localizely](https://localizely.com/) for kind support to open source projects.
### ![English] ### ![English]
### ![Chinese Simplified] ### ![Chinese Simplified]
### ![French] ### ![French]
### ![Spanish] ### ![Spanish]
### ![Portuguese] ### ![Portuguese]
## License ## License
Tsacdop is licensed under the [GPL v3.0](https://github.com/stonega/tsacdop/blob/master/LICENSE) license. Tsacdop is licensed under the [GPL v3.0](https://github.com/stonega/tsacdop/blob/master/LICENSE) license.
## Build ## Build
1. If you don't have Flutter SDK installed, please visit offcial [Flutter][Flutter Install] site. 1. If you don't have Flutter SDK installed, please visit offcial [Flutter][Flutter Install] site.
2. Fetch latest sorce code from master branch. 2. Fetch latest sorce code from master branch.
``` ```
git clone https://github.com/stonega/tsacdop.git git clone https://github.com/stonega/tsacdop.git
``` ```
3. Add api search api configure file. 3. Add api search api configure file.
Tsacdop uses ListenNotes API 1.0 pro to search for podcasts, which is not free, so I can not expose the API key in the repo. Tsacdop uses ListenNotes API 1.0 pro to search for podcasts, which is not free, so I can not expose the API key in the repo.
If you want to build the app, you need to create a new file named `.env.dart` in lib folder. Add the following code to `.env.dart` . If you want to build the app, you need to create a new file named `.env.dart` in lib folder. Add the following code to `.env.dart` .
You can get your own API key on [ListenNotes](https://www.listennotes.com/api/), remember that you need to get pro plan API, because basic plan dosen't provide rss link for serach result. If no api key is added, the search function in the app won't work. But you can still add podcasts by using an RSS link or importing an OMPL file. You can get your own API key on [ListenNotes](https://www.listennotes.com/api/), remember that you need to get pro plan API, because basic plan dosen't provide rss link for serach result. If no api key is added, the search function in the app won't work. But you can still add podcasts by using an RSS link or importing an OMPL file.
``` dart ``` dart
final environment = {"apiKey":"APIKEY"}; final environment = {"apiKey":"APIKEY"};
``` ```
4. Run the app with Android Studio or Visual Studio. Or the command line. 4. Run the app with Android Studio or Visual Studio. Or the command line.
``` ```
flutter pub get flutter pub get
flutter run flutter run
``` ```
## Contribute ## Contribute
If you have an issue or found a bug, please raise a GitHub issue. Pull requests are also welcome. If you have an issue or found a bug, please raise a GitHub issue. Pull requests are also welcome.
## Archetecture ## Archetecture
### Plugins ### Plugins
* Local storage * Local storage
+ sqflite + sqflite
+ shared_preferences + shared_preferences
* Audio * Audio
+ just_audio + just_audio
+ audio_service + audio_service
* State management * State management
+ provider + provider
* Download * Download
+ flutter_downloader + flutter_downloader
* Background task * Background task
+ workmanager + workmanager
### Directory Structure ### Directory Structure
``` ```
UI UI
src src
├──home ├──home
├──home.dart [Homepage] ├──home.dart [Homepage]
├──searc_podcast.dart [Search Page] ├──searc_podcast.dart [Search Page]
└──playlist.dart [Playlist Page] └──playlist.dart [Playlist Page]
├──podcasts ├──podcasts
├──podcast_manage.dart [Group Page] ├──podcast_manage.dart [Group Page]
└──podcast_detail.dart [Podcast Page] └──podcast_detail.dart [Podcast Page]
├──episodes ├──episodes
└──episode_detail.dart [Episode Page] └──episode_detail.dart [Episode Page]
├──settings ├──settings
└──setting.dart [Setting Page] └──setting.dart [Setting Page]
STATE STATE
src src
├──state ├──state
├──audio_state.dart [Audio State] ├──audio_state.dart [Audio State]
├──download_state.dart [Episode Download] ├──download_state.dart [Episode Download]
├──podcast_group.dart [Podcast Groups] ├──podcast_group.dart [Podcast Groups]
├──refresh_podcast.dart [Episode Refresh] ├──refresh_podcast.dart [Episode Refresh]
└──setting_state.dart [Setting] └──setting_state.dart [Setting]
Service Service
src src
├──service ├──service
├──api_service.dart [Podcast Search] ├──api_service.dart [Podcast Search]
└──ompl_builde.dart [OMPL export] └──ompl_builde.dart [OMPL export]
``` ```
## Known Issue ## Known Issue
* Playlist is unstable * Playlist is unstable
## Contact ## Contact
You can reach out to me directly at [tsacdop.app@gmail.com](mailto:<tsacdop.app@gmail.com>). You can reach out to me directly at [tsacdop.app@gmail.com](mailto:<tsacdop.app@gmail.com>).
Or you can join our [Telegram Group](https://t.me/joinchat/Bk3LkRpTHy40QYC78PK7Qg). Or you can join our [Telegram Group](https://t.me/joinchat/Bk3LkRpTHy40QYC78PK7Qg).
## Getting Started with Flutter ## Getting Started with Flutter
This project is a starting point for a Flutter application. This project is a starting point for a Flutter application.
Here are a few resources to get you started if this is your first Flutter project: Here are a few resources to get you started if this is your first Flutter project:
* [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) * [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
* [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) * [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
For help getting started with Flutter, view our For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials, samples, guidance on mobile development, and a full API reference. [online documentation](https://flutter.dev/docs), which offers tutorials, samples, guidance on mobile development, and a full API reference.
[Flutter Install]: https://flutter.dev/docs/get-started/install [Flutter Install]: https://flutter.dev/docs/get-started/install
[tsacdop banner]: https://raw.githubusercontent.com/stonega/tsacdop/master/preview/banner.png [tsacdop banner]: https://raw.githubusercontent.com/stonega/tsacdop/master/preview/banner.png
[build status - cirrus]: https://circleci.com/gh/stonega/tsacdop/tree/master.svg?style=shield [build status - cirrus]: https://circleci.com/gh/stonega/tsacdop/tree/master.svg?style=shield
[github action]: https://github.com/stonega/tsacdop/workflows/Flutter%20Build/badge.svg [github action]: https://github.com/stonega/tsacdop/workflows/Flutter%20Build/badge.svg
[github action link]: https://github.com/stonega/tsacdop/actions [github action link]: https://github.com/stonega/tsacdop/actions
[build status ]: https://circleci.com/gh/stonega/tsacdop/tree/master [build status ]: https://circleci.com/gh/stonega/tsacdop/tree/master
[github release]: https://img.shields.io/github/v/release/stonega/tsacdop [github release]: https://img.shields.io/github/v/release/stonega/tsacdop
[github release - recent]: https://github.com/stonega/tsacdop/releases [github release - recent]: https://github.com/stonega/tsacdop/releases
[github downloads]: https://img.shields.io/github/downloads/stonega/tsacdop/total?color=%230000d&label=downloads [github downloads]: https://img.shields.io/github/downloads/stonega/tsacdop/total?color=%230000d&label=downloads
[localizely]: https://img.shields.io/badge/dynamic/json?color=%2326c6da&label=localizely&query=%24.languages.length&url=https%3A%2F%2Fapi.localizely.com%2Fv1%2Fprojects%2Fbde4e9bd-4cb2-449b-9de2-18f231ddb47d%2Fstatus [localizely]: https://img.shields.io/badge/dynamic/json?color=%2326c6da&label=localizely&query=%24.languages.length&url=https%3A%2F%2Fapi.localizely.com%2Fv1%2Fprojects%2Fbde4e9bd-4cb2-449b-9de2-18f231ddb47d%2Fstatus
[English]: https://img.shields.io/badge/dynamic/json?style=for-the-badge&color=%2323CCC6&label=English&query=%24.languages%5B3%5D.reviewedProgress&url=https%3A%2F%2Fapi.localizely.com%2Fv1%2Fprojects%2Fbde4e9bd-4cb2-449b-9de2-18f231ddb47d%2Fstatus&suffix=% [English]: https://img.shields.io/badge/dynamic/json?style=for-the-badge&color=%2323CCC6&label=English&query=%24.languages%5B3%5D.reviewedProgress&url=https%3A%2F%2Fapi.localizely.com%2Fv1%2Fprojects%2Fbde4e9bd-4cb2-449b-9de2-18f231ddb47d%2Fstatus&suffix=%
[Chinese Simplified]: https://img.shields.io/badge/dynamic/json?style=for-the-badge&color=%2323CCC6&label=Chinese%20Simplified&query=%24.languages%5B2%5D.reviewedProgress&url=https%3A%2F%2Fapi.localizely.com%2Fv1%2Fprojects%2Fbde4e9bd-4cb2-449b-9de2-18f231ddb47d%2Fstatus&suffix=% [Chinese Simplified]: https://img.shields.io/badge/dynamic/json?style=for-the-badge&color=%2323CCC6&label=Chinese%20Simplified&query=%24.languages%5B2%5D.reviewedProgress&url=https%3A%2F%2Fapi.localizely.com%2Fv1%2Fprojects%2Fbde4e9bd-4cb2-449b-9de2-18f231ddb47d%2Fstatus&suffix=%
[French]: https://img.shields.io/badge/dynamic/json?style=for-the-badge&color=%2323CCC6&label=French(ppp)&query=%24.languages%5B5%5D.reviewedProgress&url=https%3A%2F%2Fapi.localizely.com%2Fv1%2Fprojects%2Fbde4e9bd-4cb2-449b-9de2-18f231ddb47d%2Fstatus&suffix=% [French]: https://img.shields.io/badge/dynamic/json?style=for-the-badge&color=%2323CCC6&label=French(ppp)&query=%24.languages%5B5%5D.reviewedProgress&url=https%3A%2F%2Fapi.localizely.com%2Fv1%2Fprojects%2Fbde4e9bd-4cb2-449b-9de2-18f231ddb47d%2Fstatus&suffix=%
[Spanish]: https://img.shields.io/badge/dynamic/json?style=for-the-badge&color=%2323CCC6&label=Spanish(Joel)&query=%24.languages%5B7%5D.reviewedProgress&url=https%3A%2F%2Fapi.localizely.com%2Fv1%2Fprojects%2Fbde4e9bd-4cb2-449b-9de2-18f231ddb47d%2Fstatus&suffix=% [Spanish]: https://img.shields.io/badge/dynamic/json?style=for-the-badge&color=%2323CCC6&label=Spanish(Joel)&query=%24.languages%5B7%5D.reviewedProgress&url=https%3A%2F%2Fapi.localizely.com%2Fv1%2Fprojects%2Fbde4e9bd-4cb2-449b-9de2-18f231ddb47d%2Fstatus&suffix=%
[Portuguese]: https://img.shields.io/badge/dynamic/json?style=for-the-badge&color=%2323CCC6&label=portuguese(Bruno)&query=%24.languages%5B9%5D.reviewedProgress&url=https%3A%2F%2Fapi.localizely.com%2Fv1%2Fprojects%2Fbde4e9bd-4cb2-449b-9de2-18f231ddb47d%2Fstatus&suffix=% [Portuguese]: https://img.shields.io/badge/dynamic/json?style=for-the-badge&color=%2323CCC6&label=portuguese(Bruno)&query=%24.languages%5B9%5D.reviewedProgress&url=https%3A%2F%2Fapi.localizely.com%2Fv1%2Fprojects%2Fbde4e9bd-4cb2-449b-9de2-18f231ddb47d%2Fstatus&suffix=%
[localizely - website]: https://localizely.com/ [localizely - website]: https://localizely.com/
[google play - icon]: https://img.shields.io/badge/google-playStore-%2323CCC6 [google play - icon]: https://img.shields.io/badge/google-playStore-%2323CCC6
[google play]: https://play.google.com/store/apps/details?id=com.stonegate.tsacdop [google play]: https://play.google.com/store/apps/details?id=com.stonegate.tsacdop
[Homepage ScreenShot]: https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585893838840.png [Homepage ScreenShot]: https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585893838840.png
[Group Screenshot]: https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585894051734.png [Group Screenshot]: https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585894051734.png
[Podcast Screenshot]: https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585893877702.png [Podcast Screenshot]: https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585893877702.png
[Episode Screenshot]: https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585896237809.png [Episode Screenshot]: https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585896237809.png
[Darkmode Screenshot]: https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585893920721.png [Darkmode Screenshot]: https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585893920721.png
[style: effective dart]: https://img.shields.io/badge/style-effective_dart-40c4ff.svg [style: effective dart]: https://img.shields.io/badge/style-effective_dart-40c4ff.svg
[effective dart pub]: https://pub.dev/packages/effective_dart [effective dart pub]: https://pub.dev/packages/effective_dart
[license]: https://github.com/stonega/tsacdop/blob/master/LICENSE [license]: https://github.com/stonega/tsacdop/blob/master/LICENSE
[License badge]: https://img.shields.io/badge/license-GPLv3-yellow.svg [License badge]: https://img.shields.io/badge/license-GPLv3-yellow.svg

14
android/.gitignore vendored
View File

@ -1,7 +1,7 @@
gradle-wrapper.jar gradle-wrapper.jar
/.gradle /.gradle
/captures/ /captures/
/gradlew /gradlew
/gradlew.bat /gradlew.bat
/local.properties /local.properties
GeneratedPluginRegistrant.java GeneratedPluginRegistrant.java

View File

@ -1,17 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<projectDescription> <projectDescription>
<name>android</name> <name>android</name>
<comment>Project android created by Buildship.</comment> <comment>Project android created by Buildship.</comment>
<projects> <projects>
</projects> </projects>
<buildSpec> <buildSpec>
<buildCommand> <buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name> <name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments> <arguments>
</arguments> </arguments>
</buildCommand> </buildCommand>
</buildSpec> </buildSpec>
<natures> <natures>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature> <nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures> </natures>
</projectDescription> </projectDescription>

View File

@ -1,13 +1,13 @@
arguments= arguments=
auto.sync=false auto.sync=false
build.scans.enabled=false build.scans.enabled=false
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
connection.project.dir=app connection.project.dir=app
eclipse.preferences.version=1 eclipse.preferences.version=1
gradle.user.home= gradle.user.home=
java.home=C\:/Program Files/Java/jdk1.8.0_171 java.home=C\:/Program Files/Java/jdk1.8.0_171
jvm.arguments= jvm.arguments=
offline.mode=false offline.mode=false
override.workspace.settings=true override.workspace.settings=true
show.console.view=true show.console.view=true
show.executions.view=true show.executions.view=true

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<classpath> <classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/> <classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/> <classpathentry kind="output" path="bin/default"/>
</classpath> </classpath>

View File

@ -1,23 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<projectDescription> <projectDescription>
<name>app</name> <name>app</name>
<comment>Project app created by Buildship.</comment> <comment>Project app created by Buildship.</comment>
<projects> <projects>
</projects> </projects>
<buildSpec> <buildSpec>
<buildCommand> <buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name> <name>org.eclipse.jdt.core.javabuilder</name>
<arguments> <arguments>
</arguments> </arguments>
</buildCommand> </buildCommand>
<buildCommand> <buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name> <name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments> <arguments>
</arguments> </arguments>
</buildCommand> </buildCommand>
</buildSpec> </buildSpec>
<natures> <natures>
<nature>org.eclipse.jdt.core.javanature</nature> <nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature> <nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures> </natures>
</projectDescription> </projectDescription>

View File

@ -1,13 +1,13 @@
arguments= arguments=
auto.sync=false auto.sync=false
build.scans.enabled=false build.scans.enabled=false
connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(6.3)) connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(6.3))
connection.project.dir= connection.project.dir=
eclipse.preferences.version=1 eclipse.preferences.version=1
gradle.user.home= gradle.user.home=
java.home=C\:/Program Files/Java/jdk1.8.0_171 java.home=C\:/Program Files/Java/jdk1.8.0_171
jvm.arguments= jvm.arguments=
offline.mode=false offline.mode=false
override.workspace.settings=true override.workspace.settings=true
show.console.view=true show.console.view=true
show.executions.view=true show.executions.view=true

View File

@ -1,4 +1,4 @@
eclipse.preferences.version=1 eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.source=1.8 org.eclipse.jdt.core.compiler.source=1.8

View File

@ -1,90 +1,90 @@
def localProperties = new Properties() def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties') def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) { if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader -> localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader) localProperties.load(reader)
} }
} }
def flutterRoot = localProperties.getProperty('flutter.sdk') def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) { if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
} }
def flutterVersionCode = localProperties.getProperty('flutter.versionCode') def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) { if (flutterVersionCode == null) {
flutterVersionCode = '1' flutterVersionCode = '1'
} }
def flutterVersionName = localProperties.getProperty('flutter.versionName') def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) { if (flutterVersionName == null) {
flutterVersionName = '1.0' flutterVersionName = '1.0'
} }
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystoreProperties = new Properties() def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties') def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) { if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
} }
android { android {
compileSdkVersion 29 compileSdkVersion 29
ndkVersion "21.3.6528147" ndkVersion "21.3.6528147"
sourceSets { sourceSets {
main.java.srcDirs += 'src/main/kotlin' main.java.srcDirs += 'src/main/kotlin'
} }
lintOptions { lintOptions {
disable 'InvalidPackage' disable 'InvalidPackage'
} }
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.stonegate.tsacdop" applicationId "com.stonegate.tsacdop"
minSdkVersion 19 minSdkVersion 19
targetSdkVersion 29 targetSdkVersion 29
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
signingConfigs { signingConfigs {
release { release {
storeFile file(System.getenv("KEYSTORE") ?:"keystore.jks") storeFile file(System.getenv("KEYSTORE") ?:"keystore.jks")
storePassword System.getenv("KEYSTORE_PASSWORD") storePassword System.getenv("KEYSTORE_PASSWORD")
keyAlias System.getenv("KEY_ALIAS") keyAlias System.getenv("KEY_ALIAS")
keyPassword System.getenv("KEY_PASSWORD") keyPassword System.getenv("KEY_PASSWORD")
// keyAlias keystoreProperties['keyAlias'] // keyAlias keystoreProperties['keyAlias']
// keyPassword keystoreProperties['keyPassword'] // keyPassword keystoreProperties['keyPassword']
// storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null // storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
// storePassword keystoreProperties['storePassword'] // storePassword keystoreProperties['storePassword']
} }
} }
buildTypes { buildTypes {
release { release {
signingConfig signingConfigs.release signingConfig signingConfigs.release
shrinkResources false shrinkResources false
} }
} }
} }
flutter { flutter {
source '../..' source '../..'
} }
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
testImplementation 'junit:junit:4.13' testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
def appcompat_version = "1.1.0" def appcompat_version = "1.1.0"
implementation "androidx.appcompat:appcompat:$appcompat_version" implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation "androidx.appcompat:appcompat-resources:$appcompat_version" implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
} }

View File

@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.stonegate.tsacdop"> package="com.stonegate.tsacdop">
<!-- Flutter needs it to communicate with the running application <!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.
--> -->
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
</manifest> </manifest>

View File

@ -1,34 +1,34 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.stonegate.tsacdop" xmlns:tools="http://schemas.android.com/tools" android:installLocation="auto"> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.stonegate.tsacdop" xmlns:tools="http://schemas.android.com/tools" android:installLocation="auto">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that <!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method. calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. --> FlutterApplication and put your custom class here. -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<application android:name="io.flutter.app.FlutterApplication" android:label="Tsacdop" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:networkSecurityConfig="@xml/network_security_config"> <application android:name="io.flutter.app.FlutterApplication" android:label="Tsacdop" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:networkSecurityConfig="@xml/network_security_config">
<activity android:name=".MainActivity" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"> <activity android:name=".MainActivity" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
<meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/LaunchTheme" /> <meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/LaunchTheme" />
<meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/normal_background" /> <meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/normal_background" />
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<!-- <meta-data android:name="io.flutter.embedding.android.SplashScreenUntilFirstFrame" android:value="true" /> --> <!-- <meta-data android:name="io.flutter.embedding.android.SplashScreenUntilFirstFrame" android:value="true" /> -->
</activity> </activity>
<service android:name="com.ryanheise.audioservice.AudioService"> <service android:name="com.ryanheise.audioservice.AudioService">
<intent-filter> <intent-filter>
<action android:name="android.media.browse.MediaBrowserService" /> <action android:name="android.media.browse.MediaBrowserService" />
</intent-filter> </intent-filter>
</service> </service>
<receiver android:name="com.ryanheise.audioservice.MediaButtonReceiver"> <receiver android:name="com.ryanheise.audioservice.MediaButtonReceiver">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" /> <action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<!-- Don't delete the meta-data below. <!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data android:name="flutterEmbedding" android:value="2" /> <meta-data android:name="flutterEmbedding" android:value="2" />
</application> </application>
</manifest> </manifest>

View File

@ -1,39 +1,39 @@
package io.flutter.plugins; package io.flutter.plugins;
import androidx.annotation.Keep; import androidx.annotation.Keep;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistry; import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistry;
import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugins.pathprovider.PathProviderPlugin; import io.flutter.plugins.pathprovider.PathProviderPlugin;
import com.tekartik.sqflite.SqflitePlugin; import com.tekartik.sqflite.SqflitePlugin;
import io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin; import io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin;
import vn.hunghd.flutterdownloader.FlutterDownloaderPlugin; import vn.hunghd.flutterdownloader.FlutterDownloaderPlugin;
/** /**
* Generated file. Do not edit. This file is generated by the Flutter tool based * Generated file. Do not edit. This file is generated by the Flutter tool based
* on the plugins that support the Android platform. * on the plugins that support the Android platform.
*/ */
@Keep @Keep
public final class IsolatePluginRegistrant { public final class IsolatePluginRegistrant {
public static void registerWith(PluginRegistry registry) { public static void registerWith(PluginRegistry registry) {
if (alreadyRegisteredWith(registry)) { if (alreadyRegisteredWith(registry)) {
return; return;
} }
PathProviderPlugin.registerWith(registry.registrarFor("io.flutter.plugins.pathprovider.PathProviderPlugin")); PathProviderPlugin.registerWith(registry.registrarFor("io.flutter.plugins.pathprovider.PathProviderPlugin"));
SqflitePlugin.registerWith(registry.registrarFor("com.tekartik.sqflite.SqflitePlugin")); SqflitePlugin.registerWith(registry.registrarFor("com.tekartik.sqflite.SqflitePlugin"));
SharedPreferencesPlugin.registerWith(registry.registrarFor("io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin")); SharedPreferencesPlugin.registerWith(registry.registrarFor("io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin"));
FlutterDownloaderPlugin.registerWith(registry.registrarFor("vn.hunghd.flutterdownloader.FlutterDownloaderPlugin")); FlutterDownloaderPlugin.registerWith(registry.registrarFor("vn.hunghd.flutterdownloader.FlutterDownloaderPlugin"));
} }
private static boolean alreadyRegisteredWith(PluginRegistry registry) { private static boolean alreadyRegisteredWith(PluginRegistry registry) {
final String key = IsolatePluginRegistrant.class.getCanonicalName(); final String key = IsolatePluginRegistrant.class.getCanonicalName();
if (registry.hasPlugin(key)) { if (registry.hasPlugin(key)) {
return true; return true;
} }
registry.registrarFor(key); registry.registrarFor(key);
return false; return false;
} }
} }

View File

@ -1,28 +1,28 @@
package com.stonegate.tsacdop package com.stonegate.tsacdop
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
import io.flutter.view.FlutterNativeView import io.flutter.view.FlutterNativeView
import io.flutter.plugins.GeneratedPluginRegistrant import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.plugins.IsolatePluginRegistrant import io.flutter.plugins.IsolatePluginRegistrant
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.embedding.engine.dart.DartExecutor import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.embedding.engine.dart.DartExecutor.DartCallback import io.flutter.embedding.engine.dart.DartExecutor.DartCallback
import com.rmawatson.flutterisolate.FlutterIsolatePlugin import com.rmawatson.flutterisolate.FlutterIsolatePlugin
class MainActivity: FlutterActivity() { class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine); GeneratedPluginRegistrant.registerWith(flutterEngine);
FlutterIsolatePlugin.setCustomIsolateRegistrant(IsolatePluginRegistrant::class.java); FlutterIsolatePlugin.setCustomIsolateRegistrant(IsolatePluginRegistrant::class.java);
MethodChannel(flutterEngine.dartExecutor, "android_app_retain").apply { MethodChannel(flutterEngine.dartExecutor, "android_app_retain").apply {
setMethodCallHandler { method, result -> setMethodCallHandler { method, result ->
if (method.method == "sendToBackground") { if (method.method == "sendToBackground") {
moveTaskToBack(true) moveTaskToBack(true)
} }
} }
} }
} }
} }

View File

@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen --> <!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/blackGrey" /> <item android:drawable="@color/blackGrey" />
<!-- You can insert your own image assets here --> <!-- You can insert your own image assets here -->
<item> <item>
<bitmap <bitmap
android:gravity="center" android:gravity="center"
android:tileMode="disabled" android:tileMode="disabled"
android:src="@mipmap/ic_splash" /> android:src="@mipmap/ic_splash" />
</item> </item>
<item android:bottom="100dp"> <item android:bottom="100dp">
<bitmap <bitmap
android:gravity="bottom" android:gravity="bottom"
android:src="@mipmap/text_light" /> android:src="@mipmap/text_light" />
</item> </item>
</layer-list> </layer-list>

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen --> <!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/blackGrey" /> <item android:drawable="@color/blackGrey" />
<!-- You can insert your own image assets here --> <!-- You can insert your own image assets here -->
<item android:bottom="100dp"> <item android:bottom="100dp">
<bitmap android:gravity="bottom" android:src="@mipmap/text_light" /> <bitmap android:gravity="bottom" android:src="@mipmap/text_light" />
</item>--> </item>-->
</layer-list> </layer-list>

View File

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen --> <!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" /> <item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here --> <!-- You can insert your own image assets here -->
<item> <item>
<bitmap android:gravity="center" android:tileMode="disabled" android:src="@mipmap/ic_splash" /> <bitmap android:gravity="center" android:tileMode="disabled" android:src="@mipmap/ic_splash" />
</item> </item>
<item android:bottom="100dp"> <item android:bottom="100dp">
<bitmap android:gravity="bottom" android:src="@mipmap/text" /> <bitmap android:gravity="bottom" android:src="@mipmap/text" />
</item> </item>
</layer-list> </layer-list>

View File

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen --> <!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/blackGrey" /> <item android:drawable="@color/blackGrey" />
<!-- You can insert your own image assets here --> <!-- You can insert your own image assets here -->
<item> <item>
<bitmap android:gravity="center" android:tileMode="disabled" android:src="@mipmap/ic_splash" /> <bitmap android:gravity="center" android:tileMode="disabled" android:src="@mipmap/ic_splash" />
</item> </item>
<item android:bottom="100dp"> <item android:bottom="100dp">
<bitmap android:gravity="bottom" android:src="@mipmap/text_light" /> <bitmap android:gravity="bottom" android:src="@mipmap/text_light" />
</item> </item>
</layer-list> </layer-list>

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen --> <!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" /> <item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here --> <!-- You can insert your own image assets here -->
<item android:bottom="100dp"> <item android:bottom="100dp">
<bitmap android:gravity="bottom" android:src="@mipmap/text_light" /> <bitmap android:gravity="bottom" android:src="@mipmap/text_light" />
</item> </item>
</layer-list> </layer-list>

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen --> <!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/blackGrey" /> <item android:drawable="@color/blackGrey" />
<!-- You can insert your own image assets here --> <!-- You can insert your own image assets here -->
<item android:bottom="100dp"> <item android:bottom="100dp">
<bitmap android:gravity="bottom" android:src="@mipmap/text_light" /> <bitmap android:gravity="bottom" android:src="@mipmap/text_light" />
</item> </item>
</layer-list> </layer-list>

View File

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="LaunchTheme" parent="Theme.AppCompat.NoActionBar"> <style name="LaunchTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowBackground">@drawable/launch_background_night</item> <item name="android:windowBackground">@drawable/launch_background_night</item>
<item name="android:navigationBarColor">@color/blackGrey</item> <item name="android:navigationBarColor">@color/blackGrey</item>
<item name="android:statusBarColor">@color/blackGrey</item> <item name="android:statusBarColor">@color/blackGrey</item>
<item name="android:windowLightStatusBar">false</item> <item name="android:windowLightStatusBar">false</item>
<item name="android:windowLightNavigationBar">true</item> <item name="android:windowLightNavigationBar">true</item>
</style> </style>
</resources> </resources>

View File

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="LaunchTheme" parent="Theme.AppCompat.Light.NoActionBar"> <style name="LaunchTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Show a splash screen on the activity. Automatically removed when <!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame --> Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item> <item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:statusBarColor">@color/white</item> <item name="android:statusBarColor">@color/white</item>
<item name="android:navigationBarColor">@color/white</item> <item name="android:navigationBarColor">@color/white</item>
<item name="android:windowLightStatusBar">true</item> <item name="android:windowLightStatusBar">true</item>
<item name="android:windowLightNavigationBar">false</item> <item name="android:windowLightNavigationBar">false</item>
</style> </style>
</resources> </resources>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<network-security-config> <network-security-config>
<base-config cleartextTrafficPermitted="true"> <base-config cleartextTrafficPermitted="true">
<trust-anchors> <trust-anchors>
<certificates src="system" /> <certificates src="system" />
</trust-anchors> </trust-anchors>
</base-config> </base-config>
</network-security-config> </network-security-config>

View File

@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.stonegate.tsacdop"> package="com.stonegate.tsacdop">
<!-- Flutter needs it to communicate with the running application <!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.
--> -->
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
</manifest> </manifest>

View File

@ -1,32 +1,32 @@
buildscript { buildscript {
ext.kotlin_version = '1.3.70' ext.kotlin_version = '1.3.70'
repositories { repositories {
google() google()
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.6.2' classpath 'com.android.tools.build:gradle:3.6.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }
allprojects { allprojects {
repositories { repositories {
google() google()
jcenter() jcenter()
} }
} }
rootProject.buildDir = '../build' rootProject.buildDir = '../build'
subprojects { subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}" project.buildDir = "${rootProject.buildDir}/${project.name}"
} }
//subprojects { //subprojects {
// project.evaluationDependsOn(':app') // project.evaluationDependsOn(':app')
//} //}
task clean(type: Delete) { task clean(type: Delete) {
delete rootProject.buildDir delete rootProject.buildDir
} }

View File

@ -1,4 +1,4 @@
org.gradle.jvmargs=-Xmx1536M org.gradle.jvmargs=-Xmx1536M
android.enableR8=true android.enableR8=true
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true

View File

@ -1,6 +1,6 @@
#Fri Mar 20 23:46:20 CST 2020 #Fri Mar 20 23:46:20 CST 2020
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip

View File

@ -1,15 +1,15 @@
include ':app' include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties() def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) { if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
} }
plugins.each { name, path -> plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name" include ":$name"
project(":$name").projectDir = pluginDirectory project(":$name").projectDir = pluginDirectory
} }

View File

@ -1 +1 @@
include ':app' include ':app'

64
ios/.gitignore vendored
View File

@ -1,32 +1,32 @@
*.mode1v3 *.mode1v3
*.mode2v3 *.mode2v3
*.moved-aside *.moved-aside
*.pbxuser *.pbxuser
*.perspectivev3 *.perspectivev3
**/*sync/ **/*sync/
.sconsign.dblite .sconsign.dblite
.tags* .tags*
**/.vagrant/ **/.vagrant/
**/DerivedData/ **/DerivedData/
Icon? Icon?
**/Pods/ **/Pods/
**/.symlinks/ **/.symlinks/
profile profile
xcuserdata xcuserdata
**/.generated/ **/.generated/
Flutter/App.framework Flutter/App.framework
Flutter/Flutter.framework Flutter/Flutter.framework
Flutter/Flutter.podspec Flutter/Flutter.podspec
Flutter/Generated.xcconfig Flutter/Generated.xcconfig
Flutter/app.flx Flutter/app.flx
Flutter/app.zip Flutter/app.zip
Flutter/flutter_assets/ Flutter/flutter_assets/
Flutter/flutter_export_environment.sh Flutter/flutter_export_environment.sh
ServiceDefinitions.json ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.* Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules. # Exceptions to above rules.
!default.mode1v3 !default.mode1v3
!default.mode2v3 !default.mode2v3
!default.pbxuser !default.pbxuser
!default.perspectivev3 !default.perspectivev3

View File

@ -1,26 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>App</string> <string>App</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string> <string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>App</string> <string>App</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>FMWK</string> <string>FMWK</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0</string> <string>1.0</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0</string> <string>1.0</string>
<key>MinimumOSVersion</key> <key>MinimumOSVersion</key>
<string>8.0</string> <string>8.0</string>
</dict> </dict>
</plist> </plist>

View File

@ -1,2 +1,2 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig" #include "Generated.xcconfig"

View File

@ -1,2 +1,2 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig" #include "Generated.xcconfig"

View File

@ -1,88 +1,88 @@
# Uncomment this line to define a global platform for your project # Uncomment this line to define a global platform for your project
platform :ios, '11.0' platform :ios, '11.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'
source 'https://cdn.cocoapods.org/' source 'https://cdn.cocoapods.org/'
project 'Runner', { project 'Runner', {
'Debug' => :debug, 'Debug' => :debug,
'Profile' => :release, 'Profile' => :release,
'Release' => :release, 'Release' => :release,
} }
def parse_KV_file(file, separator='=') def parse_KV_file(file, separator='=')
file_abs_path = File.expand_path(file) file_abs_path = File.expand_path(file)
if !File.exists? file_abs_path if !File.exists? file_abs_path
return []; return [];
end end
generated_key_values = {} generated_key_values = {}
skip_line_start_symbols = ["#", "/"] skip_line_start_symbols = ["#", "/"]
File.foreach(file_abs_path) do |line| File.foreach(file_abs_path) do |line|
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
plugin = line.split(pattern=separator) plugin = line.split(pattern=separator)
if plugin.length == 2 if plugin.length == 2
podname = plugin[0].strip() podname = plugin[0].strip()
path = plugin[1].strip() path = plugin[1].strip()
podpath = File.expand_path("#{path}", file_abs_path) podpath = File.expand_path("#{path}", file_abs_path)
generated_key_values[podname] = podpath generated_key_values[podname] = podpath
else else
puts "Invalid plugin specification: #{line}" puts "Invalid plugin specification: #{line}"
end end
end end
generated_key_values generated_key_values
end end
target 'Runner' do target 'Runner' do
use_frameworks! use_frameworks!
use_modular_headers! use_modular_headers!
# Flutter Pod # Flutter Pod
copied_flutter_dir = File.join(__dir__, 'Flutter') copied_flutter_dir = File.join(__dir__, 'Flutter')
copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path)
# Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet.
# That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration.
# CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig')
unless File.exist?(generated_xcode_build_settings_path) unless File.exist?(generated_xcode_build_settings_path)
raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end end
generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path)
cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR'];
unless File.exist?(copied_framework_path) unless File.exist?(copied_framework_path)
FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
end end
unless File.exist?(copied_podspec_path) unless File.exist?(copied_podspec_path)
FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
end end
end end
# Keep pod path relative so it can be checked into Podfile.lock. # Keep pod path relative so it can be checked into Podfile.lock.
pod 'Flutter', :path => 'Flutter' pod 'Flutter', :path => 'Flutter'
# Plugin Pods # Plugin Pods
# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
# referring to absolute paths on developers' machines. # referring to absolute paths on developers' machines.
system('rm -rf .symlinks') system('rm -rf .symlinks')
system('mkdir -p .symlinks/plugins') system('mkdir -p .symlinks/plugins')
plugin_pods = parse_KV_file('../.flutter-plugins') plugin_pods = parse_KV_file('../.flutter-plugins')
plugin_pods.each do |name, path| plugin_pods.each do |name, path|
symlink = File.join('.symlinks', 'plugins', name) symlink = File.join('.symlinks', 'plugins', name)
File.symlink(path, symlink) File.symlink(path, symlink)
pod name, :path => File.join(symlink, 'ios') pod name, :path => File.join(symlink, 'ios')
end end
end end
post_install do |installer| post_install do |installer|
installer.pods_project.targets.each do |target| installer.pods_project.targets.each do |target|
target.build_configurations.each do |config| target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO' config.build_settings['ENABLE_BITCODE'] = 'NO'
end end
end end
end end

View File

@ -1,204 +1,204 @@
PODS: PODS:
- audio_service (0.0.1): - audio_service (0.0.1):
- Flutter - Flutter
- connectivity (0.0.1): - connectivity (0.0.1):
- Flutter - Flutter
- Reachability - Reachability
- connectivity_macos (0.0.1): - connectivity_macos (0.0.1):
- Flutter - Flutter
- DKImagePickerController/Core (4.2.2): - DKImagePickerController/Core (4.2.2):
- DKImagePickerController/ImageDataManager - DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource - DKImagePickerController/Resource
- DKImagePickerController/ImageDataManager (4.2.2) - DKImagePickerController/ImageDataManager (4.2.2)
- DKImagePickerController/PhotoGallery (4.2.2): - DKImagePickerController/PhotoGallery (4.2.2):
- DKImagePickerController/Core - DKImagePickerController/Core
- DKPhotoGallery - DKPhotoGallery
- DKImagePickerController/Resource (4.2.2) - DKImagePickerController/Resource (4.2.2)
- DKPhotoGallery (0.0.14): - DKPhotoGallery (0.0.14):
- DKPhotoGallery/Core (= 0.0.14) - DKPhotoGallery/Core (= 0.0.14)
- DKPhotoGallery/Model (= 0.0.14) - DKPhotoGallery/Model (= 0.0.14)
- DKPhotoGallery/Preview (= 0.0.14) - DKPhotoGallery/Preview (= 0.0.14)
- DKPhotoGallery/Resource (= 0.0.14) - DKPhotoGallery/Resource (= 0.0.14)
- SDWebImage - SDWebImage
- SDWebImageFLPlugin - SDWebImageFLPlugin
- DKPhotoGallery/Core (0.0.14): - DKPhotoGallery/Core (0.0.14):
- DKPhotoGallery/Model - DKPhotoGallery/Model
- DKPhotoGallery/Preview - DKPhotoGallery/Preview
- SDWebImage - SDWebImage
- SDWebImageFLPlugin - SDWebImageFLPlugin
- DKPhotoGallery/Model (0.0.14): - DKPhotoGallery/Model (0.0.14):
- SDWebImage - SDWebImage
- SDWebImageFLPlugin - SDWebImageFLPlugin
- DKPhotoGallery/Preview (0.0.14): - DKPhotoGallery/Preview (0.0.14):
- DKPhotoGallery/Model - DKPhotoGallery/Model
- DKPhotoGallery/Resource - DKPhotoGallery/Resource
- SDWebImage - SDWebImage
- SDWebImageFLPlugin - SDWebImageFLPlugin
- DKPhotoGallery/Resource (0.0.14): - DKPhotoGallery/Resource (0.0.14):
- SDWebImage - SDWebImage
- SDWebImageFLPlugin - SDWebImageFLPlugin
- file_picker (0.0.1): - file_picker (0.0.1):
- DKImagePickerController/PhotoGallery - DKImagePickerController/PhotoGallery
- Flutter - Flutter
- FLAnimatedImage (1.0.12) - FLAnimatedImage (1.0.12)
- Flutter (1.0.0) - Flutter (1.0.0)
- flutter_downloader (0.0.1): - flutter_downloader (0.0.1):
- Flutter - Flutter
- flutter_file_dialog (0.0.1): - flutter_file_dialog (0.0.1):
- Flutter - Flutter
- flutter_isolate (0.0.1): - flutter_isolate (0.0.1):
- Flutter - Flutter
- flutter_plugin_android_lifecycle (0.0.1): - flutter_plugin_android_lifecycle (0.0.1):
- Flutter - Flutter
- fluttertoast (0.0.2): - fluttertoast (0.0.2):
- Flutter - Flutter
- FMDB (2.7.5): - FMDB (2.7.5):
- FMDB/standard (= 2.7.5) - FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5) - FMDB/standard (2.7.5)
- just_audio (0.0.1): - just_audio (0.0.1):
- Flutter - Flutter
- path_provider (0.0.1): - path_provider (0.0.1):
- Flutter - Flutter
- path_provider_macos (0.0.1): - path_provider_macos (0.0.1):
- Flutter - Flutter
- "permission_handler (5.0.0+hotfix.5)": - "permission_handler (5.0.0+hotfix.5)":
- Flutter - Flutter
- Reachability (3.2) - Reachability (3.2)
- SDWebImage (5.7.4): - SDWebImage (5.7.4):
- SDWebImage/Core (= 5.7.4) - SDWebImage/Core (= 5.7.4)
- SDWebImage/Core (5.7.4) - SDWebImage/Core (5.7.4)
- SDWebImageFLPlugin (0.4.0): - SDWebImageFLPlugin (0.4.0):
- FLAnimatedImage (>= 1.0.11) - FLAnimatedImage (>= 1.0.11)
- SDWebImage/Core (~> 5.6) - SDWebImage/Core (~> 5.6)
- shared_preferences (0.0.1): - shared_preferences (0.0.1):
- Flutter - Flutter
- shared_preferences_macos (0.0.1): - shared_preferences_macos (0.0.1):
- Flutter - Flutter
- shared_preferences_web (0.0.1): - shared_preferences_web (0.0.1):
- Flutter - Flutter
- sqflite (0.0.1): - sqflite (0.0.1):
- Flutter - Flutter
- FMDB (~> 2.7.2) - FMDB (~> 2.7.2)
- url_launcher (0.0.1): - url_launcher (0.0.1):
- Flutter - Flutter
- url_launcher_macos (0.0.1): - url_launcher_macos (0.0.1):
- Flutter - Flutter
- url_launcher_web (0.0.1): - url_launcher_web (0.0.1):
- Flutter - Flutter
- workmanager (0.0.1): - workmanager (0.0.1):
- Flutter - Flutter
DEPENDENCIES: DEPENDENCIES:
- audio_service (from `.symlinks/plugins/audio_service/ios`) - audio_service (from `.symlinks/plugins/audio_service/ios`)
- connectivity (from `.symlinks/plugins/connectivity/ios`) - connectivity (from `.symlinks/plugins/connectivity/ios`)
- connectivity_macos (from `.symlinks/plugins/connectivity_macos/ios`) - connectivity_macos (from `.symlinks/plugins/connectivity_macos/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_downloader (from `.symlinks/plugins/flutter_downloader/ios`) - flutter_downloader (from `.symlinks/plugins/flutter_downloader/ios`)
- flutter_file_dialog (from `.symlinks/plugins/flutter_file_dialog/ios`) - flutter_file_dialog (from `.symlinks/plugins/flutter_file_dialog/ios`)
- flutter_isolate (from `.symlinks/plugins/flutter_isolate/ios`) - flutter_isolate (from `.symlinks/plugins/flutter_isolate/ios`)
- flutter_plugin_android_lifecycle (from `.symlinks/plugins/flutter_plugin_android_lifecycle/ios`) - flutter_plugin_android_lifecycle (from `.symlinks/plugins/flutter_plugin_android_lifecycle/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- just_audio (from `.symlinks/plugins/just_audio/ios`) - just_audio (from `.symlinks/plugins/just_audio/ios`)
- path_provider (from `.symlinks/plugins/path_provider/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`)
- path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`)
- permission_handler (from `.symlinks/plugins/permission_handler/ios`) - permission_handler (from `.symlinks/plugins/permission_handler/ios`)
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
- shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`) - shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`)
- shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`) - shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`)
- sqflite (from `.symlinks/plugins/sqflite/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`)
- url_launcher (from `.symlinks/plugins/url_launcher/ios`) - url_launcher (from `.symlinks/plugins/url_launcher/ios`)
- url_launcher_macos (from `.symlinks/plugins/url_launcher_macos/ios`) - url_launcher_macos (from `.symlinks/plugins/url_launcher_macos/ios`)
- url_launcher_web (from `.symlinks/plugins/url_launcher_web/ios`) - url_launcher_web (from `.symlinks/plugins/url_launcher_web/ios`)
- workmanager (from `.symlinks/plugins/workmanager/ios`) - workmanager (from `.symlinks/plugins/workmanager/ios`)
SPEC REPOS: SPEC REPOS:
trunk: trunk:
- DKImagePickerController - DKImagePickerController
- DKPhotoGallery - DKPhotoGallery
- FLAnimatedImage - FLAnimatedImage
- FMDB - FMDB
- Reachability - Reachability
- SDWebImage - SDWebImage
- SDWebImageFLPlugin - SDWebImageFLPlugin
EXTERNAL SOURCES: EXTERNAL SOURCES:
audio_service: audio_service:
:path: ".symlinks/plugins/audio_service/ios" :path: ".symlinks/plugins/audio_service/ios"
connectivity: connectivity:
:path: ".symlinks/plugins/connectivity/ios" :path: ".symlinks/plugins/connectivity/ios"
connectivity_macos: connectivity_macos:
:path: ".symlinks/plugins/connectivity_macos/ios" :path: ".symlinks/plugins/connectivity_macos/ios"
file_picker: file_picker:
:path: ".symlinks/plugins/file_picker/ios" :path: ".symlinks/plugins/file_picker/ios"
Flutter: Flutter:
:path: Flutter :path: Flutter
flutter_downloader: flutter_downloader:
:path: ".symlinks/plugins/flutter_downloader/ios" :path: ".symlinks/plugins/flutter_downloader/ios"
flutter_file_dialog: flutter_file_dialog:
:path: ".symlinks/plugins/flutter_file_dialog/ios" :path: ".symlinks/plugins/flutter_file_dialog/ios"
flutter_isolate: flutter_isolate:
:path: ".symlinks/plugins/flutter_isolate/ios" :path: ".symlinks/plugins/flutter_isolate/ios"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
:path: ".symlinks/plugins/flutter_plugin_android_lifecycle/ios" :path: ".symlinks/plugins/flutter_plugin_android_lifecycle/ios"
fluttertoast: fluttertoast:
:path: ".symlinks/plugins/fluttertoast/ios" :path: ".symlinks/plugins/fluttertoast/ios"
just_audio: just_audio:
:path: ".symlinks/plugins/just_audio/ios" :path: ".symlinks/plugins/just_audio/ios"
path_provider: path_provider:
:path: ".symlinks/plugins/path_provider/ios" :path: ".symlinks/plugins/path_provider/ios"
path_provider_macos: path_provider_macos:
:path: ".symlinks/plugins/path_provider_macos/ios" :path: ".symlinks/plugins/path_provider_macos/ios"
permission_handler: permission_handler:
:path: ".symlinks/plugins/permission_handler/ios" :path: ".symlinks/plugins/permission_handler/ios"
shared_preferences: shared_preferences:
:path: ".symlinks/plugins/shared_preferences/ios" :path: ".symlinks/plugins/shared_preferences/ios"
shared_preferences_macos: shared_preferences_macos:
:path: ".symlinks/plugins/shared_preferences_macos/ios" :path: ".symlinks/plugins/shared_preferences_macos/ios"
shared_preferences_web: shared_preferences_web:
:path: ".symlinks/plugins/shared_preferences_web/ios" :path: ".symlinks/plugins/shared_preferences_web/ios"
sqflite: sqflite:
:path: ".symlinks/plugins/sqflite/ios" :path: ".symlinks/plugins/sqflite/ios"
url_launcher: url_launcher:
:path: ".symlinks/plugins/url_launcher/ios" :path: ".symlinks/plugins/url_launcher/ios"
url_launcher_macos: url_launcher_macos:
:path: ".symlinks/plugins/url_launcher_macos/ios" :path: ".symlinks/plugins/url_launcher_macos/ios"
url_launcher_web: url_launcher_web:
:path: ".symlinks/plugins/url_launcher_web/ios" :path: ".symlinks/plugins/url_launcher_web/ios"
workmanager: workmanager:
:path: ".symlinks/plugins/workmanager/ios" :path: ".symlinks/plugins/workmanager/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
audio_service: f509d65da41b9521a61f1c404dd58651f265a567 audio_service: f509d65da41b9521a61f1c404dd58651f265a567
connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467 connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467
connectivity_macos: e2e9731b6b22dda39eb1b128f6969d574460e191 connectivity_macos: e2e9731b6b22dda39eb1b128f6969d574460e191
DKImagePickerController: 4a3e7948a848c4348e600b3fe5ce41478835fa10 DKImagePickerController: 4a3e7948a848c4348e600b3fe5ce41478835fa10
DKPhotoGallery: 0290d32343574f06eaa4c26f8f2f8a1035e916be DKPhotoGallery: 0290d32343574f06eaa4c26f8f2f8a1035e916be
file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1
FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31 FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
flutter_downloader: 058b9c41564a90500f67f3e432e3524613a7fd83 flutter_downloader: 058b9c41564a90500f67f3e432e3524613a7fd83
flutter_file_dialog: 34ab8c55c460c69cb70e75a8d74bfe8b5c852824 flutter_file_dialog: 34ab8c55c460c69cb70e75a8d74bfe8b5c852824
flutter_isolate: 0edf5081826d071adf21759d1eb10ff5c24503b5 flutter_isolate: 0edf5081826d071adf21759d1eb10ff5c24503b5
flutter_plugin_android_lifecycle: dc0b544e129eebb77a6bfb1239d4d1c673a60a35 flutter_plugin_android_lifecycle: dc0b544e129eebb77a6bfb1239d4d1c673a60a35
fluttertoast: b644586ef3b16f67fae9a1f8754cef6b2d6b634b fluttertoast: b644586ef3b16f67fae9a1f8754cef6b2d6b634b
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
just_audio: c695d6e7e37f9e96672dd84039d7530e7fd5c205 just_audio: c695d6e7e37f9e96672dd84039d7530e7fd5c205
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0
permission_handler: 6226fcb78b97c7c7458a95c7346a11d5184fec12 permission_handler: 6226fcb78b97c7c7458a95c7346a11d5184fec12
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
SDWebImage: 48b88379b798fd1e4298f95bb25d2cdabbf4deb3 SDWebImage: 48b88379b798fd1e4298f95bb25d2cdabbf4deb3
SDWebImageFLPlugin: 6c2295fb1242d44467c6c87dc5db6b0a13228fd8 SDWebImageFLPlugin: 6c2295fb1242d44467c6c87dc5db6b0a13228fd8
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087 shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087
shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9 shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9
sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313 url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313
url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c
workmanager: ffff78bf18495e2bc5b8c955ead94a81db340681 workmanager: ffff78bf18495e2bc5b8c955ead94a81db340681
PODFILE CHECKSUM: 7a493bdf8e8fed20acf4793e6605bdc446895cf3 PODFILE CHECKSUM: 7a493bdf8e8fed20acf4793e6605bdc446895cf3
COCOAPODS: 1.9.1 COCOAPODS: 1.9.1

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Workspace <Workspace
version = "1.0"> version = "1.0">
<FileRef <FileRef
location = "group:Runner.xcodeproj"> location = "group:Runner.xcodeproj">
</FileRef> </FileRef>
</Workspace> </Workspace>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>IDEDidComputeMac32BitWarning</key> <key>IDEDidComputeMac32BitWarning</key>
<true/> <true/>
</dict> </dict>
</plist> </plist>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>PreviewsEnabled</key> <key>PreviewsEnabled</key>
<false/> <false/>
</dict> </dict>
</plist> </plist>

View File

@ -1,91 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1020" LastUpgradeVersion = "1020"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"> buildImplicitDependencies = "YES">
<BuildActionEntries> <BuildActionEntries>
<BuildActionEntry <BuildActionEntry
buildForTesting = "YES" buildForTesting = "YES"
buildForRunning = "YES" buildForRunning = "YES"
buildForProfiling = "YES" buildForProfiling = "YES"
buildForArchiving = "YES" buildForArchiving = "YES"
buildForAnalyzing = "YES"> buildForAnalyzing = "YES">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D" BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app" BuildableName = "Runner.app"
BlueprintName = "Runner" BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildActionEntry> </BuildActionEntry>
</BuildActionEntries> </BuildActionEntries>
</BuildAction> </BuildAction>
<TestAction <TestAction
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<Testables> <Testables>
</Testables> </Testables>
<MacroExpansion> <MacroExpansion>
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D" BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app" BuildableName = "Runner.app"
BlueprintName = "Runner" BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
<AdditionalOptions> <AdditionalOptions>
</AdditionalOptions> </AdditionalOptions>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0" launchStyle = "0"
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES" debugDocumentVersioning = "YES"
debugServiceExtension = "internal" debugServiceExtension = "internal"
allowLocationSimulation = "YES"> allowLocationSimulation = "YES">
<BuildableProductRunnable <BuildableProductRunnable
runnableDebuggingMode = "0"> runnableDebuggingMode = "0">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D" BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app" BuildableName = "Runner.app"
BlueprintName = "Runner" BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
<AdditionalOptions> <AdditionalOptions>
</AdditionalOptions> </AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "Profile" buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES" shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = "" savedToolIdentifier = ""
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"> debugDocumentVersioning = "YES">
<BuildableProductRunnable <BuildableProductRunnable
runnableDebuggingMode = "0"> runnableDebuggingMode = "0">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D" BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app" BuildableName = "Runner.app"
BlueprintName = "Runner" BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
</ProfileAction> </ProfileAction>
<AnalyzeAction <AnalyzeAction
buildConfiguration = "Debug"> buildConfiguration = "Debug">
</AnalyzeAction> </AnalyzeAction>
<ArchiveAction <ArchiveAction
buildConfiguration = "Release" buildConfiguration = "Release"
revealArchiveInOrganizer = "YES"> revealArchiveInOrganizer = "YES">
</ArchiveAction> </ArchiveAction>
</Scheme> </Scheme>

View File

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Workspace <Workspace
version = "1.0"> version = "1.0">
<FileRef <FileRef
location = "group:Runner.xcodeproj"> location = "group:Runner.xcodeproj">
</FileRef> </FileRef>
<FileRef <FileRef
location = "group:Pods/Pods.xcodeproj"> location = "group:Pods/Pods.xcodeproj">
</FileRef> </FileRef>
</Workspace> </Workspace>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>IDEDidComputeMac32BitWarning</key> <key>IDEDidComputeMac32BitWarning</key>
<true/> <true/>
</dict> </dict>
</plist> </plist>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>PreviewsEnabled</key> <key>PreviewsEnabled</key>
<false/> <false/>
</dict> </dict>
</plist> </plist>

View File

@ -1,23 +1,23 @@
import UIKit import UIKit
import Flutter import Flutter
import flutter_downloader import flutter_downloader
@UIApplicationMain @UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate { @objc class AppDelegate: FlutterAppDelegate {
override func application( override func application(
_ application: UIApplication, _ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool { ) -> Bool {
GeneratedPluginRegistrant.register(with: self) GeneratedPluginRegistrant.register(with: self)
FlutterDownloaderPlugin.setPluginRegistrantCallback(registerPlugins) FlutterDownloaderPlugin.setPluginRegistrantCallback(registerPlugins)
UIApplication.shared.setMinimumBackgroundFetchInterval(TimeInterval(60*60*4)) UIApplication.shared.setMinimumBackgroundFetchInterval(TimeInterval(60*60*4))
return super.application(application, didFinishLaunchingWithOptions: launchOptions) return super.application(application, didFinishLaunchingWithOptions: launchOptions)
} }
} }
private func registerPlugins(registry: FlutterPluginRegistry) { private func registerPlugins(registry: FlutterPluginRegistry) {
if (!registry.hasPlugin("FlutterDownloaderPlugin")) { if (!registry.hasPlugin("FlutterDownloaderPlugin")) {
FlutterDownloaderPlugin.register(with: registry.registrar(forPlugin: "FlutterDownloaderPlugin")) FlutterDownloaderPlugin.register(with: registry.registrar(forPlugin: "FlutterDownloaderPlugin"))
} }
} }

View File

@ -1,122 +1,122 @@
{ {
"images" : [ "images" : [
{ {
"size" : "20x20", "size" : "20x20",
"idiom" : "iphone", "idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png", "filename" : "Icon-App-20x20@2x.png",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"size" : "20x20", "size" : "20x20",
"idiom" : "iphone", "idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png", "filename" : "Icon-App-20x20@3x.png",
"scale" : "3x" "scale" : "3x"
}, },
{ {
"size" : "29x29", "size" : "29x29",
"idiom" : "iphone", "idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png", "filename" : "Icon-App-29x29@1x.png",
"scale" : "1x" "scale" : "1x"
}, },
{ {
"size" : "29x29", "size" : "29x29",
"idiom" : "iphone", "idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png", "filename" : "Icon-App-29x29@2x.png",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"size" : "29x29", "size" : "29x29",
"idiom" : "iphone", "idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png", "filename" : "Icon-App-29x29@3x.png",
"scale" : "3x" "scale" : "3x"
}, },
{ {
"size" : "40x40", "size" : "40x40",
"idiom" : "iphone", "idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png", "filename" : "Icon-App-40x40@2x.png",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"size" : "40x40", "size" : "40x40",
"idiom" : "iphone", "idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png", "filename" : "Icon-App-40x40@3x.png",
"scale" : "3x" "scale" : "3x"
}, },
{ {
"size" : "60x60", "size" : "60x60",
"idiom" : "iphone", "idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png", "filename" : "Icon-App-60x60@2x.png",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"size" : "60x60", "size" : "60x60",
"idiom" : "iphone", "idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png", "filename" : "Icon-App-60x60@3x.png",
"scale" : "3x" "scale" : "3x"
}, },
{ {
"size" : "20x20", "size" : "20x20",
"idiom" : "ipad", "idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png", "filename" : "Icon-App-20x20@1x.png",
"scale" : "1x" "scale" : "1x"
}, },
{ {
"size" : "20x20", "size" : "20x20",
"idiom" : "ipad", "idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png", "filename" : "Icon-App-20x20@2x.png",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"size" : "29x29", "size" : "29x29",
"idiom" : "ipad", "idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png", "filename" : "Icon-App-29x29@1x.png",
"scale" : "1x" "scale" : "1x"
}, },
{ {
"size" : "29x29", "size" : "29x29",
"idiom" : "ipad", "idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png", "filename" : "Icon-App-29x29@2x.png",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"size" : "40x40", "size" : "40x40",
"idiom" : "ipad", "idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png", "filename" : "Icon-App-40x40@1x.png",
"scale" : "1x" "scale" : "1x"
}, },
{ {
"size" : "40x40", "size" : "40x40",
"idiom" : "ipad", "idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png", "filename" : "Icon-App-40x40@2x.png",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"size" : "76x76", "size" : "76x76",
"idiom" : "ipad", "idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png", "filename" : "Icon-App-76x76@1x.png",
"scale" : "1x" "scale" : "1x"
}, },
{ {
"size" : "76x76", "size" : "76x76",
"idiom" : "ipad", "idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png", "filename" : "Icon-App-76x76@2x.png",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"size" : "83.5x83.5", "size" : "83.5x83.5",
"idiom" : "ipad", "idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png", "filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"size" : "1024x1024", "size" : "1024x1024",
"idiom" : "ios-marketing", "idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png", "filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x" "scale" : "1x"
} }
], ],
"info" : { "info" : {
"version" : 1, "version" : 1,
"author" : "xcode" "author" : "xcode"
} }
} }

View File

@ -1,23 +1,23 @@
{ {
"images" : [ "images" : [
{ {
"idiom" : "universal", "idiom" : "universal",
"filename" : "LaunchImage.png", "filename" : "LaunchImage.png",
"scale" : "1x" "scale" : "1x"
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"filename" : "LaunchImage@2x.png", "filename" : "LaunchImage@2x.png",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"filename" : "LaunchImage@3x.png", "filename" : "LaunchImage@3x.png",
"scale" : "3x" "scale" : "3x"
} }
], ],
"info" : { "info" : {
"version" : 1, "version" : 1,
"author" : "xcode" "author" : "xcode"
} }
} }

View File

@ -1,5 +1,5 @@
# Launch Screen Assets # Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory. You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

View File

@ -1,37 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies> </dependencies>
<scenes> <scenes>
<!--View Controller--> <!--View Controller-->
<scene sceneID="EHf-IW-A2E"> <scene sceneID="EHf-IW-A2E">
<objects> <objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController"> <viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides> <layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/> <viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/> <viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides> </layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3"> <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4"> <imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView> </imageView>
</subviews> </subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints> <constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/> <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/> <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints> </constraints>
</view> </view>
</viewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="53" y="375"/> <point key="canvasLocation" x="53" y="375"/>
</scene> </scene>
</scenes> </scenes>
<resources> <resources>
<image name="LaunchImage" width="168" height="185"/> <image name="LaunchImage" width="168" height="185"/>
</resources> </resources>
</document> </document>

View File

@ -1,26 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies> </dependencies>
<scenes> <scenes>
<!--Flutter View Controller--> <!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu"> <scene sceneID="tne-QT-ifu">
<objects> <objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController"> <viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides> <layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/> <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/> <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides> </layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC"> <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/> <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view> </view>
</viewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects> </objects>
</scene> </scene>
</scenes> </scenes>
</document> </document>

View File

@ -1,58 +1,58 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>tsacdop_player</string> <string>tsacdop_player</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string> <string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string> <string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true /> <true />
<key>NSAppTransportSecurity</key> <key>NSAppTransportSecurity</key>
<dict> <dict>
<key>NSAllowsArbitraryLoads</key> <key>NSAllowsArbitraryLoads</key>
<true /> <true />
<key>NSAllowsArbitraryLoadsForMedia</key> <key>NSAllowsArbitraryLoadsForMedia</key>
<true /> <true />
</dict> </dict>
<key>UIBackgroundModes</key> <key>UIBackgroundModes</key>
<array> <array>
<string>audio</string> <string>audio</string>
<string>fetch</string> <string>fetch</string>
<string>remote-notification</string> <string>remote-notification</string>
</array> </array>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>UIMainStoryboardFile</key> <key>UIMainStoryboardFile</key>
<string>Main</string> <string>Main</string>
<key>UISupportedInterfaceOrientations</key> <key>UISupportedInterfaceOrientations</key>
<array> <array>
<string>UIInterfaceOrientationPortrait</string> <string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>UISupportedInterfaceOrientations~ipad</key> <key>UISupportedInterfaceOrientations~ipad</key>
<array> <array>
<string>UIInterfaceOrientationPortrait</string> <string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string> <string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>UIViewControllerBasedStatusBarAppearance</key> <key>UIViewControllerBasedStatusBarAppearance</key>
<false /> <false />
</dict> </dict>
</plist> </plist>

View File

@ -1 +1 @@
#import "GeneratedPluginRegistrant.h" #import "GeneratedPluginRegistrant.h"

View File

@ -1 +1 @@
export '../state/setting_state.dart'; export '../state/setting_state.dart';

File diff suppressed because it is too large Load Diff

View File

@ -1,266 +1,266 @@
import 'dart:async'; import 'dart:async';
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:ui'; import 'dart:ui';
import 'package:connectivity/connectivity.dart'; import 'package:connectivity/connectivity.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../local_storage/key_value_storage.dart'; import '../local_storage/key_value_storage.dart';
import '../state/audio_state.dart'; import '../state/audio_state.dart';
import '../state/download_state.dart'; import '../state/download_state.dart';
import '../type/episode_task.dart'; import '../type/episode_task.dart';
import '../type/episodebrief.dart'; import '../type/episodebrief.dart';
import '../util/custom_widget.dart'; import '../util/custom_widget.dart';
import '../util/extension_helper.dart'; import '../util/extension_helper.dart';
import '../util/general_dialog.dart'; import '../util/general_dialog.dart';
class DownloadButton extends StatefulWidget { class DownloadButton extends StatefulWidget {
final EpisodeBrief episode; final EpisodeBrief episode;
DownloadButton({this.episode, Key key}) : super(key: key); DownloadButton({this.episode, Key key}) : super(key: key);
@override @override
_DownloadButtonState createState() => _DownloadButtonState(); _DownloadButtonState createState() => _DownloadButtonState();
} }
class _DownloadButtonState extends State<DownloadButton> { class _DownloadButtonState extends State<DownloadButton> {
Future<void> _requestDownload(EpisodeBrief episode) async { Future<void> _requestDownload(EpisodeBrief episode) async {
final downloadUsingData = await KeyValueStorage(downloadUsingDataKey) final downloadUsingData = await KeyValueStorage(downloadUsingDataKey)
.getBool(defaultValue: true, reverse: true); .getBool(defaultValue: true, reverse: true);
final permissionReady = await _checkPermmison(); final permissionReady = await _checkPermmison();
final result = await Connectivity().checkConnectivity(); final result = await Connectivity().checkConnectivity();
final usingData = result == ConnectivityResult.mobile; final usingData = result == ConnectivityResult.mobile;
var dataConfirm = true; var dataConfirm = true;
if (permissionReady) { if (permissionReady) {
if (downloadUsingData && usingData) { if (downloadUsingData && usingData) {
dataConfirm = await _useDataConfirm(); dataConfirm = await _useDataConfirm();
} }
if (dataConfirm) { if (dataConfirm) {
Provider.of<DownloadState>(context, listen: false).startTask(episode); Provider.of<DownloadState>(context, listen: false).startTask(episode);
} }
} }
} }
void _deleteDownload(EpisodeBrief episode) async { void _deleteDownload(EpisodeBrief episode) async {
Provider.of<DownloadState>(context, listen: false).delTask(episode); Provider.of<DownloadState>(context, listen: false).delTask(episode);
Fluttertoast.showToast( Fluttertoast.showToast(
msg: context.s.downloadRemovedToast, msg: context.s.downloadRemovedToast,
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.BOTTOM,
); );
} }
Future<void> _pauseDownload(EpisodeBrief episode) async { Future<void> _pauseDownload(EpisodeBrief episode) async {
Provider.of<DownloadState>(context, listen: false).pauseTask(episode); Provider.of<DownloadState>(context, listen: false).pauseTask(episode);
} }
Future<void> _resumeDownload(EpisodeBrief episode) async { Future<void> _resumeDownload(EpisodeBrief episode) async {
Provider.of<DownloadState>(context, listen: false).resumeTask(episode); Provider.of<DownloadState>(context, listen: false).resumeTask(episode);
} }
Future<void> _retryDownload(EpisodeBrief episode) async { Future<void> _retryDownload(EpisodeBrief episode) async {
Provider.of<DownloadState>(context, listen: false).retryTask(episode); Provider.of<DownloadState>(context, listen: false).retryTask(episode);
} }
Future<bool> _checkPermmison() async { Future<bool> _checkPermmison() async {
var permission = await Permission.storage.status; var permission = await Permission.storage.status;
if (permission != PermissionStatus.granted) { if (permission != PermissionStatus.granted) {
var permissions = await [Permission.storage].request(); var permissions = await [Permission.storage].request();
if (permissions[Permission.storage] == PermissionStatus.granted) { if (permissions[Permission.storage] == PermissionStatus.granted) {
return true; return true;
} else { } else {
return false; return false;
} }
} else { } else {
return true; return true;
} }
} }
Future<bool> _useDataConfirm() async { Future<bool> _useDataConfirm() async {
var ifUseData = false; var ifUseData = false;
final s = context.s; final s = context.s;
await generalDialog( await generalDialog(
context, context,
title: Text(s.cellularConfirm), title: Text(s.cellularConfirm),
content: Text(s.cellularConfirmDes), content: Text(s.cellularConfirmDes),
actions: <Widget>[ actions: <Widget>[
FlatButton( FlatButton(
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: Text( child: Text(
s.cancel, s.cancel,
style: TextStyle(color: Colors.grey[600]), style: TextStyle(color: Colors.grey[600]),
), ),
), ),
FlatButton( FlatButton(
onPressed: () { onPressed: () {
ifUseData = true; ifUseData = true;
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: Text( child: Text(
s.confirm, s.confirm,
style: TextStyle(color: Colors.red), style: TextStyle(color: Colors.red),
), ),
) )
], ],
); );
return ifUseData; return ifUseData;
} }
Widget _buttonOnMenu(Widget widget, Function() onTap) => Material( Widget _buttonOnMenu(Widget widget, Function() onTap) => Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
onTap: onTap, onTap: onTap,
child: Container( child: Container(
height: 50.0, height: 50.0,
padding: EdgeInsets.symmetric(horizontal: 15.0), padding: EdgeInsets.symmetric(horizontal: 15.0),
child: widget), child: widget),
), ),
); );
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<DownloadState>(builder: (_, downloader, __) { return Consumer<DownloadState>(builder: (_, downloader, __) {
var _task = Provider.of<DownloadState>(context, listen: false) var _task = Provider.of<DownloadState>(context, listen: false)
.episodeToTask(widget.episode); .episodeToTask(widget.episode);
return Row( return Row(
children: <Widget>[ children: <Widget>[
_downloadButton(_task, context), _downloadButton(_task, context),
AnimatedContainer( AnimatedContainer(
duration: Duration(seconds: 1), duration: Duration(seconds: 1),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).accentColor, color: Theme.of(context).accentColor,
borderRadius: BorderRadius.all(Radius.circular(15.0))), borderRadius: BorderRadius.all(Radius.circular(15.0))),
height: 20.0, height: 20.0,
width: (_task.status == DownloadTaskStatus.running) ? 50.0 : 0, width: (_task.status == DownloadTaskStatus.running) ? 50.0 : 0,
alignment: Alignment.center, alignment: Alignment.center,
child: SingleChildScrollView( child: SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: Text('${math.max(_task.progress, 0)}%', child: Text('${math.max(_task.progress, 0)}%',
style: TextStyle(color: Colors.white)), style: TextStyle(color: Colors.white)),
)), )),
], ],
); );
}); });
} }
Widget _downloadButton(EpisodeTask task, BuildContext context) { Widget _downloadButton(EpisodeTask task, BuildContext context) {
switch (task.status.value) { switch (task.status.value) {
case 0: case 0:
return _buttonOnMenu( return _buttonOnMenu(
Center( Center(
child: SizedBox( child: SizedBox(
height: 20, height: 20,
width: 20, width: 20,
child: CustomPaint( child: CustomPaint(
painter: DownloadPainter( painter: DownloadPainter(
color: Colors.grey[700], color: Colors.grey[700],
fraction: 0, fraction: 0,
progressColor: context.accentColor, progressColor: context.accentColor,
), ),
), ),
), ),
), ),
() => _requestDownload(task.episode)); () => _requestDownload(task.episode));
break; break;
case 2: case 2:
return Material( return Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
onTap: () { onTap: () {
if (task.progress > 0) _pauseDownload(task.episode); if (task.progress > 0) _pauseDownload(task.episode);
}, },
child: Container( child: Container(
height: 50.0, height: 50.0,
alignment: Alignment.center, alignment: Alignment.center,
padding: EdgeInsets.symmetric(horizontal: 15.0), padding: EdgeInsets.symmetric(horizontal: 15.0),
child: TweenAnimationBuilder( child: TweenAnimationBuilder(
duration: Duration(milliseconds: 1000), duration: Duration(milliseconds: 1000),
tween: Tween(begin: 0.0, end: 1.0), tween: Tween(begin: 0.0, end: 1.0),
builder: (context, fraction, child) => SizedBox( builder: (context, fraction, child) => SizedBox(
height: 20, height: 20,
width: 20, width: 20,
child: CustomPaint( child: CustomPaint(
painter: DownloadPainter( painter: DownloadPainter(
color: context.accentColor, color: context.accentColor,
fraction: fraction, fraction: fraction,
progressColor: context.accentColor, progressColor: context.accentColor,
progress: task.progress / 100), progress: task.progress / 100),
), ),
), ),
), ),
), ),
), ),
); );
break; break;
case 6: case 6:
return Material( return Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
onTap: () { onTap: () {
_resumeDownload(task.episode); _resumeDownload(task.episode);
}, },
child: Container( child: Container(
height: 50.0, height: 50.0,
alignment: Alignment.center, alignment: Alignment.center,
padding: EdgeInsets.symmetric(horizontal: 15), padding: EdgeInsets.symmetric(horizontal: 15),
child: TweenAnimationBuilder( child: TweenAnimationBuilder(
duration: Duration(milliseconds: 500), duration: Duration(milliseconds: 500),
tween: Tween(begin: 0.0, end: 1.0), tween: Tween(begin: 0.0, end: 1.0),
builder: (context, fraction, child) => SizedBox( builder: (context, fraction, child) => SizedBox(
height: 20, height: 20,
width: 20, width: 20,
child: CustomPaint( child: CustomPaint(
painter: DownloadPainter( painter: DownloadPainter(
color: context.accentColor, color: context.accentColor,
fraction: 1, fraction: 1,
progressColor: context.accentColor, progressColor: context.accentColor,
progress: task.progress / 100, progress: task.progress / 100,
pauseProgress: fraction), pauseProgress: fraction),
), ),
), ),
), ),
), ),
), ),
); );
break; break;
case 3: case 3:
Provider.of<AudioPlayerNotifier>(context, listen: false) Provider.of<AudioPlayerNotifier>(context, listen: false)
.updateMediaItem(task.episode); .updateMediaItem(task.episode);
return Material( return Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
onTap: () { onTap: () {
_deleteDownload(task.episode); _deleteDownload(task.episode);
}, },
child: Container( child: Container(
height: 50.0, height: 50.0,
alignment: Alignment.center, alignment: Alignment.center,
padding: EdgeInsets.symmetric(horizontal: 15), padding: EdgeInsets.symmetric(horizontal: 15),
child: SizedBox( child: SizedBox(
height: 20, height: 20,
width: 20, width: 20,
child: CustomPaint( child: CustomPaint(
painter: DownloadPainter( painter: DownloadPainter(
color: context.accentColor, color: context.accentColor,
fraction: 1, fraction: 1,
progressColor: context.accentColor, progressColor: context.accentColor,
progress: 1, progress: 1,
), ),
), ),
), ),
), ),
), ),
); );
break; break;
case 4: case 4:
return _buttonOnMenu(Icon(Icons.refresh, color: Colors.red), return _buttonOnMenu(Icon(Icons.refresh, color: Colors.red),
() => _retryDownload(task.episode)); () => _retryDownload(task.episode));
break; break;
default: default:
return Center(); return Center();
} }
} }
} }

View File

@ -9,6 +9,8 @@ import 'intl/messages_all.dart';
// ************************************************************************** // **************************************************************************
// ignore_for_file: non_constant_identifier_names, lines_longer_than_80_chars // ignore_for_file: non_constant_identifier_names, lines_longer_than_80_chars
// ignore_for_file: join_return_with_assignment, prefer_final_in_for_each
// ignore_for_file: avoid_redundant_argument_values
class S { class S {
S(); S();

View File

@ -1,334 +1,334 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:line_icons/line_icons.dart'; import 'package:line_icons/line_icons.dart';
import '../util/custom_widget.dart'; import '../util/custom_widget.dart';
import '../util/extension_helper.dart'; import '../util/extension_helper.dart';
const String version = '0.5.0'; const String version = '0.5.0';
class AboutApp extends StatefulWidget { class AboutApp extends StatefulWidget {
@override @override
_AboutAppState createState() => _AboutAppState(); _AboutAppState createState() => _AboutAppState();
} }
class _AboutAppState extends State<AboutApp> { class _AboutAppState extends State<AboutApp> {
ScrollController _scrollController; ScrollController _scrollController;
bool _scroll; bool _scroll;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_scroll = false; _scroll = false;
_scrollController = ScrollController() _scrollController = ScrollController()
..addListener(() { ..addListener(() {
if (_scrollController.offset > 0 && !_scroll && mounted) { if (_scrollController.offset > 0 && !_scroll && mounted) {
setState(() => _scroll = true); setState(() => _scroll = true);
} }
if (_scrollController.offset <= 0 && _scroll && mounted) { if (_scrollController.offset <= 0 && _scroll && mounted) {
setState(() => _scroll = false); setState(() => _scroll = false);
} }
}); });
} }
Widget _listItem( Widget _listItem(
BuildContext context, String text, IconData icons, String url) => BuildContext context, String text, IconData icons, String url) =>
InkWell( InkWell(
onTap: () => url.launchUrl, onTap: () => url.launchUrl,
child: Container( child: Container(
height: 50.0, height: 50.0,
padding: EdgeInsets.symmetric(horizontal: 20.0), padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
bottom: Divider.createBorderSide(context), bottom: Divider.createBorderSide(context),
), ),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Icon(icons, color: Theme.of(context).accentColor), Icon(icons, color: Theme.of(context).accentColor),
Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: 10), padding: EdgeInsets.symmetric(horizontal: 10),
), ),
Text(text), Text(text),
], ],
), ),
), ),
); );
Widget _translatorInfo(BuildContext context, {String name, String flag}) => Widget _translatorInfo(BuildContext context, {String name, String flag}) =>
Container( Container(
height: 50.0, height: 50.0,
padding: EdgeInsets.symmetric(horizontal: 20.0), padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
bottom: Divider.createBorderSide(context), bottom: Divider.createBorderSide(context),
), ),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Icon(LineIcons.user, color: Theme.of(context).accentColor), Icon(LineIcons.user, color: Theme.of(context).accentColor),
Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: 10), padding: EdgeInsets.symmetric(horizontal: 10),
), ),
Expanded( Expanded(
child: Text( child: Text(
name, name,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.fade, overflow: TextOverflow.fade,
)), )),
if (flag != null) if (flag != null)
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
child: Image( child: Image(
image: AssetImage('assets/$flag.png'), image: AssetImage('assets/$flag.png'),
height: 20, height: 20,
width: 30, width: 30,
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
), ),
], ],
), ),
); );
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
OverlayEntry _createOverlayEntry(TapDownDetails detail) { OverlayEntry _createOverlayEntry(TapDownDetails detail) {
// RenderBox renderBox = context.findRenderObject(); // RenderBox renderBox = context.findRenderObject();
var offset = detail.globalPosition; var offset = detail.globalPosition;
return OverlayEntry( return OverlayEntry(
builder: (constext) => Positioned( builder: (constext) => Positioned(
left: offset.dx - 5, left: offset.dx - 5,
top: offset.dy - 120, top: offset.dy - 120,
child: Container( child: Container(
width: 20, width: 20,
height: 120, height: 120,
color: Colors.transparent, color: Colors.transparent,
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
child: HeartSet(height: 120, width: 20)), child: HeartSet(height: 120, width: 20)),
), ),
); );
} }
final s = context.s; final s = context.s;
return AnnotatedRegion<SystemUiOverlayStyle>( return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle( value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness, statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: Theme.of(context).primaryColor, systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness: systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness, Theme.of(context).accentColorBrightness,
), ),
child: Scaffold( child: Scaffold(
backgroundColor: Theme.of(context).primaryColor, backgroundColor: Theme.of(context).primaryColor,
appBar: AppBar( appBar: AppBar(
title: Text(s.homeToprightMenuAbout), title: Text(s.homeToprightMenuAbout),
leading: CustomBackButton(), leading: CustomBackButton(),
elevation: _scroll ? 1 : 0, elevation: _scroll ? 1 : 0,
), ),
body: SafeArea( body: SafeArea(
child: ScrollConfiguration( child: ScrollConfiguration(
behavior: NoGrowBehavior(), behavior: NoGrowBehavior(),
child: SingleChildScrollView( child: SingleChildScrollView(
controller: _scrollController, controller: _scrollController,
scrollDirection: Axis.vertical, scrollDirection: Axis.vertical,
child: Padding( child: Padding(
padding: const EdgeInsets.all(20.0), padding: const EdgeInsets.all(20.0),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Container( Container(
height: 110.0, height: 110.0,
alignment: Alignment.center, alignment: Alignment.center,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Image( Image(
image: AssetImage('assets/logo.png'), image: AssetImage('assets/logo.png'),
height: 80, height: 80,
), ),
Text(s.version(version)), Text(s.version(version)),
], ],
), ),
), ),
Padding( Padding(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
child: Text( child: Text(
'Tsacdop is a podcast player built with flutter, a clean, simply beautiful and friendly app.', 'Tsacdop is a podcast player built with flutter, a clean, simply beautiful and friendly app.',
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
TextButton( TextButton(
onPressed: () => onPressed: () =>
'https://tsacdop.stonegate.me/#/privacy' 'https://tsacdop.stonegate.me/#/privacy'
.launchUrl, .launchUrl,
style: TextButton.styleFrom( style: TextButton.styleFrom(
primary: context.accentColor, primary: context.accentColor,
textStyle: textStyle:
TextStyle(fontWeight: FontWeight.bold)), TextStyle(fontWeight: FontWeight.bold)),
child: Text( child: Text(
s.privacyPolicy, s.privacyPolicy,
), ),
), ),
Container( Container(
margin: const EdgeInsets.symmetric(horizontal: 5), margin: const EdgeInsets.symmetric(horizontal: 5),
height: 4, height: 4,
width: 4, width: 4,
decoration: BoxDecoration( decoration: BoxDecoration(
color: context.accentColor, color: context.accentColor,
shape: BoxShape.circle), shape: BoxShape.circle),
), ),
TextButton( TextButton(
onPressed: () => onPressed: () =>
'https://tsacdop.stonegate.me/#/changelog' 'https://tsacdop.stonegate.me/#/changelog'
.launchUrl, .launchUrl,
style: TextButton.styleFrom( style: TextButton.styleFrom(
primary: context.accentColor, primary: context.accentColor,
textStyle: textStyle:
TextStyle(fontWeight: FontWeight.bold)), TextStyle(fontWeight: FontWeight.bold)),
child: Text(s.changelog, child: Text(s.changelog,
style: TextStyle(color: context.accentColor)), style: TextStyle(color: context.accentColor)),
), ),
], ],
), ),
Padding( Padding(
padding: EdgeInsets.only(top: 20.0, bottom: 10.0), padding: EdgeInsets.only(top: 20.0, bottom: 10.0),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
_listItem(context, 'Twitter @tsacdop', _listItem(context, 'Twitter @tsacdop',
LineIcons.twitter, 'https://twitter.com/tsacdop'), LineIcons.twitter, 'https://twitter.com/tsacdop'),
_listItem(context, 'GitHub', LineIcons.github_alt, _listItem(context, 'GitHub', LineIcons.github_alt,
'https://github.com/stonega/tsacdop'), 'https://github.com/stonega/tsacdop'),
_listItem(context, 'Telegram', LineIcons.telegram, _listItem(context, 'Telegram', LineIcons.telegram,
'https://t.me/joinchat/Bk3LkRpTHy40QYC78PK7Qg'), 'https://t.me/joinchat/Bk3LkRpTHy40QYC78PK7Qg'),
Center( Center(
child: SizedBox( child: SizedBox(
width: 200, width: 200,
child: ElevatedButton( child: ElevatedButton(
onPressed: () => onPressed: () =>
'https://www.buymeacoffee.com/stonegate' 'https://www.buymeacoffee.com/stonegate'
.launchUrl, .launchUrl,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
primary: context.accentColor), primary: context.accentColor),
child: Container( child: Container(
height: 30.0, height: 30.0,
padding: padding:
EdgeInsets.symmetric(horizontal: 4.0), EdgeInsets.symmetric(horizontal: 4.0),
alignment: Alignment.center, alignment: Alignment.center,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Text('Buy Me A Coffee', Text('Buy Me A Coffee',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold)), fontWeight: FontWeight.bold)),
SizedBox(width: 10), SizedBox(width: 10),
Image( Image(
image: AssetImage( image: AssetImage(
'assets/buymeacoffee.png'), 'assets/buymeacoffee.png'),
height: 20, height: 20,
fit: BoxFit.fitHeight, fit: BoxFit.fitHeight,
), ),
], ],
), ),
), ),
), ),
), ),
), ),
], ],
), ),
), ),
Padding( Padding(
padding: EdgeInsets.all(10.0), padding: EdgeInsets.all(10.0),
), ),
Padding( Padding(
padding: EdgeInsets.only(top: 20.0, bottom: 10.0), padding: EdgeInsets.only(top: 20.0, bottom: 10.0),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Row( Row(
children: [ children: [
SizedBox(width: 25), SizedBox(width: 25),
Text( Text(
s.translators, s.translators,
style: TextStyle( style: TextStyle(
color: Theme.of(context).accentColor, color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold), fontWeight: FontWeight.bold),
), ),
SizedBox(width: 2), SizedBox(width: 2),
Icon(Icons.favorite, color: Colors.red, size: 20), Icon(Icons.favorite, color: Colors.red, size: 20),
], ],
), ),
_translatorInfo(context, name: 'Atrate'), _translatorInfo(context, name: 'Atrate'),
_translatorInfo(context, name: 'ppp', flag: 'fr'), _translatorInfo(context, name: 'ppp', flag: 'fr'),
_translatorInfo(context, _translatorInfo(context,
name: 'Joel Israel', flag: 'mx'), name: 'Joel Israel', flag: 'mx'),
_translatorInfo(context, _translatorInfo(context,
name: 'Bruno Pinheiro', flag: 'pt'), name: 'Bruno Pinheiro', flag: 'pt'),
_translatorInfo(context, _translatorInfo(context,
name: 'Edoardo Maria Elidoro', flag: 'it'), name: 'Edoardo Maria Elidoro', flag: 'it'),
], ],
), ),
), ),
//Spacer(), //Spacer(),
Padding( Padding(
padding: EdgeInsets.symmetric(vertical: 10), padding: EdgeInsets.symmetric(vertical: 10),
), ),
Container( Container(
height: 50, height: 50,
alignment: Alignment.center, alignment: Alignment.center,
child: GestureDetector( child: GestureDetector(
onTapDown: (detail) async { onTapDown: (detail) async {
OverlayEntry _overlayEntry; OverlayEntry _overlayEntry;
_overlayEntry = _createOverlayEntry(detail); _overlayEntry = _createOverlayEntry(detail);
Overlay.of(context).insert(_overlayEntry); Overlay.of(context).insert(_overlayEntry);
await Future.delayed(Duration(seconds: 2)); await Future.delayed(Duration(seconds: 2));
_overlayEntry?.remove(); _overlayEntry?.remove();
}, },
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Image.asset( Image.asset(
'assets/text.png', 'assets/text.png',
height: 25, height: 25,
), ),
Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: 5), padding: EdgeInsets.symmetric(horizontal: 5),
), ),
Icon( Icon(
Icons.favorite, Icons.favorite,
color: Colors.blue, color: Colors.blue,
), ),
Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: 5), padding: EdgeInsets.symmetric(horizontal: 5),
), ),
FlutterLogo( FlutterLogo(
size: 18, size: 18,
), ),
], ],
), ),
), ),
), ),
], ],
), ),
), ),
), ),
), ),
), ),
), ),
); );
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,154 +1,154 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../episodes/episode_detail.dart'; import '../episodes/episode_detail.dart';
import '../state/download_state.dart'; import '../state/download_state.dart';
import '../type/episode_task.dart'; import '../type/episode_task.dart';
import '../util/pageroute.dart'; import '../util/pageroute.dart';
class DownloadList extends StatefulWidget { class DownloadList extends StatefulWidget {
DownloadList({Key key}) : super(key: key); DownloadList({Key key}) : super(key: key);
@override @override
_DownloadListState createState() => _DownloadListState(); _DownloadListState createState() => _DownloadListState();
} }
Widget _downloadButton(EpisodeTask task, BuildContext context) { Widget _downloadButton(EpisodeTask task, BuildContext context) {
var downloader = Provider.of<DownloadState>(context, listen: false); var downloader = Provider.of<DownloadState>(context, listen: false);
switch (task.status.value) { switch (task.status.value) {
case 2: case 2:
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
IconButton( IconButton(
icon: Icon( icon: Icon(
Icons.pause_circle_filled, Icons.pause_circle_filled,
), ),
onPressed: () => downloader.pauseTask(task.episode), onPressed: () => downloader.pauseTask(task.episode),
), ),
IconButton( IconButton(
icon: Icon(Icons.close), icon: Icon(Icons.close),
onPressed: () => downloader.delTask(task.episode), onPressed: () => downloader.delTask(task.episode),
), ),
], ],
); );
case 4: case 4:
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
IconButton( IconButton(
icon: Icon(Icons.refresh, color: Colors.red), icon: Icon(Icons.refresh, color: Colors.red),
onPressed: () => downloader.retryTask(task.episode), onPressed: () => downloader.retryTask(task.episode),
), ),
IconButton( IconButton(
icon: Icon(Icons.close), icon: Icon(Icons.close),
onPressed: () => downloader.delTask(task.episode), onPressed: () => downloader.delTask(task.episode),
), ),
], ],
); );
case 6: case 6:
return Row(mainAxisSize: MainAxisSize.min, children: [ return Row(mainAxisSize: MainAxisSize.min, children: [
IconButton( IconButton(
icon: Icon(Icons.play_circle_filled), icon: Icon(Icons.play_circle_filled),
onPressed: () => downloader.resumeTask(task.episode), onPressed: () => downloader.resumeTask(task.episode),
), ),
IconButton( IconButton(
icon: Icon(Icons.close), icon: Icon(Icons.close),
onPressed: () => downloader.delTask(task.episode), onPressed: () => downloader.delTask(task.episode),
), ),
]); ]);
break; break;
default: default:
return SizedBox( return SizedBox(
width: 10, width: 10,
height: 10, height: 10,
); );
} }
} }
class _DownloadListState extends State<DownloadList> { class _DownloadListState extends State<DownloadList> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<DownloadState>(builder: (_, downloader, __) { return Consumer<DownloadState>(builder: (_, downloader, __) {
final tasks = downloader.episodeTasks final tasks = downloader.episodeTasks
.where((task) => task.status.value != 3) .where((task) => task.status.value != 3)
.toList(); .toList();
return tasks.length > 0 return tasks.length > 0
? SliverPadding( ? SliverPadding(
padding: EdgeInsets.symmetric(vertical: 5.0), padding: EdgeInsets.symmetric(vertical: 5.0),
sliver: SliverList( sliver: SliverList(
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
return ListTile( return ListTile(
onTap: () => Navigator.push( onTap: () => Navigator.push(
context, context,
ScaleRoute( ScaleRoute(
page: EpisodeDetail( page: EpisodeDetail(
episodeItem: tasks[index].episode, episodeItem: tasks[index].episode,
)), )),
), ),
title: SizedBox( title: SizedBox(
height: 40, height: 40,
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
flex: 5, flex: 5,
child: Text( child: Text(
tasks[index].episode.title, tasks[index].episode.title,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
Expanded( Expanded(
flex: 1, flex: 1,
child: tasks[index].progress >= 0 && child: tasks[index].progress >= 0 &&
tasks[index].status != tasks[index].status !=
DownloadTaskStatus.failed DownloadTaskStatus.failed
? Container( ? Container(
width: 40.0, width: 40.0,
height: 20.0, height: 20.0,
padding: padding:
EdgeInsets.symmetric(horizontal: 2), EdgeInsets.symmetric(horizontal: 2),
alignment: Alignment.center, alignment: Alignment.center,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(
Radius.circular(6)), Radius.circular(6)),
color: Colors.red), color: Colors.red),
child: Text( child: Text(
'${tasks[index].progress}%', '${tasks[index].progress}%',
textAlign: TextAlign.center, textAlign: TextAlign.center,
maxLines: 1, maxLines: 1,
style: TextStyle(color: Colors.white), style: TextStyle(color: Colors.white),
)) ))
: Container( : Container(
height: 40, height: 40,
), ),
), ),
], ],
), ),
), ),
subtitle: SizedBox( subtitle: SizedBox(
height: 2, height: 2,
child: LinearProgressIndicator( child: LinearProgressIndicator(
value: tasks[index].progress / 100, value: tasks[index].progress / 100,
), ),
), ),
leading: CircleAvatar( leading: CircleAvatar(
radius: 20, radius: 20,
backgroundImage: tasks[index].episode.avatarImage), backgroundImage: tasks[index].episode.avatarImage),
trailing: _downloadButton(tasks[index], context), trailing: _downloadButton(tasks[index], context),
); );
}, },
childCount: tasks.length, childCount: tasks.length,
), ),
), ),
) )
: SliverToBoxAdapter( : SliverToBoxAdapter(
child: Center(), child: Center(),
); );
}); });
} }
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,216 +1,202 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer' as developer; import 'dart:developer' as developer;
import 'dart:io'; import 'dart:io';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:line_icons/line_icons.dart'; import 'package:line_icons/line_icons.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../local_storage/key_value_storage.dart'; import '../local_storage/key_value_storage.dart';
import '../service/opml_build.dart'; import '../service/opml_build.dart';
import '../settings/settting.dart'; import '../settings/settting.dart';
import '../state/podcast_group.dart'; import '../state/podcast_group.dart';
import '../state/refresh_podcast.dart'; import '../state/refresh_podcast.dart';
import '../util/extension_helper.dart'; import '../util/extension_helper.dart';
import 'about.dart'; import 'about.dart';
class PopupMenu extends StatefulWidget { class PopupMenu extends StatefulWidget {
@override @override
_PopupMenuState createState() => _PopupMenuState(); _PopupMenuState createState() => _PopupMenuState();
} }
class _PopupMenuState extends State<PopupMenu> { class _PopupMenuState extends State<PopupMenu> {
Future<String> _getRefreshDate(BuildContext context) async { Future<String> _getRefreshDate(BuildContext context) async {
int refreshDate; int refreshDate;
var refreshstorage = KeyValueStorage('refreshdate'); var refreshstorage = KeyValueStorage('refreshdate');
var i = await refreshstorage.getInt(); var i = await refreshstorage.getInt();
if (i == 0) { if (i == 0) {
var refreshstorage = KeyValueStorage('refreshdate'); var refreshstorage = KeyValueStorage('refreshdate');
await refreshstorage.saveInt(DateTime.now().millisecondsSinceEpoch); await refreshstorage.saveInt(DateTime.now().millisecondsSinceEpoch);
refreshDate = DateTime.now().millisecondsSinceEpoch; refreshDate = DateTime.now().millisecondsSinceEpoch;
} else { } else {
refreshDate = i; refreshDate = i;
} }
return refreshDate.toDate(context); return refreshDate.toDate(context);
// var date = DateTime.fromMillisecondsSinceEpoch(refreshDate); }
// var difference = DateTime.now().difference(date);
// if (difference.inSeconds < 60) { void _saveOmpl(String path) async {
// return s.secondsAgo(difference.inSeconds); var subscribeWorker = Provider.of<GroupList>(context, listen: false);
// } else if (difference.inMinutes < 60) { var rssExp = RegExp(r'^(https?):\/\/(.*)');
// return s.minsAgo(difference.inMinutes); final s = context.s;
// } else if (difference.inHours < 24) { var file = File(path);
// return s.hoursAgo(difference.inHours); try {
// } else if (difference.inDays < 7) { final opml = file.readAsStringSync();
// return s.daysAgo(difference.inDays); Map<String, List<OmplOutline>> data = PodcastsBackup.parseOPML(opml);
// } else { for (var entry in data.entries) {
// return DateFormat.yMMMd() var title = entry.key;
// .format(DateTime.fromMillisecondsSinceEpoch(refreshDate)); var list = entry.value.reversed;
// } for (var rss in list) {
} var rssLink = rssExp.stringMatch(rss.xmlUrl);
if (rssLink != null) {
void _saveOmpl(String path) async { var item = SubscribeItem(rssLink, rss.text, group: title);
var subscribeWorker = Provider.of<GroupList>(context, listen: false); await subscribeWorker.setSubscribeItem(item);
var rssExp = RegExp(r'^(https?):\/\/(.*)'); await Future.delayed(Duration(milliseconds: 200));
final s = context.s; }
var file = File(path); }
try { }
final opml = file.readAsStringSync(); } catch (e) {
Map<String, List<OmplOutline>> data = PodcastsBackup.parseOPML(opml); developer.log(e, name: 'OMPL parse error');
for (var entry in data.entries) { Fluttertoast.showToast(
var title = entry.key; msg: s.toastFileError,
var list = entry.value.reversed; gravity: ToastGravity.TOP,
for (var rss in list) { );
var rssLink = rssExp.stringMatch(rss.xmlUrl); }
if (rssLink != null) { }
var item = SubscribeItem(rssLink, rss.text, group: title);
await subscribeWorker.setSubscribeItem(item); void _getFilePath() async {
await Future.delayed(Duration(milliseconds: 200)); final s = context.s;
} try {
} var filePickResult =
} await FilePicker.platform.pickFiles(type: FileType.any);
} catch (e) { if (filePickResult == null) {
developer.log(e, name: 'OMPL parse error'); return;
Fluttertoast.showToast( }
msg: s.toastFileError, Fluttertoast.showToast(
gravity: ToastGravity.TOP, msg: s.toastReadFile,
); gravity: ToastGravity.TOP,
} );
} final filePath = filePickResult.files.first.path;
_saveOmpl(filePath);
void _getFilePath() async { } on PlatformException catch (e) {
final s = context.s; developer.log(e.toString(), name: 'Get OMPL file');
try { }
var filePickResult = }
await FilePicker.platform.pickFiles(type: FileType.any);
if (filePickResult == null) { @override
return; Widget build(BuildContext context) {
} var refreshWorker = Provider.of<RefreshWorker>(context, listen: false);
Fluttertoast.showToast( final s = context.s;
msg: s.toastReadFile, return Material(
gravity: ToastGravity.TOP, color: Colors.transparent,
); borderRadius: BorderRadius.circular(100),
final filePath = filePickResult.files.first.path; clipBehavior: Clip.hardEdge,
_saveOmpl(filePath); child: PopupMenuButton<int>(
} on PlatformException catch (e) { shape: RoundedRectangleBorder(
developer.log(e.toString(), name: 'Get OMPL file'); borderRadius: BorderRadius.all(Radius.circular(10))),
} elevation: 1,
} tooltip: s.menu,
itemBuilder: (context) => [
@override PopupMenuItem(
Widget build(BuildContext context) { value: 1,
var refreshWorker = Provider.of<RefreshWorker>(context, listen: false); child: Container(
final s = context.s; padding: EdgeInsets.only(left: 10),
return Material( child: Row(
color: Colors.transparent, crossAxisAlignment: CrossAxisAlignment.start,
borderRadius: BorderRadius.circular(100), children: <Widget>[
clipBehavior: Clip.hardEdge, Icon(LineIcons.cloud_download_alt_solid),
child: PopupMenuButton<int>( Padding(
shape: RoundedRectangleBorder( padding: EdgeInsets.symmetric(horizontal: 5.0),
borderRadius: BorderRadius.all(Radius.circular(10))), ),
elevation: 1, Column(
tooltip: s.menu, crossAxisAlignment: CrossAxisAlignment.start,
itemBuilder: (context) => [ children: <Widget>[
PopupMenuItem( Text(
value: 1, s.homeToprightMenuRefreshAll,
child: Container( ),
padding: EdgeInsets.only(left: 10), FutureBuilder<String>(
child: Row( future: _getRefreshDate(context),
crossAxisAlignment: CrossAxisAlignment.start, builder: (_, snapshot) {
children: <Widget>[ if (snapshot.hasData) {
Icon(LineIcons.cloud_download_alt_solid), return Text(
Padding( snapshot.data,
padding: EdgeInsets.symmetric(horizontal: 5.0), style:
), TextStyle(color: Colors.red, fontSize: 12),
Column( );
crossAxisAlignment: CrossAxisAlignment.start, } else {
children: <Widget>[ return Center();
Text( }
s.homeToprightMenuRefreshAll, })
), ],
FutureBuilder<String>( ),
future: _getRefreshDate(context), ],
builder: (_, snapshot) { ),
if (snapshot.hasData) { ),
return Text( ),
snapshot.data, PopupMenuItem(
style: value: 2,
TextStyle(color: Colors.red, fontSize: 12), child: Container(
); padding: EdgeInsets.only(left: 10),
} else { child: Row(
return Center(); children: <Widget>[
} Icon(LineIcons.paperclip_solid),
}) Padding(
], padding: EdgeInsets.symmetric(horizontal: 5.0),
), ),
], Text(s.homeToprightMenuImportOMPL),
), ],
), ),
), ),
PopupMenuItem( ),
value: 2, PopupMenuItem(
child: Container( value: 4,
padding: EdgeInsets.only(left: 10), child: Container(
child: Row( padding: EdgeInsets.only(left: 10),
children: <Widget>[ child: Row(
Icon(LineIcons.paperclip_solid), children: <Widget>[
Padding( Icon(LineIcons.cog_solid),
padding: EdgeInsets.symmetric(horizontal: 5.0), Padding(
), padding: EdgeInsets.symmetric(horizontal: 5.0),
Text(s.homeToprightMenuImportOMPL), ),
], Text(s.settings),
), ],
), ),
), ),
PopupMenuItem( ),
value: 4, PopupMenuItem(
child: Container( value: 5,
padding: EdgeInsets.only(left: 10), child: Container(
child: Row( padding: EdgeInsets.only(left: 10),
children: <Widget>[ child: Row(
Icon(LineIcons.cog_solid), children: <Widget>[
Padding( Icon(LineIcons.info_circle_solid),
padding: EdgeInsets.symmetric(horizontal: 5.0), Padding(
), padding: EdgeInsets.symmetric(horizontal: 5.0),
Text(s.settings), ),
], Text(s.homeToprightMenuAbout),
), ],
), ),
), ),
PopupMenuItem( ),
value: 5, ],
child: Container( onSelected: (value) {
padding: EdgeInsets.only(left: 10), if (value == 5) {
child: Row( Navigator.push(
children: <Widget>[ context, MaterialPageRoute(builder: (context) => AboutApp()));
Icon(LineIcons.info_circle_solid), } else if (value == 2) {
Padding( _getFilePath();
padding: EdgeInsets.symmetric(horizontal: 5.0), } else if (value == 1) {
), refreshWorker.start([]);
Text(s.homeToprightMenuAbout), } else if (value == 3) {
], // setting.theme != 2 ? setting.setTheme(2) : setting.setTheme(1);
), } else if (value == 4) {
), Navigator.push(
), context, MaterialPageRoute(builder: (context) => Settings()));
], }
onSelected: (value) { },
if (value == 5) { ),
Navigator.push( );
context, MaterialPageRoute(builder: (context) => AboutApp())); }
} else if (value == 2) { }
_getFilePath();
} else if (value == 1) {
refreshWorker.start([]);
} else if (value == 3) {
// setting.theme != 2 ? setting.setTheme(2) : setting.setTheme(1);
} else if (value == 4) {
Navigator.push(
context, MaterialPageRoute(builder: (context) => Settings()));
}
},
),
);
}
}

View File

@ -1,105 +1,105 @@
import 'package:connectivity/connectivity.dart'; import 'package:connectivity/connectivity.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../local_storage/key_value_storage.dart'; import '../local_storage/key_value_storage.dart';
import '../local_storage/sqflite_localpodcast.dart'; import '../local_storage/sqflite_localpodcast.dart';
import '../state/download_state.dart'; import '../state/download_state.dart';
import '../state/podcast_group.dart'; import '../state/podcast_group.dart';
import '../state/refresh_podcast.dart'; import '../state/refresh_podcast.dart';
import '../util/extension_helper.dart'; import '../util/extension_helper.dart';
class Import extends StatelessWidget { class Import extends StatelessWidget {
Widget importColumn(String text, BuildContext context) { Widget importColumn(String text, BuildContext context) {
return Container( return Container(
color: context.primaryColorDark, color: context.primaryColorDark,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
SizedBox(height: 2.0, child: LinearProgressIndicator()), SizedBox(height: 2.0, child: LinearProgressIndicator()),
Container( Container(
padding: EdgeInsets.symmetric(horizontal: 20.0), padding: EdgeInsets.symmetric(horizontal: 20.0),
height: 20.0, height: 20.0,
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text(text), child: Text(text),
), ),
]), ]),
); );
} }
_autoDownloadNew(BuildContext context) async { _autoDownloadNew(BuildContext context) async {
final dbHelper = DBHelper(); final dbHelper = DBHelper();
var downloader = Provider.of<DownloadState>(context, listen: false); var downloader = Provider.of<DownloadState>(context, listen: false);
var result = await Connectivity().checkConnectivity(); var result = await Connectivity().checkConnectivity();
var autoDownloadStorage = KeyValueStorage(autoDownloadNetworkKey); var autoDownloadStorage = KeyValueStorage(autoDownloadNetworkKey);
var autoDownloadNetwork = await autoDownloadStorage.getInt(); var autoDownloadNetwork = await autoDownloadStorage.getInt();
if (autoDownloadNetwork == 1) { if (autoDownloadNetwork == 1) {
var episodes = await dbHelper.getNewEpisodes('all'); var episodes = await dbHelper.getNewEpisodes('all');
// For safety // For safety
if (episodes.length < 100 && episodes.length > 0) { if (episodes.length < 100 && episodes.length > 0) {
for (var episode in episodes) { for (var episode in episodes) {
await downloader.startTask(episode, showNotification: true); await downloader.startTask(episode, showNotification: true);
} }
} }
} else if (result == ConnectivityResult.wifi) { } else if (result == ConnectivityResult.wifi) {
var episodes = await dbHelper.getNewEpisodes('all'); var episodes = await dbHelper.getNewEpisodes('all');
//For safety //For safety
if (episodes.length < 100 && episodes.length > 0) { if (episodes.length < 100 && episodes.length > 0) {
for (var episode in episodes) { for (var episode in episodes) {
await downloader.startTask(episode, showNotification: true); await downloader.startTask(episode, showNotification: true);
} }
} }
} }
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final s = context.s; final s = context.s;
var groupList = Provider.of<GroupList>(context, listen: false); var groupList = Provider.of<GroupList>(context, listen: false);
return Column( return Column(
children: <Widget>[ children: <Widget>[
Consumer<GroupList>( Consumer<GroupList>(
builder: (_, subscribeWorker, __) { builder: (_, subscribeWorker, __) {
var item = subscribeWorker.currentSubscribeItem; var item = subscribeWorker.currentSubscribeItem;
switch (item.subscribeState) { switch (item.subscribeState) {
case SubscribeState.start: case SubscribeState.start:
return importColumn( return importColumn(
s.notificationSubscribe(item.title), context); s.notificationSubscribe(item.title), context);
case SubscribeState.subscribe: case SubscribeState.subscribe:
return importColumn(s.notificaitonFatch(item.title), context); return importColumn(s.notificaitonFatch(item.title), context);
case SubscribeState.fetch: case SubscribeState.fetch:
return importColumn(s.notificationSuccess(item.title), context); return importColumn(s.notificationSuccess(item.title), context);
case SubscribeState.exist: case SubscribeState.exist:
return importColumn( return importColumn(
s.notificationSubscribeExisted(item.title), context); s.notificationSubscribeExisted(item.title), context);
case SubscribeState.error: case SubscribeState.error:
return importColumn( return importColumn(
s.notificationNetworkError(item.title), context); s.notificationNetworkError(item.title), context);
default: default:
return Center(); return Center();
} }
}, },
), ),
Consumer<RefreshWorker>( Consumer<RefreshWorker>(
builder: (context, refreshWorker, child) { builder: (context, refreshWorker, child) {
var item = refreshWorker.currentRefreshItem; var item = refreshWorker.currentRefreshItem;
if (refreshWorker.complete) { if (refreshWorker.complete) {
groupList.updateGroups(); groupList.updateGroups();
_autoDownloadNew(context); _autoDownloadNew(context);
} }
switch (item.refreshState) { switch (item.refreshState) {
case RefreshState.fetch: case RefreshState.fetch:
return importColumn(s.notificationUpdate(item.title), context); return importColumn(s.notificationUpdate(item.title), context);
case RefreshState.error: case RefreshState.error:
return importColumn( return importColumn(
s.notificationUpdateError(item.title), context); s.notificationUpdateError(item.title), context);
default: default:
return Center(); return Center();
} }
}, },
) )
], ],
); );
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,483 +1,483 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:line_icons/line_icons.dart'; import 'package:line_icons/line_icons.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../local_storage/key_value_storage.dart'; import '../local_storage/key_value_storage.dart';
import '../service/search_api.dart'; import '../service/search_api.dart';
import '../state/search_state.dart'; import '../state/search_state.dart';
import '../type/search_api/search_genre.dart'; import '../type/search_api/search_genre.dart';
import '../type/search_api/searchpodcast.dart'; import '../type/search_api/searchpodcast.dart';
import '../util/custom_widget.dart'; import '../util/custom_widget.dart';
import '../util/extension_helper.dart'; import '../util/extension_helper.dart';
import 'search_podcast.dart'; import 'search_podcast.dart';
class DiscoveryPage extends StatefulWidget { class DiscoveryPage extends StatefulWidget {
DiscoveryPage({this.onTap, Key key}) : super(key: key); DiscoveryPage({this.onTap, Key key}) : super(key: key);
final ValueChanged<String> onTap; final ValueChanged<String> onTap;
@override @override
DiscoveryPageState createState() => DiscoveryPageState(); DiscoveryPageState createState() => DiscoveryPageState();
} }
class DiscoveryPageState extends State<DiscoveryPage> { class DiscoveryPageState extends State<DiscoveryPage> {
Genre _selectedGenre; Genre _selectedGenre;
Genre get selectedGenre => _selectedGenre; Genre get selectedGenre => _selectedGenre;
final List<OnlinePodcast> _podcastList = []; final List<OnlinePodcast> _podcastList = [];
Future _searchTopPodcast; Future _searchTopPodcast;
Future<List<String>> _getSearchHistory() { Future<List<String>> _getSearchHistory() {
final storage = KeyValueStorage(searchHistoryKey); final storage = KeyValueStorage(searchHistoryKey);
final history = storage.getStringList(); final history = storage.getStringList();
return history; return history;
} }
void backToHome() { void backToHome() {
setState(() { setState(() {
_selectedGenre = null; _selectedGenre = null;
}); });
} }
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_searchTopPodcast = _getTopPodcasts(page: 1); _searchTopPodcast = _getTopPodcasts(page: 1);
} }
Widget _loadTopPodcasts() => Container( Widget _loadTopPodcasts() => Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10), color: context.primaryColor), borderRadius: BorderRadius.circular(10), color: context.primaryColor),
width: 120, width: 120,
margin: EdgeInsets.fromLTRB(10, 10, 0, 10), margin: EdgeInsets.fromLTRB(10, 10, 0, 10),
padding: EdgeInsets.all(4), padding: EdgeInsets.all(4),
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
child: Column( child: Column(
children: [ children: [
Expanded( Expanded(
flex: 2, flex: 2,
child: Center( child: Center(
child: Container( child: Container(
height: 50, height: 50,
width: 50, width: 50,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: context.primaryColorDark, color: context.primaryColorDark,
), ),
alignment: Alignment.center, alignment: Alignment.center,
child: SizedBox( child: SizedBox(
width: 20, width: 20,
height: 2, height: 2,
child: LinearProgressIndicator(value: 0), child: LinearProgressIndicator(value: 0),
), ),
), ),
), ),
), ),
Expanded( Expanded(
flex: 1, flex: 1,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Container( Container(
width: 80, width: 80,
height: context.textTheme.bodyText1.fontSize, height: context.textTheme.bodyText1.fontSize,
decoration: BoxDecoration( decoration: BoxDecoration(
color: context.primaryColorDark, color: context.primaryColorDark,
borderRadius: BorderRadius.circular(4)), borderRadius: BorderRadius.circular(4)),
), ),
SizedBox(height: 10), SizedBox(height: 10),
Container( Container(
width: 40, width: 40,
height: context.textTheme.bodyText1.fontSize, height: context.textTheme.bodyText1.fontSize,
decoration: BoxDecoration( decoration: BoxDecoration(
color: context.primaryColorDark, color: context.primaryColorDark,
borderRadius: BorderRadius.circular(4)), borderRadius: BorderRadius.circular(4)),
), ),
], ],
), ),
), ),
Expanded( Expanded(
flex: 1, flex: 1,
child: Center( child: Center(
child: SizedBox( child: SizedBox(
height: 32, height: 32,
child: OutlineButton( child: OutlineButton(
color: context.accentColor.withOpacity(0.5), color: context.accentColor.withOpacity(0.5),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100.0), borderRadius: BorderRadius.circular(100.0),
side: BorderSide(color: Colors.grey[500])), side: BorderSide(color: Colors.grey[500])),
highlightedBorderColor: Colors.grey[500], highlightedBorderColor: Colors.grey[500],
disabledTextColor: Colors.grey[500], disabledTextColor: Colors.grey[500],
child: Text(context.s.subscribe), child: Text(context.s.subscribe),
disabledBorderColor: Colors.grey[500], disabledBorderColor: Colors.grey[500],
onPressed: () {}), onPressed: () {}),
), ),
), ),
), ),
], ],
)); ));
Widget _historyList() => FutureBuilder<List<String>>( Widget _historyList() => FutureBuilder<List<String>>(
future: _getSearchHistory(), future: _getSearchHistory(),
initialData: [], initialData: [],
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data.isNotEmpty) { if (snapshot.hasData && snapshot.data.isNotEmpty) {
final history = snapshot.data; final history = snapshot.data;
return SizedBox( return SizedBox(
child: Wrap( child: Wrap(
direction: Axis.horizontal, direction: Axis.horizontal,
children: history children: history
.map<Widget>((e) => Padding( .map<Widget>((e) => Padding(
padding: const EdgeInsets.fromLTRB(8, 2, 0, 0), padding: const EdgeInsets.fromLTRB(8, 2, 0, 0),
child: FlatButton.icon( child: FlatButton.icon(
color: color:
Colors.accents[history.indexOf(e)].withAlpha(70), Colors.accents[history.indexOf(e)].withAlpha(70),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100.0), borderRadius: BorderRadius.circular(100.0),
), ),
onPressed: () => widget.onTap(e), onPressed: () => widget.onTap(e),
label: Text(e), label: Text(e),
icon: Icon( icon: Icon(
Icons.search, Icons.search,
size: 20, size: 20,
), ),
), ),
)) ))
.toList(), .toList(),
), ),
); );
} }
return SizedBox( return SizedBox(
height: 0, height: 0,
); );
}); });
Future<List<OnlinePodcast>> _getTopPodcasts({int page}) async { Future<List<OnlinePodcast>> _getTopPodcasts({int page}) async {
final searchEngine = ListenNotesSearch(); final searchEngine = ListenNotesSearch();
var searchResult = await searchEngine.fetchBestPodcast( var searchResult = await searchEngine.fetchBestPodcast(
genre: '', genre: '',
page: page, page: page,
); );
final podcastTopList = final podcastTopList =
searchResult.podcasts.map((e) => e?.toOnlinePodcast).toList(); searchResult.podcasts.map((e) => e?.toOnlinePodcast).toList();
_podcastList.addAll(podcastTopList.cast()); _podcastList.addAll(podcastTopList.cast());
return _podcastList; return _podcastList;
} }
Future<bool> _getHideDiscovery() async { Future<bool> _getHideDiscovery() async {
final storage = KeyValueStorage(hidePodcastDiscoveryKey); final storage = KeyValueStorage(hidePodcastDiscoveryKey);
return await storage.getBool(defaultValue: false); return await storage.getBool(defaultValue: false);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final searchState = context.watch<SearchState>(); final searchState = context.watch<SearchState>();
return FutureBuilder<bool>( return FutureBuilder<bool>(
future: _getHideDiscovery(), future: _getHideDiscovery(),
initialData: true, initialData: true,
builder: (context, snapshot) => snapshot.data builder: (context, snapshot) => snapshot.data
? ScrollConfiguration( ? ScrollConfiguration(
behavior: NoGrowBehavior(), behavior: NoGrowBehavior(),
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_historyList(), _historyList(),
SizedBox( SizedBox(
height: 150, height: 150,
child: Center( child: Center(
child: Icon( child: Icon(
Icons.search, Icons.search,
size: 80, size: 80,
color: Colors.grey[400], color: Colors.grey[400],
), ),
), ),
), ),
SizedBox( SizedBox(
height: 50, height: 50,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon( Icon(
LineIcons.microphone_solid, LineIcons.microphone_solid,
size: 30, size: 30,
color: Colors.lightBlue, color: Colors.lightBlue,
), ),
SizedBox(width: 50), SizedBox(width: 50),
Icon( Icon(
LineIcons.broadcast_tower_solid, LineIcons.broadcast_tower_solid,
size: 30, size: 30,
color: Colors.deepPurple, color: Colors.deepPurple,
), ),
SizedBox(width: 50), SizedBox(width: 50),
Icon( Icon(
LineIcons.rss_square_solid, LineIcons.rss_square_solid,
size: 30, size: 30,
color: Colors.blueGrey, color: Colors.blueGrey,
), ),
], ],
), ),
), ),
Padding( Padding(
padding: EdgeInsets.fromLTRB(50, 20, 50, 20), padding: EdgeInsets.fromLTRB(50, 20, 50, 20),
child: Center( child: Center(
child: Text( child: Text(
context.s.searchHelper, context.s.searchHelper,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: context.textTheme.headline6 style: context.textTheme.headline6
.copyWith(color: Colors.grey[400]), .copyWith(color: Colors.grey[400]),
), ),
), ),
), ),
], ],
), ),
), ),
) )
: PodcastSlideup( : PodcastSlideup(
searchEngine: SearchEngine.listenNotes, searchEngine: SearchEngine.listenNotes,
child: _selectedGenre == null child: _selectedGenre == null
? SingleChildScrollView( ? SingleChildScrollView(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_historyList(), _historyList(),
Padding( Padding(
padding: EdgeInsets.fromLTRB(20, 10, 10, 4), padding: EdgeInsets.fromLTRB(20, 10, 10, 4),
child: Text('Popular', child: Text('Popular',
style: context.textTheme.headline6 style: context.textTheme.headline6
.copyWith(color: context.accentColor)), .copyWith(color: context.accentColor)),
), ),
SizedBox( SizedBox(
height: 200, height: 200,
child: FutureBuilder<List<OnlinePodcast>>( child: FutureBuilder<List<OnlinePodcast>>(
future: _searchTopPodcast, future: _searchTopPodcast,
builder: (context, snapshot) { builder: (context, snapshot) {
return ScrollConfiguration( return ScrollConfiguration(
behavior: NoGrowBehavior(), behavior: NoGrowBehavior(),
child: ListView( child: ListView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
children: snapshot.hasData children: snapshot.hasData
? snapshot.data ? snapshot.data
.map<Widget>((podcast) { .map<Widget>((podcast) {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: borderRadius:
BorderRadius.circular( BorderRadius.circular(
10), 10),
color: color:
context.primaryColor), context.primaryColor),
width: 120, width: 120,
margin: EdgeInsets.fromLTRB( margin: EdgeInsets.fromLTRB(
10, 10, 0, 10), 10, 10, 0, 10),
child: Material( child: Material(
color: Colors.transparent, color: Colors.transparent,
borderRadius: borderRadius:
BorderRadius.circular( BorderRadius.circular(
10), 10),
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
child: InkWell( child: InkWell(
onTap: () { onTap: () {
searchState searchState
.selectedPodcast = .selectedPodcast =
podcast; podcast;
widget.onTap(''); widget.onTap('');
}, },
child: Padding( child: Padding(
padding: padding:
EdgeInsets.all(4.0), EdgeInsets.all(4.0),
child: Column( child: Column(
children: [ children: [
Expanded( Expanded(
flex: 2, flex: 2,
child: Center( child: Center(
child: PodcastAvatar( child: PodcastAvatar(
podcast)), podcast)),
), ),
Expanded( Expanded(
flex: 1, flex: 1,
child: Text( child: Text(
podcast.title, podcast.title,
textAlign: textAlign:
TextAlign TextAlign
.center, .center,
maxLines: 2, maxLines: 2,
overflow: overflow:
TextOverflow TextOverflow
.fade, .fade,
style: TextStyle( style: TextStyle(
fontWeight: fontWeight:
FontWeight FontWeight
.bold), .bold),
), ),
), ),
Expanded( Expanded(
flex: 1, flex: 1,
child: Center( child: Center(
child: SizedBox( child: SizedBox(
height: 32, height: 32,
child: SubscribeButton( child: SubscribeButton(
podcast)), podcast)),
), ),
), ),
], ],
), ),
), ),
), ),
), ),
); );
}).toList() }).toList()
: [ : [
_loadTopPodcasts(), _loadTopPodcasts(),
_loadTopPodcasts(), _loadTopPodcasts(),
_loadTopPodcasts(), _loadTopPodcasts(),
_loadTopPodcasts(), _loadTopPodcasts(),
]), ]),
); );
}), }),
), ),
Padding( Padding(
padding: EdgeInsets.fromLTRB(20, 10, 10, 4), padding: EdgeInsets.fromLTRB(20, 10, 10, 4),
child: Text('Categories', child: Text('Categories',
style: context.textTheme.headline6 style: context.textTheme.headline6
.copyWith(color: context.accentColor)), .copyWith(color: context.accentColor)),
), ),
ListView( ListView(
shrinkWrap: true, shrinkWrap: true,
physics: NeverScrollableScrollPhysics(), physics: NeverScrollableScrollPhysics(),
children: genres children: genres
.map<Widget>((e) => ListTile( .map<Widget>((e) => ListTile(
contentPadding: contentPadding:
EdgeInsets.fromLTRB(20, 0, 20, 0), EdgeInsets.fromLTRB(20, 0, 20, 0),
onTap: () { onTap: () {
widget.onTap(''); widget.onTap('');
setState(() => _selectedGenre = e); setState(() => _selectedGenre = e);
}, },
title: Text(e.name), title: Text(e.name),
)) ))
.toList(), .toList(),
), ),
SizedBox( SizedBox(
height: 40, height: 40,
child: Center( child: Center(
child: Image( child: Image(
image: context.brightness == Brightness.light image: context.brightness == Brightness.light
? AssetImage('assets/listennotes.png') ? AssetImage('assets/listennotes.png')
: AssetImage( : AssetImage(
'assets/listennotes_light.png'), 'assets/listennotes_light.png'),
height: 15, height: 15,
), ),
), ),
) )
], ],
), ),
) )
: _TopPodcastList(genre: _selectedGenre), : _TopPodcastList(genre: _selectedGenre),
), ),
); );
} }
} }
class _TopPodcastList extends StatefulWidget { class _TopPodcastList extends StatefulWidget {
final Genre genre; final Genre genre;
_TopPodcastList({this.genre, Key key}) : super(key: key); _TopPodcastList({this.genre, Key key}) : super(key: key);
@override @override
__TopPodcastListState createState() => __TopPodcastListState(); __TopPodcastListState createState() => __TopPodcastListState();
} }
class __TopPodcastListState extends State<_TopPodcastList> { class __TopPodcastListState extends State<_TopPodcastList> {
final List<OnlinePodcast> _podcastList = []; final List<OnlinePodcast> _podcastList = [];
Future _searchFuture; Future _searchFuture;
bool _loading; bool _loading;
int _page; int _page;
Future<List<OnlinePodcast>> _getTopPodcasts({Genre genre, int page}) async { Future<List<OnlinePodcast>> _getTopPodcasts({Genre genre, int page}) async {
final searchEngine = ListenNotesSearch(); final searchEngine = ListenNotesSearch();
var searchResult = await searchEngine.fetchBestPodcast( var searchResult = await searchEngine.fetchBestPodcast(
genre: genre.id, genre: genre.id,
page: page, page: page,
); );
final podcastTopList = final podcastTopList =
searchResult.podcasts.map((e) => e?.toOnlinePodcast).toList(); searchResult.podcasts.map((e) => e?.toOnlinePodcast).toList();
_podcastList.addAll(podcastTopList.cast()); _podcastList.addAll(podcastTopList.cast());
_loading = false; _loading = false;
return _podcastList; return _podcastList;
} }
@override @override
void initState() { void initState() {
_page = 1; _page = 1;
_searchFuture = _getTopPodcasts(genre: widget.genre, page: _page); _searchFuture = _getTopPodcasts(genre: widget.genre, page: _page);
super.initState(); super.initState();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FutureBuilder( return FutureBuilder(
future: _searchFuture, future: _searchFuture,
builder: (context, snapshot) { builder: (context, snapshot) {
if (!snapshot.hasData) { if (!snapshot.hasData) {
return Container( return Container(
padding: EdgeInsets.only(top: 200), padding: EdgeInsets.only(top: 200),
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
); );
} }
final content = snapshot.data; final content = snapshot.data;
return CustomScrollView( return CustomScrollView(
slivers: [ slivers: [
SliverToBoxAdapter( SliverToBoxAdapter(
child: Padding( child: Padding(
padding: EdgeInsets.fromLTRB(20, 10, 10, 4), padding: EdgeInsets.fromLTRB(20, 10, 10, 4),
child: Text(widget.genre.name, child: Text(widget.genre.name,
style: context.textTheme.headline6 style: context.textTheme.headline6
.copyWith(color: context.accentColor)), .copyWith(color: context.accentColor)),
), ),
), ),
SliverList( SliverList(
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
return SearchResult( return SearchResult(
onlinePodcast: content[index], onlinePodcast: content[index],
); );
}, },
childCount: content.length, childCount: content.length,
), ),
), ),
SliverToBoxAdapter( SliverToBoxAdapter(
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.only(top: 10.0, bottom: 20.0), padding: const EdgeInsets.only(top: 10.0, bottom: 20.0),
child: OutlineButton( child: OutlineButton(
highlightedBorderColor: context.accentColor, highlightedBorderColor: context.accentColor,
splashColor: context.accentColor.withOpacity(0.5), splashColor: context.accentColor.withOpacity(0.5),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(100))), borderRadius: BorderRadius.all(Radius.circular(100))),
child: _loading child: _loading
? SizedBox( ? SizedBox(
height: 20, height: 20,
width: 20, width: 20,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2, strokeWidth: 2,
)) ))
: Text(context.s.loadMore), : Text(context.s.loadMore),
onPressed: () => _loading onPressed: () => _loading
? null ? null
: setState( : setState(
() { () {
_loading = true; _loading = true;
_page++; _page++;
_searchFuture = _getTopPodcasts( _searchFuture = _getTopPodcasts(
genre: widget.genre, page: _page); genre: widget.genre, page: _page);
}, },
), ),
), ),
) )
], ],
), ),
) )
], ],
); );
}, },
); );
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,241 +1,241 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../home/home.dart'; import '../home/home.dart';
import '../state/setting_state.dart'; import '../state/setting_state.dart';
import '../util/extension_helper.dart'; import '../util/extension_helper.dart';
import '../util/pageroute.dart'; import '../util/pageroute.dart';
import 'firstpage.dart'; import 'firstpage.dart';
import 'fourthpage.dart'; import 'fourthpage.dart';
import 'secondpage.dart'; import 'secondpage.dart';
import 'thirdpage.dart'; import 'thirdpage.dart';
enum Goto { home, settings } enum Goto { home, settings }
class SlideIntro extends StatefulWidget { class SlideIntro extends StatefulWidget {
final Goto goto; final Goto goto;
SlideIntro({this.goto, Key key}) : super(key: key); SlideIntro({this.goto, Key key}) : super(key: key);
@override @override
_SlideIntroState createState() => _SlideIntroState(); _SlideIntroState createState() => _SlideIntroState();
} }
class _SlideIntroState extends State<SlideIntro> { class _SlideIntroState extends State<SlideIntro> {
final List<BoxShadow> _customShadow = [ final List<BoxShadow> _customShadow = [
BoxShadow(blurRadius: 2, offset: Offset(-2, -2), color: Colors.white54), BoxShadow(blurRadius: 2, offset: Offset(-2, -2), color: Colors.white54),
BoxShadow( BoxShadow(
blurRadius: 8, blurRadius: 8,
offset: Offset(2, 2), offset: Offset(2, 2),
color: Colors.grey[600].withOpacity(0.4)) color: Colors.grey[600].withOpacity(0.4))
]; ];
PageController _controller; PageController _controller;
double _position; double _position;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_position = 0; _position = 0;
_controller = PageController() _controller = PageController()
..addListener(() { ..addListener(() {
setState(() { setState(() {
_position = _controller.page; _position = _controller.page;
}); });
}); });
} }
@override @override
void dispose() { void dispose() {
_controller.dispose(); _controller.dispose();
super.dispose(); super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>( return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle( value: SystemUiOverlayStyle(
statusBarBrightness: Brightness.light, statusBarBrightness: Brightness.light,
systemNavigationBarIconBrightness: Brightness.dark), systemNavigationBarIconBrightness: Brightness.dark),
child: Scaffold( child: Scaffold(
backgroundColor: Colors.grey[100], backgroundColor: Colors.grey[100],
body: Container( body: Container(
child: Stack( child: Stack(
children: <Widget>[ children: <Widget>[
PageView( PageView(
physics: const PageScrollPhysics(), physics: const PageScrollPhysics(),
controller: _controller, controller: _controller,
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
children: <Widget>[ children: <Widget>[
FirstPage(), FirstPage(),
SecondPage(), SecondPage(),
ThirdPage(), ThirdPage(),
FourthPage(), FourthPage(),
], ],
), ),
Positioned( Positioned(
bottom: 0, bottom: 0,
left: 0, left: 0,
child: Container( child: Container(
color: Colors.grey[100].withOpacity(0.5), color: Colors.grey[100].withOpacity(0.5),
width: MediaQuery.of(context).size.width, width: MediaQuery.of(context).size.width,
// alignment: Alignment.center, // alignment: Alignment.center,
padding: padding:
EdgeInsets.only(left: 40, right: 20, bottom: 30, top: 20), EdgeInsets.only(left: 40, right: 20, bottom: 30, top: 20),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: Container( child: Container(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Container( Container(
alignment: Alignment.center, alignment: Alignment.center,
child: _position < 0.2 child: _position < 0.2
? Text( ? Text(
'1', '1',
style: TextStyle( style: TextStyle(
color: Color.fromRGBO( color: Color.fromRGBO(
35, 204, 198, 1)), 35, 204, 198, 1)),
) )
: Center(), : Center(),
margin: EdgeInsets.symmetric(horizontal: 10), margin: EdgeInsets.symmetric(horizontal: 10),
height: _position > 1 height: _position > 1
? 10 ? 10
: (1 - _position) * 10 + 10, : (1 - _position) * 10 + 10,
width: _position > 1 width: _position > 1
? 10 ? 10
: (1 - _position) * 10 + 10, : (1 - _position) * 10 + 10,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: Colors.white, color: Colors.white,
boxShadow: _customShadow), boxShadow: _customShadow),
), ),
Container( Container(
child: _position < 1.2 && _position > 0.8 child: _position < 1.2 && _position > 0.8
? Text('2', ? Text('2',
style: TextStyle( style: TextStyle(
color: Color.fromRGBO( color: Color.fromRGBO(
77, 145, 190, 1))) 77, 145, 190, 1)))
: Center(), : Center(),
alignment: Alignment.center, alignment: Alignment.center,
margin: EdgeInsets.symmetric(horizontal: 10), margin: EdgeInsets.symmetric(horizontal: 10),
height: _position > 2 height: _position > 2
? 10 ? 10
: 20 - (_position - 1).abs() * 10, : 20 - (_position - 1).abs() * 10,
width: _position > 2 width: _position > 2
? 10 ? 10
: 20 - (_position - 1).abs() * 10, : 20 - (_position - 1).abs() * 10,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: Colors.white, color: Colors.white,
boxShadow: _customShadow), boxShadow: _customShadow),
), ),
Container( Container(
child: _position < 2.2 && _position > 1.8 child: _position < 2.2 && _position > 1.8
? Text('3', ? Text('3',
style: TextStyle( style: TextStyle(
color: Color.fromRGBO( color: Color.fromRGBO(
35, 204, 198, 1))) 35, 204, 198, 1)))
: Center(), : Center(),
alignment: Alignment.center, alignment: Alignment.center,
margin: EdgeInsets.symmetric(horizontal: 10), margin: EdgeInsets.symmetric(horizontal: 10),
height: _position < 1 height: _position < 1
? 10 ? 10
: 20 - (_position - 2).abs() * 10, : 20 - (_position - 2).abs() * 10,
width: _position < 1 width: _position < 1
? 10 ? 10
: 20 - (_position - 2).abs() * 10, : 20 - (_position - 2).abs() * 10,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: Colors.white, color: Colors.white,
boxShadow: _customShadow), boxShadow: _customShadow),
), ),
Container( Container(
child: _position > 2.8 child: _position > 2.8
? Text( ? Text(
'4', '4',
style: TextStyle( style: TextStyle(
color: Color.fromRGBO( color: Color.fromRGBO(
77, 145, 190, 1)), 77, 145, 190, 1)),
) )
: Center(), : Center(),
alignment: Alignment.center, alignment: Alignment.center,
margin: EdgeInsets.symmetric(horizontal: 10), margin: EdgeInsets.symmetric(horizontal: 10),
height: _position < 2 height: _position < 2
? 10 ? 10
: 20 - (3 - _position) * 10, : 20 - (3 - _position) * 10,
width: _position < 2 width: _position < 2
? 10 ? 10
: 20 - (3 - _position) * 10, : 20 - (3 - _position) * 10,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: Colors.white, color: Colors.white,
boxShadow: _customShadow), boxShadow: _customShadow),
), ),
], ],
), ),
), ),
), ),
Container( Container(
alignment: Alignment.center, alignment: Alignment.center,
height: 40, height: 40,
width: 80, width: 80,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(width: 1, color: Colors.white), border: Border.all(width: 1, color: Colors.white),
borderRadius: BorderRadius.all(Radius.circular(20)), borderRadius: BorderRadius.all(Radius.circular(20)),
color: Colors.white, color: Colors.white,
boxShadow: _customShadow, boxShadow: _customShadow,
), ),
child: Material( child: Material(
color: Colors.transparent, color: Colors.transparent,
child: _position < 2.5 child: _position < 2.5
? InkWell( ? InkWell(
borderRadius: borderRadius:
BorderRadius.all(Radius.circular(20)), BorderRadius.all(Radius.circular(20)),
onTap: () => _controller.animateToPage( onTap: () => _controller.animateToPage(
_position.toInt() + 1, _position.toInt() + 1,
duration: Duration(milliseconds: 200), duration: Duration(milliseconds: 200),
curve: Curves.linear), curve: Curves.linear),
child: SizedBox( child: SizedBox(
height: 40, height: 40,
width: 80, width: 80,
child: Center( child: Center(
child: Text(context.s.next, child: Text(context.s.next,
style: TextStyle( style: TextStyle(
color: Colors.black))))) color: Colors.black)))))
: InkWell( : InkWell(
borderRadius: borderRadius:
BorderRadius.all(Radius.circular(20)), BorderRadius.all(Radius.circular(20)),
onTap: () { onTap: () {
if (widget.goto == Goto.home) { if (widget.goto == Goto.home) {
Navigator.push(context, Navigator.push(context,
SlideLeftRoute(page: Home())); SlideLeftRoute(page: Home()));
Provider.of<SettingState>(context, Provider.of<SettingState>(context,
listen: false) listen: false)
.saveShowIntro(1); .saveShowIntro(1);
} else if (widget.goto == Goto.settings) { } else if (widget.goto == Goto.settings) {
Navigator.pop(context); Navigator.pop(context);
} }
}, },
child: SizedBox( child: SizedBox(
height: 40, height: 40,
width: 80, width: 80,
child: Center( child: Center(
child: Text(context.s.done, child: Text(context.s.done,
style: TextStyle( style: TextStyle(
color: Colors.black))))), color: Colors.black))))),
), ),
), ),
], ],
), ),
), ),
), ),
], ],
), ),
), ),
), ),
); );
} }
} }

View File

@ -1,42 +1,42 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flare_flutter/flare_actor.dart'; import 'package:flare_flutter/flare_actor.dart';
import '../util/extension_helper.dart'; import '../util/extension_helper.dart';
class FirstPage extends StatefulWidget { class FirstPage extends StatefulWidget {
FirstPage({Key key}) : super(key: key); FirstPage({Key key}) : super(key: key);
@override @override
_FirstPageState createState() => _FirstPageState(); _FirstPageState createState() => _FirstPageState();
} }
class _FirstPageState extends State<FirstPage> { class _FirstPageState extends State<FirstPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
body: Container( body: Container(
color: Color.fromRGBO(35, 204, 198, 1), color: Color.fromRGBO(35, 204, 198, 1),
child: Center( child: Center(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Padding( Padding(
padding: EdgeInsets.symmetric(vertical: 100), padding: EdgeInsets.symmetric(vertical: 100),
), ),
Container( Container(
height: context.width * 3 / 4, height: context.width * 3 / 4,
// color: Colors.red, // color: Colors.red,
child: FlareActor( child: FlareActor(
'assets/splash.flr', 'assets/splash.flr',
alignment: Alignment.center, alignment: Alignment.center,
animation: 'logo', animation: 'logo',
fit: BoxFit.cover, fit: BoxFit.cover,
)), )),
Spacer(), Spacer(),
], ],
), ),
), ),
), ),
); );
} }
} }

View File

@ -1,46 +1,46 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flare_flutter/flare_actor.dart'; import 'package:flare_flutter/flare_actor.dart';
import '../util/extension_helper.dart'; import '../util/extension_helper.dart';
class FourthPage extends StatefulWidget { class FourthPage extends StatefulWidget {
FourthPage({Key key}) : super(key: key); FourthPage({Key key}) : super(key: key);
@override @override
_FourthPageState createState() => _FourthPageState(); _FourthPageState createState() => _FourthPageState();
} }
class _FourthPageState extends State<FourthPage> { class _FourthPageState extends State<FourthPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
color: Color.fromRGBO(77, 145, 190, 1), color: Color.fromRGBO(77, 145, 190, 1),
child: Center( child: Center(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Container( Container(
height: 200, height: 200,
alignment: Alignment.center, alignment: Alignment.center,
padding: EdgeInsets.all(40), padding: EdgeInsets.all(40),
child: Text( child: Text(
context.s.introFourthPage, context.s.introFourthPage,
style: TextStyle(fontSize: 30, color: Colors.white), style: TextStyle(fontSize: 30, color: Colors.white),
), ),
), ),
Container( Container(
height: context.width * 3 / 4, height: context.width * 3 / 4,
// color: Colors.red, // color: Colors.red,
child: FlareActor( child: FlareActor(
'assets/longtap.flr', 'assets/longtap.flr',
alignment: Alignment.center, alignment: Alignment.center,
animation: 'longtap', animation: 'longtap',
fit: BoxFit.cover, fit: BoxFit.cover,
)), )),
Spacer(), Spacer(),
], ],
), ),
), ),
); );
} }
} }

View File

@ -1,48 +1,48 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flare_flutter/flare_actor.dart'; import 'package:flare_flutter/flare_actor.dart';
import '../util/extension_helper.dart'; import '../util/extension_helper.dart';
class SecondPage extends StatefulWidget { class SecondPage extends StatefulWidget {
SecondPage({Key key}) : super(key: key); SecondPage({Key key}) : super(key: key);
@override @override
_SecondPageState createState() => _SecondPageState(); _SecondPageState createState() => _SecondPageState();
} }
class _SecondPageState extends State<SecondPage> { class _SecondPageState extends State<SecondPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
color: Color.fromRGBO(77, 145, 190, 1), color: Color.fromRGBO(77, 145, 190, 1),
child: Center( child: Center(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Container( Container(
height: 200, height: 200,
alignment: Alignment.center, alignment: Alignment.center,
padding: padding:
EdgeInsets.only(top: 20, bottom: 20, left: 40, right: 40), EdgeInsets.only(top: 20, bottom: 20, left: 40, right: 40),
child: Text( child: Text(
context.s.introSecondPage, context.s.introSecondPage,
style: TextStyle(fontSize: 30, color: Colors.white), style: TextStyle(fontSize: 30, color: Colors.white),
), ),
), ),
Container( Container(
height: context.width * 3 / 4, height: context.width * 3 / 4,
// color: Colors.red, // color: Colors.red,
child: FlareActor( child: FlareActor(
'assets/add.flr', 'assets/add.flr',
isPaused: false, isPaused: false,
alignment: Alignment.center, alignment: Alignment.center,
animation: 'add', animation: 'add',
fit: BoxFit.cover, fit: BoxFit.cover,
)), )),
Spacer(), Spacer(),
], ],
), ),
), ),
); );
} }
} }

View File

@ -1,46 +1,46 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flare_flutter/flare_actor.dart'; import 'package:flare_flutter/flare_actor.dart';
import '../util/extension_helper.dart'; import '../util/extension_helper.dart';
class ThirdPage extends StatefulWidget { class ThirdPage extends StatefulWidget {
ThirdPage({Key key}) : super(key: key); ThirdPage({Key key}) : super(key: key);
@override @override
_ThirdPageState createState() => _ThirdPageState(); _ThirdPageState createState() => _ThirdPageState();
} }
class _ThirdPageState extends State<ThirdPage> { class _ThirdPageState extends State<ThirdPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
color: Color.fromRGBO(35, 204, 198, 1), color: Color.fromRGBO(35, 204, 198, 1),
child: Center( child: Center(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Container( Container(
height: 200, height: 200,
alignment: Alignment.center, alignment: Alignment.center,
padding: EdgeInsets.all(40), padding: EdgeInsets.all(40),
child: Text( child: Text(
context.s.introThirdPage, context.s.introThirdPage,
style: TextStyle(fontSize: 30, color: Colors.white), style: TextStyle(fontSize: 30, color: Colors.white),
), ),
), ),
Container( Container(
height: context.width * 3 / 4, height: context.width * 3 / 4,
// color: Colors.red, // color: Colors.red,
child: FlareActor( child: FlareActor(
'assets/swipe.flr', 'assets/swipe.flr',
alignment: Alignment.center, alignment: Alignment.center,
animation: 'swipe', animation: 'swipe',
fit: BoxFit.cover, fit: BoxFit.cover,
)), )),
Spacer(), Spacer(),
], ],
), ),
), ),
); );
} }
} }

View File

@ -1,202 +1,201 @@
import 'dart:async'; import 'dart:convert';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/foundation.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../state/podcast_group.dart';
import '../state/podcast_group.dart';
const String groupsKey = 'groups';
const String groupsKey = 'groups'; const String playlistKey = 'playlist';
const String playlistKey = 'playlist'; const String autoPlayKey = 'autoPlay';
const String autoPlayKey = 'autoPlay'; const String audioPositionKey = 'audioposition';
const String audioPositionKey = 'audioposition'; const String lastWorkKey = 'lastWork';
const String lastWorkKey = 'lastWork'; const String refreshdateKey = 'refreshdate';
const String refreshdateKey = 'refreshdate'; const String themesKey = 'themes';
const String themesKey = 'themes'; const String accentsKey = 'accents';
const String accentsKey = 'accents'; const String autoUpdateKey = 'autoAdd';
const String autoUpdateKey = 'autoAdd'; const String updateIntervalKey = 'updateInterval';
const String updateIntervalKey = 'updateInterval'; const String downloadUsingDataKey = 'downloadUsingData';
const String downloadUsingDataKey = 'downloadUsingData'; const String introKey = 'intro';
const String introKey = 'intro'; const String realDarkKey = 'realDark';
const String realDarkKey = 'realDark'; const String cacheMaxKey = 'cacheMax';
const String cacheMaxKey = 'cacheMax'; const String podcastLayoutKey = 'podcastLayoutKey';
const String podcastLayoutKey = 'podcastLayoutKey'; const String recentLayoutKey = 'recentLayoutKey';
const String recentLayoutKey = 'recentLayoutKey'; const String favLayoutKey = 'favLayoutKey';
const String favLayoutKey = 'favLayoutKey'; const String downloadLayoutKey = 'downloadLayoutKey';
const String downloadLayoutKey = 'downloadLayoutKey'; const String autoDownloadNetworkKey = 'autoDownloadNetwork';
const String autoDownloadNetworkKey = 'autoDownloadNetwork'; const String episodePopupMenuKey = 'episodePopupMenuKey';
const String episodePopupMenuKey = 'episodePopupMenuKey'; const String autoDeleteKey = 'autoDeleteKey';
const String autoDeleteKey = 'autoDeleteKey'; const String autoSleepTimerKey = 'autoSleepTimerKey';
const String autoSleepTimerKey = 'autoSleepTimerKey'; const String autoSleepTimerStartKey = 'autoSleepTimerStartKey';
const String autoSleepTimerStartKey = 'autoSleepTimerStartKey'; const String autoSleepTimerEndKey = 'autoSleepTimerEndKey';
const String autoSleepTimerEndKey = 'autoSleepTimerEndKey'; const String defaultSleepTimerKey = 'defaultSleepTimerKey';
const String defaultSleepTimerKey = 'defaultSleepTimerKey'; const String autoSleepTimerModeKey = 'autoSleepTimerModeKey';
const String autoSleepTimerModeKey = 'autoSleepTimerModeKey'; const String tapToOpenPopupMenuKey = 'tapToOpenPopupMenuKey';
const String tapToOpenPopupMenuKey = 'tapToOpenPopupMenuKey'; const String fastForwardSecondsKey = 'fastForwardSecondsKey';
const String fastForwardSecondsKey = 'fastForwardSecondsKey'; const String rewindSecondsKey = 'rewindSecondsKey';
const String rewindSecondsKey = 'rewindSecondsKey'; const String playerHeightKey = 'playerHeightKey';
const String playerHeightKey = 'playerHeightKey'; const String speedKey = 'speedKey';
const String speedKey = 'speedKey'; const String skipSilenceKey = 'skipSilenceKey';
const String skipSilenceKey = 'skipSilenceKey'; const String localeKey = 'localeKey';
const String localeKey = 'localeKey'; const String boostVolumeKey = 'boostVolumeKey';
const String boostVolumeKey = 'boostVolumeKey'; const String volumeGainKey = 'volumeGainKey';
const String volumeGainKey = 'volumeGainKey'; const String hideListenedKey = 'hideListenedKey';
const String hideListenedKey = 'hideListenedKey'; const String notificationLayoutKey = 'notificationLayoutKey';
const String notificationLayoutKey = 'notificationLayoutKey'; const String showNotesFontKey = 'showNotesFontKey';
const String showNotesFontKey = 'showNotesFontKey'; const String speedListKey = 'speedListKey';
const String speedListKey = 'speedListKey'; const String searchHistoryKey = 'searchHistoryKey';
const String searchHistoryKey = 'searchHistoryKey'; const String gpodderApiKey = 'gpodderApiKey';
const String gpodderApiKey = 'gpodderApiKey'; const String gpodderAddKey = 'gpodderAddKey';
const String gpodderAddKey = 'gpodderAddKey'; const String gpodderRemoveKey = 'gpodderRemoveKey';
const String gpodderRemoveKey = 'gpodderRemoveKey'; const String gpodderSyncStatusKey = 'gpodderSyncStatusKey';
const String gpodderSyncStatusKey = 'gpodderSyncStatusKey'; const String gpodderSyncDateTimeKey = 'gpodderSyncDateTimeKey';
const String gpodderSyncDateTimeKey = 'gpodderSyncDateTimeKey'; const String gpodderRemoteAddKey = 'gpodderRemoteAddKey';
const String gpodderRemoteAddKey = 'gpodderRemoteAddKey'; const String gpodderRemoteRemoveKey = 'gpodderRemoteRemoveKey';
const String gpodderRemoteRemoveKey = 'gpodderRemoteRemoveKey'; const String hidePodcastDiscoveryKey = 'hidePodcastDiscoveryKey';
const String hidePodcastDiscoveryKey = 'hidePodcastDiscoveryKey'; const String searchEngineKey = 'searchEngineKey';
const String searchEngineKey = 'searchEngineKey'; const String markListenedAfterSkipKey = 'markListenedAfterSkipKey';
const String markListenedAfterSkipKey = 'markListenedAfterSkipKey'; const String downloadPositionKey = 'downloadPositionKey';
const String downloadPositionKey = 'downloadPositionKey'; const String deleteAfterPlayedKey = 'removeAfterPlayedKey';
const String deleteAfterPlayedKey = 'removeAfterPlayedKey';
class KeyValueStorage {
class KeyValueStorage { final String key;
final String key; KeyValueStorage(this.key);
KeyValueStorage(this.key); Future<List<GroupEntity>> getGroups() async {
Future<List<GroupEntity>> getGroups() async { var prefs = await SharedPreferences.getInstance();
var prefs = await SharedPreferences.getInstance(); if (prefs.getString(key) == null) {
if (prefs.getString(key) == null) { var home = PodcastGroup('Home');
var home = PodcastGroup('Home'); await prefs.setString(
await prefs.setString( key,
key, json.encode({
json.encode({ 'groups': [home.toEntity().toJson()]
'groups': [home.toEntity().toJson()] }));
})); }
} return json
return json .decode(prefs.getString(key))['groups']
.decode(prefs.getString(key))['groups'] .cast<Map<String, Object>>()
.cast<Map<String, Object>>() .map<GroupEntity>(GroupEntity.fromJson)
.map<GroupEntity>(GroupEntity.fromJson) .toList(growable: false);
.toList(growable: false); }
}
Future<bool> saveGroup(List<GroupEntity> groupList) async {
Future<bool> saveGroup(List<GroupEntity> groupList) async { var prefs = await SharedPreferences.getInstance();
var prefs = await SharedPreferences.getInstance(); return prefs.setString(
return prefs.setString( key,
key, json.encode(
json.encode( {'groups': groupList.map((group) => group.toJson()).toList()}));
{'groups': groupList.map((group) => group.toJson()).toList()})); }
}
Future<bool> saveInt(int setting) async {
Future<bool> saveInt(int setting) async { var prefs = await SharedPreferences.getInstance();
var prefs = await SharedPreferences.getInstance(); return prefs.setInt(key, setting);
return prefs.setInt(key, setting); }
}
Future<int> getInt({int defaultValue = 0}) async {
Future<int> getInt({int defaultValue = 0}) async { var prefs = await SharedPreferences.getInstance();
var prefs = await SharedPreferences.getInstance(); if (prefs.getInt(key) == null) await prefs.setInt(key, defaultValue);
if (prefs.getInt(key) == null) await prefs.setInt(key, defaultValue); return prefs.getInt(key);
return prefs.getInt(key); }
}
Future<bool> saveStringList(List<String> playList) async {
Future<bool> saveStringList(List<String> playList) async { var prefs = await SharedPreferences.getInstance();
var prefs = await SharedPreferences.getInstance(); return prefs.setStringList(key, playList);
return prefs.setStringList(key, playList); }
}
Future<List<String>> getStringList() async {
Future<List<String>> getStringList() async { var prefs = await SharedPreferences.getInstance();
var prefs = await SharedPreferences.getInstance(); if (prefs.getStringList(key) == null) {
if (prefs.getStringList(key) == null) { await prefs.setStringList(key, []);
await prefs.setStringList(key, []); }
} return prefs.getStringList(key);
return prefs.getStringList(key); }
}
Future<bool> saveString(String string) async {
Future<bool> saveString(String string) async { var prefs = await SharedPreferences.getInstance();
var prefs = await SharedPreferences.getInstance(); return prefs.setString(key, string);
return prefs.setString(key, string); }
}
Future<String> getString() async {
Future<String> getString() async { var prefs = await SharedPreferences.getInstance();
var prefs = await SharedPreferences.getInstance(); if (prefs.getString(key) == null) {
if (prefs.getString(key) == null) { await prefs.setString(key, '');
await prefs.setString(key, ''); }
} return prefs.getString(key);
return prefs.getString(key); }
}
Future<bool> saveMenu(List<int> list) async {
Future<bool> saveMenu(List<int> list) async { var prefs = await SharedPreferences.getInstance();
var prefs = await SharedPreferences.getInstance(); return await prefs.setStringList(
return await prefs.setStringList( key, list.map((e) => e.toString()).toList());
key, list.map((e) => e.toString()).toList()); }
}
Future<List<int>> getMenu() async {
Future<List<int>> getMenu() async { var prefs = await SharedPreferences.getInstance();
var prefs = await SharedPreferences.getInstance(); if (prefs.getStringList(key) == null || prefs.getStringList(key).isEmpty) {
if (prefs.getStringList(key) == null || prefs.getStringList(key).isEmpty) { await prefs.setStringList(key, ['0', '1', '2', '13', '14']);
await prefs.setStringList(key, ['0', '1', '2', '13', '14']); }
} var list = prefs.getStringList(key);
var list = prefs.getStringList(key); return list.map(int.parse).toList();
return list.map(int.parse).toList(); }
}
/// For player speed settings.
/// For player speed settings. Future<bool> saveSpeedList(List<double> list) async {
Future<bool> saveSpeedList(List<double> list) async { var prefs = await SharedPreferences.getInstance();
var prefs = await SharedPreferences.getInstance(); list.sort();
list.sort(); return await prefs.setStringList(
return await prefs.setStringList( key, list.map((e) => e.toStringAsFixed(1)).toList());
key, list.map((e) => e.toStringAsFixed(1)).toList()); }
}
Future<List<double>> getSpeedList() async {
Future<List<double>> getSpeedList() async { var prefs = await SharedPreferences.getInstance();
var prefs = await SharedPreferences.getInstance(); if (prefs.getStringList(key) == null || prefs.getStringList(key).isEmpty) {
if (prefs.getStringList(key) == null || prefs.getStringList(key).isEmpty) { await prefs.setStringList(
await prefs.setStringList( key, ['0.5', '0.6', '0.8', '1.0', '1.1', '1.2', '1.5', '2.0']);
key, ['0.5', '0.6', '0.8', '1.0', '1.1', '1.2', '1.5', '2.0']); }
} var list = prefs.getStringList(key);
var list = prefs.getStringList(key); return list.map(double.parse).toList();
return list.map(double.parse).toList(); }
}
/// Rreverse is used for compatite bool value save before which set true = 0, false = 1
/// Rreverse is used for compatite bool value save before which set true = 0, false = 1 Future<bool> getBool(
Future<bool> getBool( {@required bool defaultValue, bool reverse = false}) async {
{@required bool defaultValue, bool reverse = false}) async { var prefs = await SharedPreferences.getInstance();
var prefs = await SharedPreferences.getInstance(); if (prefs.getInt(key) == null) {
if (prefs.getInt(key) == null) { reverse
reverse ? await prefs.setInt(key, defaultValue ? 0 : 1)
? await prefs.setInt(key, defaultValue ? 0 : 1) : await prefs.setInt(key, defaultValue ? 1 : 0);
: await prefs.setInt(key, defaultValue ? 1 : 0); }
} var i = prefs.getInt(key);
var i = prefs.getInt(key); return reverse ? i == 0 : i == 1;
return reverse ? i == 0 : i == 1; }
}
/// Rreverse is used for compatite bool value save before which set true = 0, false = 1
/// Rreverse is used for compatite bool value save before which set true = 0, false = 1 Future<bool> saveBool(boo, {reverse = false}) async {
Future<bool> saveBool(boo, {reverse = false}) async { var prefs = await SharedPreferences.getInstance();
var prefs = await SharedPreferences.getInstance(); return reverse
return reverse ? prefs.setInt(key, boo ? 0 : 1)
? prefs.setInt(key, boo ? 0 : 1) : prefs.setInt(key, boo ? 1 : 0);
: prefs.setInt(key, boo ? 1 : 0); }
}
Future<bool> saveDouble(double data) async {
Future<bool> saveDouble(double data) async { var prefs = await SharedPreferences.getInstance();
var prefs = await SharedPreferences.getInstance(); return prefs.setDouble(key, data);
return prefs.setDouble(key, data); }
}
Future<double> getDoubel({double defaultValue = 0.0}) async {
Future<double> getDoubel({double defaultValue = 0.0}) async { var prefs = await SharedPreferences.getInstance();
var prefs = await SharedPreferences.getInstance(); if (prefs.getDouble(key) == null) {
if (prefs.getDouble(key) == null) { await prefs.setDouble(key, defaultValue);
await prefs.setDouble(key, defaultValue); }
} return prefs.getDouble(key);
return prefs.getDouble(key); }
}
Future<void> addList(List<String> addList) async {
Future<void> addList(List<String> addList) async { final list = await getStringList();
final list = await getStringList(); await saveStringList(list..addAll(addList));
await saveStringList(list..addAll(addList)); }
}
Future<void> clearList() async {
Future<void> clearList() async { await saveStringList([]);
await saveStringList([]); }
} }
}

File diff suppressed because it is too large Load Diff

View File

@ -1,146 +1,146 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class CustomTabView extends StatefulWidget { class CustomTabView extends StatefulWidget {
final int itemCount; final int itemCount;
final IndexedWidgetBuilder tabBuilder; final IndexedWidgetBuilder tabBuilder;
final IndexedWidgetBuilder pageBuilder; final IndexedWidgetBuilder pageBuilder;
final ValueChanged<int> onPositionChange; final ValueChanged<int> onPositionChange;
final ValueChanged<double> onScroll; final ValueChanged<double> onScroll;
final int initPosition; final int initPosition;
CustomTabView({ CustomTabView({
@required this.itemCount, @required this.itemCount,
@required this.tabBuilder, @required this.tabBuilder,
@required this.pageBuilder, @required this.pageBuilder,
this.onPositionChange, this.onPositionChange,
this.onScroll, this.onScroll,
this.initPosition, this.initPosition,
}); });
@override @override
_CustomTabsState createState() => _CustomTabsState(); _CustomTabsState createState() => _CustomTabsState();
} }
class _CustomTabsState extends State<CustomTabView> class _CustomTabsState extends State<CustomTabView>
with TickerProviderStateMixin { with TickerProviderStateMixin {
TabController controller; TabController controller;
int _currentCount; int _currentCount;
int _currentPosition; int _currentPosition;
@override @override
void initState() { void initState() {
_currentPosition = widget.initPosition ?? 0; _currentPosition = widget.initPosition ?? 0;
controller = TabController( controller = TabController(
length: widget.itemCount, length: widget.itemCount,
vsync: this, vsync: this,
initialIndex: _currentPosition, initialIndex: _currentPosition,
); );
controller.addListener(onPositionChange); controller.addListener(onPositionChange);
controller.animation.addListener(onScroll); controller.animation.addListener(onScroll);
_currentCount = widget.itemCount; _currentCount = widget.itemCount;
super.initState(); super.initState();
} }
@override @override
void didUpdateWidget(CustomTabView oldWidget) { void didUpdateWidget(CustomTabView oldWidget) {
if (_currentCount != widget.itemCount) { if (_currentCount != widget.itemCount) {
controller.animation.removeListener(onScroll); controller.animation.removeListener(onScroll);
controller.removeListener(onPositionChange); controller.removeListener(onPositionChange);
controller.dispose(); controller.dispose();
if (widget.initPosition != null) { if (widget.initPosition != null) {
_currentPosition = widget.initPosition; _currentPosition = widget.initPosition;
} }
if (_currentPosition > widget.itemCount - 1) { if (_currentPosition > widget.itemCount - 1) {
_currentPosition = widget.itemCount - 1; _currentPosition = widget.itemCount - 1;
_currentPosition = _currentPosition < 0 ? 0 : _currentPosition; _currentPosition = _currentPosition < 0 ? 0 : _currentPosition;
if (widget.onPositionChange is ValueChanged<int>) { if (widget.onPositionChange is ValueChanged<int>) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) { if (mounted) {
widget.onPositionChange(_currentPosition); widget.onPositionChange(_currentPosition);
} }
}); });
} }
} }
_currentCount = widget.itemCount; _currentCount = widget.itemCount;
setState(() { setState(() {
controller = TabController( controller = TabController(
length: widget.itemCount, length: widget.itemCount,
vsync: this, vsync: this,
initialIndex: _currentPosition, initialIndex: _currentPosition,
); );
controller.addListener(onPositionChange); controller.addListener(onPositionChange);
controller.animation.addListener(onScroll); controller.animation.addListener(onScroll);
}); });
} else if (widget.initPosition != null) { } else if (widget.initPosition != null) {
controller.animateTo(widget.initPosition); controller.animateTo(widget.initPosition);
} }
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
} }
@override @override
void dispose() { void dispose() {
controller.animation.removeListener(onScroll); controller.animation.removeListener(onScroll);
controller.removeListener(onPositionChange); controller.removeListener(onPositionChange);
controller.dispose(); controller.dispose();
super.dispose(); super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[ children: <Widget>[
Container( Container(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
height: 50.0, height: 50.0,
padding: EdgeInsets.all(10.0), padding: EdgeInsets.all(10.0),
child: TabBar( child: TabBar(
indicatorSize: TabBarIndicatorSize.label, indicatorSize: TabBarIndicatorSize.label,
labelPadding: EdgeInsets.symmetric(horizontal: 5.0), labelPadding: EdgeInsets.symmetric(horizontal: 5.0),
indicatorPadding: EdgeInsets.symmetric(horizontal: 5.0), indicatorPadding: EdgeInsets.symmetric(horizontal: 5.0),
isScrollable: true, isScrollable: true,
controller: controller, controller: controller,
labelColor: Colors.white, labelColor: Colors.white,
unselectedLabelColor: Colors.grey[700], unselectedLabelColor: Colors.grey[700],
indicator: BoxDecoration( indicator: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(15)), borderRadius: BorderRadius.all(Radius.circular(15)),
color: Theme.of(context).accentColor, color: Theme.of(context).accentColor,
), ),
tabs: List.generate( tabs: List.generate(
widget.itemCount, widget.itemCount,
(index) => widget.tabBuilder(context, index), (index) => widget.tabBuilder(context, index),
), ),
), ),
), ),
Expanded( Expanded(
child: TabBarView( child: TabBarView(
controller: controller, controller: controller,
children: List.generate( children: List.generate(
widget.itemCount, widget.itemCount,
(index) => widget.pageBuilder(context, index), (index) => widget.pageBuilder(context, index),
), ),
), ),
), ),
], ],
); );
} }
onPositionChange() { onPositionChange() {
if (!controller.indexIsChanging) { if (!controller.indexIsChanging) {
_currentPosition = controller.index; _currentPosition = controller.index;
if (widget.onPositionChange is ValueChanged<int>) { if (widget.onPositionChange is ValueChanged<int>) {
widget.onPositionChange(_currentPosition); widget.onPositionChange(_currentPosition);
} }
} }
} }
onScroll() { onScroll() {
if (widget.onScroll is ValueChanged<double>) { if (widget.onScroll is ValueChanged<double>) {
widget.onScroll(controller.animation.value); widget.onScroll(controller.animation.value);
} }
} }
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,470 +1,470 @@
import 'dart:developer' as developer; import 'dart:developer' as developer;
import 'dart:io'; import 'dart:io';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:image/image.dart' as img; import 'package:image/image.dart' as img;
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:webfeed/webfeed.dart'; import 'package:webfeed/webfeed.dart';
import '../local_storage/sqflite_localpodcast.dart'; import '../local_storage/sqflite_localpodcast.dart';
import '../state/podcast_group.dart'; import '../state/podcast_group.dart';
import '../type/play_histroy.dart'; import '../type/play_histroy.dart';
import '../type/podcastlocal.dart'; import '../type/podcastlocal.dart';
import '../util/custom_widget.dart'; import '../util/custom_widget.dart';
import '../util/duraiton_picker.dart'; import '../util/duraiton_picker.dart';
import '../util/extension_helper.dart'; import '../util/extension_helper.dart';
enum MarkStatus { start, complete, none } enum MarkStatus { start, complete, none }
enum RefreshCoverStatus { start, complete, error, none } enum RefreshCoverStatus { start, complete, error, none }
class PodcastSetting extends StatefulWidget { class PodcastSetting extends StatefulWidget {
const PodcastSetting({this.podcastLocal, Key key}) : super(key: key); const PodcastSetting({this.podcastLocal, Key key}) : super(key: key);
final PodcastLocal podcastLocal; final PodcastLocal podcastLocal;
@override @override
_PodcastSettingState createState() => _PodcastSettingState(); _PodcastSettingState createState() => _PodcastSettingState();
} }
class _PodcastSettingState extends State<PodcastSetting> { class _PodcastSettingState extends State<PodcastSetting> {
final _dbHelper = DBHelper(); final _dbHelper = DBHelper();
MarkStatus _markStatus = MarkStatus.none; MarkStatus _markStatus = MarkStatus.none;
RefreshCoverStatus _coverStatus = RefreshCoverStatus.none; RefreshCoverStatus _coverStatus = RefreshCoverStatus.none;
int _secondsStart; int _secondsStart;
int _secondsEnd; int _secondsEnd;
bool _markConfirm; bool _markConfirm;
bool _removeConfirm; bool _removeConfirm;
bool _showStartTimePicker; bool _showStartTimePicker;
bool _showEndTimePicker; bool _showEndTimePicker;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_secondsStart = 0; _secondsStart = 0;
_secondsEnd = 0; _secondsEnd = 0;
_markConfirm = false; _markConfirm = false;
_removeConfirm = false; _removeConfirm = false;
_showStartTimePicker = false; _showStartTimePicker = false;
_showEndTimePicker = false; _showEndTimePicker = false;
} }
Future<void> _setAutoDownload(bool boo) async { Future<void> _setAutoDownload(bool boo) async {
var permission = await _checkPermmison(); var permission = await _checkPermmison();
if (permission) { if (permission) {
await _dbHelper.saveAutoDownload(widget.podcastLocal.id, boo: boo); await _dbHelper.saveAutoDownload(widget.podcastLocal.id, boo: boo);
} }
if (mounted) setState(() {}); if (mounted) setState(() {});
} }
Future<void> _setNeverUpdate(bool boo) async { Future<void> _setNeverUpdate(bool boo) async {
await _dbHelper.saveNeverUpdate(widget.podcastLocal.id, boo: boo); await _dbHelper.saveNeverUpdate(widget.podcastLocal.id, boo: boo);
if (mounted) setState(() {}); if (mounted) setState(() {});
} }
Future<void> _saveSkipSecondsStart(int seconds) async { Future<void> _saveSkipSecondsStart(int seconds) async {
await _dbHelper.saveSkipSecondsStart(widget.podcastLocal.id, seconds); await _dbHelper.saveSkipSecondsStart(widget.podcastLocal.id, seconds);
} }
Future<void> _saveSkipSecondsEnd(int seconds) async { Future<void> _saveSkipSecondsEnd(int seconds) async {
await _dbHelper.saveSkipSecondsEnd(widget.podcastLocal.id, seconds); await _dbHelper.saveSkipSecondsEnd(widget.podcastLocal.id, seconds);
} }
Future<bool> _getAutoDownload(String id) async { Future<bool> _getAutoDownload(String id) async {
return await _dbHelper.getAutoDownload(id); return await _dbHelper.getAutoDownload(id);
} }
Future<bool> _getNeverUpdate(String id) async { Future<bool> _getNeverUpdate(String id) async {
return await _dbHelper.getNeverUpdate(id); return await _dbHelper.getNeverUpdate(id);
} }
Future<int> _getSkipSecondStart(String id) async { Future<int> _getSkipSecondStart(String id) async {
return await _dbHelper.getSkipSecondsStart(id); return await _dbHelper.getSkipSecondsStart(id);
} }
Future<int> _getSkipSecondEnd(String id) async { Future<int> _getSkipSecondEnd(String id) async {
return await _dbHelper.getSkipSecondsEnd(id); return await _dbHelper.getSkipSecondsEnd(id);
} }
Future<void> _markListened(String podcastId) async { Future<void> _markListened(String podcastId) async {
setState(() { setState(() {
_markStatus = MarkStatus.start; _markStatus = MarkStatus.start;
}); });
final episodes = await _dbHelper.getRssItem(podcastId, -1, final episodes = await _dbHelper.getRssItem(podcastId, -1,
reverse: true, hideListened: true); reverse: true, hideListened: true);
for (var episode in episodes) { for (var episode in episodes) {
final history = PlayHistory(episode.title, episode.enclosureUrl, 0, 1); final history = PlayHistory(episode.title, episode.enclosureUrl, 0, 1);
await _dbHelper.saveHistory(history); await _dbHelper.saveHistory(history);
} }
if (mounted) { if (mounted) {
setState(() { setState(() {
_markStatus = MarkStatus.complete; _markStatus = MarkStatus.complete;
}); });
} }
} }
Future<void> _refreshArtWork() async { Future<void> _refreshArtWork() async {
setState(() => _coverStatus = RefreshCoverStatus.start); setState(() => _coverStatus = RefreshCoverStatus.start);
var options = BaseOptions( var options = BaseOptions(
connectTimeout: 30000, connectTimeout: 30000,
receiveTimeout: 90000, receiveTimeout: 90000,
); );
var dio = Dio(options); var dio = Dio(options);
String imageUrl; String imageUrl;
try { try {
var response = await dio.get(widget.podcastLocal.rssUrl); var response = await dio.get(widget.podcastLocal.rssUrl);
try { try {
var p = RssFeed.parse(response.data); var p = RssFeed.parse(response.data);
imageUrl = p.itunes.image.href ?? p.image.url; imageUrl = p.itunes.image.href ?? p.image.url;
} catch (e) { } catch (e) {
developer.log(e.toString()); developer.log(e.toString());
if (mounted) setState(() => _coverStatus = RefreshCoverStatus.error); if (mounted) setState(() => _coverStatus = RefreshCoverStatus.error);
} }
} catch (e) { } catch (e) {
developer.log(e.toString()); developer.log(e.toString());
if (mounted) setState(() => _coverStatus = RefreshCoverStatus.error); if (mounted) setState(() => _coverStatus = RefreshCoverStatus.error);
} }
if (imageUrl != null && if (imageUrl != null &&
imageUrl.contains('http') && imageUrl.contains('http') &&
(imageUrl != widget.podcastLocal.imageUrl || (imageUrl != widget.podcastLocal.imageUrl ||
!File(widget.podcastLocal.imageUrl).existsSync())) { !File(widget.podcastLocal.imageUrl).existsSync())) {
try { try {
img.Image thumbnail; img.Image thumbnail;
var imageResponse = await dio.get<List<int>>(imageUrl, var imageResponse = await dio.get<List<int>>(imageUrl,
options: Options( options: Options(
responseType: ResponseType.bytes, responseType: ResponseType.bytes,
)); ));
var image = img.decodeImage(imageResponse.data); var image = img.decodeImage(imageResponse.data);
thumbnail = img.copyResize(image, width: 300); thumbnail = img.copyResize(image, width: 300);
if (thumbnail != null) { if (thumbnail != null) {
var dir = await getApplicationDocumentsDirectory(); var dir = await getApplicationDocumentsDirectory();
File("${dir.path}/${widget.podcastLocal.id}.png") File("${dir.path}/${widget.podcastLocal.id}.png")
..writeAsBytesSync(img.encodePng(thumbnail)); ..writeAsBytesSync(img.encodePng(thumbnail));
if (mounted) { if (mounted) {
setState(() => _coverStatus = RefreshCoverStatus.complete); setState(() => _coverStatus = RefreshCoverStatus.complete);
} }
} }
} catch (e) { } catch (e) {
developer.log(e.toString()); developer.log(e.toString());
if (mounted) setState(() => _coverStatus = RefreshCoverStatus.error); if (mounted) setState(() => _coverStatus = RefreshCoverStatus.error);
} }
} else if (_coverStatus == RefreshCoverStatus.start && mounted) { } else if (_coverStatus == RefreshCoverStatus.start && mounted) {
setState(() => _coverStatus = RefreshCoverStatus.complete); setState(() => _coverStatus = RefreshCoverStatus.complete);
} }
} }
Future<bool> _checkPermmison() async { Future<bool> _checkPermmison() async {
var permission = await Permission.storage.status; var permission = await Permission.storage.status;
if (permission != PermissionStatus.granted) { if (permission != PermissionStatus.granted) {
var permissions = await [Permission.storage].request(); var permissions = await [Permission.storage].request();
if (permissions[Permission.storage] == PermissionStatus.granted) { if (permissions[Permission.storage] == PermissionStatus.granted) {
return true; return true;
} else { } else {
return false; return false;
} }
} else { } else {
return true; return true;
} }
} }
Widget _getRefreshStatusIcon(RefreshCoverStatus status) { Widget _getRefreshStatusIcon(RefreshCoverStatus status) {
switch (status) { switch (status) {
case RefreshCoverStatus.none: case RefreshCoverStatus.none:
return Center(); return Center();
break; break;
case RefreshCoverStatus.start: case RefreshCoverStatus.start:
return CircularProgressIndicator(strokeWidth: 2); return CircularProgressIndicator(strokeWidth: 2);
break; break;
case RefreshCoverStatus.complete: case RefreshCoverStatus.complete:
return Icon(Icons.done); return Icon(Icons.done);
break; break;
case RefreshCoverStatus.error: case RefreshCoverStatus.error:
return Icon(Icons.refresh, color: Colors.red); return Icon(Icons.refresh, color: Colors.red);
break; break;
default: default:
return Center(); return Center();
} }
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final s = context.s; final s = context.s;
final groupList = context.watch<GroupList>(); final groupList = context.watch<GroupList>();
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
FutureBuilder<bool>( FutureBuilder<bool>(
future: _getAutoDownload(widget.podcastLocal.id), future: _getAutoDownload(widget.podcastLocal.id),
initialData: false, initialData: false,
builder: (context, snapshot) { builder: (context, snapshot) {
return ListTile( return ListTile(
onTap: () => _setAutoDownload(!snapshot.data), onTap: () => _setAutoDownload(!snapshot.data),
leading: SizedBox( leading: SizedBox(
height: 22, height: 22,
width: 24, width: 24,
child: CustomPaint( child: CustomPaint(
painter: DownloadPainter( painter: DownloadPainter(
color: context.brightness == Brightness.light color: context.brightness == Brightness.light
? Colors.grey[600] ? Colors.grey[600]
: Colors.white, : Colors.white,
fraction: 0, fraction: 0,
progressColor: context.accentColor, progressColor: context.accentColor,
), ),
), ),
), ),
title: Text(s.autoDownload), title: Text(s.autoDownload),
trailing: Transform.scale( trailing: Transform.scale(
scale: 0.9, scale: 0.9,
child: child:
Switch(value: snapshot.data, onChanged: _setAutoDownload), Switch(value: snapshot.data, onChanged: _setAutoDownload),
), ),
); );
}), }),
FutureBuilder<bool>( FutureBuilder<bool>(
future: _getNeverUpdate(widget.podcastLocal.id), future: _getNeverUpdate(widget.podcastLocal.id),
initialData: false, initialData: false,
builder: (context, snapshot) { builder: (context, snapshot) {
return ListTile( return ListTile(
onTap: () => _setNeverUpdate(!snapshot.data), onTap: () => _setNeverUpdate(!snapshot.data),
leading: Icon(Icons.lock), leading: Icon(Icons.lock),
title: Text(s.neverAutoUpdate), title: Text(s.neverAutoUpdate),
trailing: Transform.scale( trailing: Transform.scale(
scale: 0.9, scale: 0.9,
child: child:
Switch(value: snapshot.data, onChanged: _setNeverUpdate), Switch(value: snapshot.data, onChanged: _setNeverUpdate),
), ),
); );
}), }),
FutureBuilder<int>( FutureBuilder<int>(
future: _getSkipSecondStart(widget.podcastLocal.id), future: _getSkipSecondStart(widget.podcastLocal.id),
initialData: 0, initialData: 0,
builder: (context, snapshot) => ListTile( builder: (context, snapshot) => ListTile(
onTap: () { onTap: () {
_secondsStart = 0; _secondsStart = 0;
setState(() { setState(() {
_removeConfirm = false; _removeConfirm = false;
_markConfirm = false; _markConfirm = false;
_showEndTimePicker = false; _showEndTimePicker = false;
_showStartTimePicker = !_showStartTimePicker; _showStartTimePicker = !_showStartTimePicker;
}); });
}, },
leading: Icon(Icons.fast_forward), leading: Icon(Icons.fast_forward),
title: Text(s.skipSecondsAtStart), title: Text(s.skipSecondsAtStart),
trailing: Padding( trailing: Padding(
padding: const EdgeInsets.only(right: 10.0), padding: const EdgeInsets.only(right: 10.0),
child: Text(snapshot.data.toTime), child: Text(snapshot.data.toTime),
), ),
), ),
), ),
if (_showStartTimePicker) if (_showStartTimePicker)
_TimePicker( _TimePicker(
onCancel: () { onCancel: () {
_secondsStart = 0; _secondsStart = 0;
setState(() => _showStartTimePicker = false); setState(() => _showStartTimePicker = false);
}, },
onConfirm: () async { onConfirm: () async {
await _saveSkipSecondsStart(_secondsStart); await _saveSkipSecondsStart(_secondsStart);
if (mounted) setState(() => _showStartTimePicker = false); if (mounted) setState(() => _showStartTimePicker = false);
}, },
onChange: (value) => _secondsStart = value.inSeconds), onChange: (value) => _secondsStart = value.inSeconds),
// FutureBuilder<int>( // FutureBuilder<int>(
// future: _getSkipSecondEnd(widget.podcastLocal.id), // future: _getSkipSecondEnd(widget.podcastLocal.id),
// initialData: 0, // initialData: 0,
// builder: (context, snapshot) => ListTile( // builder: (context, snapshot) => ListTile(
// onTap: () { // onTap: () {
// _secondsEnd = 0; // _secondsEnd = 0;
// setState(() { // setState(() {
// _removeConfirm = false; // _removeConfirm = false;
// _markConfirm = false; // _markConfirm = false;
// _showStartTimePicker = false; // _showStartTimePicker = false;
// _showEndTimePicker = !_showEndTimePicker; // _showEndTimePicker = !_showEndTimePicker;
// }); // });
// }, // },
// leading: Icon(Icons.fast_rewind), // leading: Icon(Icons.fast_rewind),
// title: Text(s.skipSecondsAtEnd), // title: Text(s.skipSecondsAtEnd),
// trailing: Padding( // trailing: Padding(
// padding: const EdgeInsets.only(right: 10.0), // padding: const EdgeInsets.only(right: 10.0),
// child: Text(snapshot.data.toTime), // child: Text(snapshot.data.toTime),
// ), // ),
// ), // ),
// ), // ),
// if (_showEndTimePicker) // if (_showEndTimePicker)
// _TimePicker( // _TimePicker(
// onCancel: () { // onCancel: () {
// _secondsEnd = 0; // _secondsEnd = 0;
// setState(() => _showEndTimePicker = false); // setState(() => _showEndTimePicker = false);
// }, // },
// onConfirm: () async { // onConfirm: () async {
// await _saveSkipSecondsEnd(_secondsEnd); // await _saveSkipSecondsEnd(_secondsEnd);
// setState(() => _showEndTimePicker = false); // setState(() => _showEndTimePicker = false);
// }, // },
// onChange: (value) => _secondsEnd = value.inSeconds, // onChange: (value) => _secondsEnd = value.inSeconds,
// ), // ),
ListTile( ListTile(
onTap: () { onTap: () {
if (_coverStatus != RefreshCoverStatus.start) { if (_coverStatus != RefreshCoverStatus.start) {
_refreshArtWork(); _refreshArtWork();
} }
}, },
title: Text(s.refreshArtwork), title: Text(s.refreshArtwork),
leading: Icon(Icons.refresh), leading: Icon(Icons.refresh),
trailing: Padding( trailing: Padding(
padding: const EdgeInsets.only(right: 15.0), padding: const EdgeInsets.only(right: 15.0),
child: SizedBox( child: SizedBox(
height: 20, height: 20,
width: 20, width: 20,
child: _getRefreshStatusIcon(_coverStatus)))), child: _getRefreshStatusIcon(_coverStatus)))),
Divider(height: 1), Divider(height: 1),
ListTile( ListTile(
onTap: () { onTap: () {
setState(() { setState(() {
_removeConfirm = false; _removeConfirm = false;
_showStartTimePicker = false; _showStartTimePicker = false;
_showEndTimePicker = false; _showEndTimePicker = false;
_markConfirm = !_markConfirm; _markConfirm = !_markConfirm;
}); });
}, },
title: Text(s.menuMarkAllListened, title: Text(s.menuMarkAllListened,
style: TextStyle( style: TextStyle(
color: context.accentColor, fontWeight: FontWeight.bold)), color: context.accentColor, fontWeight: FontWeight.bold)),
leading: SizedBox( leading: SizedBox(
height: 22, height: 22,
width: 24, width: 24,
child: CustomPaint( child: CustomPaint(
painter: ListenedAllPainter( painter: ListenedAllPainter(
context.brightness == Brightness.light context.brightness == Brightness.light
? Colors.grey[600] ? Colors.grey[600]
: Colors.white, : Colors.white,
stroke: 2), stroke: 2),
), ),
), ),
trailing: Padding( trailing: Padding(
padding: const EdgeInsets.only(right: 10.0), padding: const EdgeInsets.only(right: 10.0),
child: SizedBox( child: SizedBox(
height: 20, height: 20,
width: 20, width: 20,
child: _markStatus == MarkStatus.none child: _markStatus == MarkStatus.none
? Center() ? Center()
: _markStatus == MarkStatus.start : _markStatus == MarkStatus.start
? CircularProgressIndicator(strokeWidth: 2) ? CircularProgressIndicator(strokeWidth: 2)
: Icon(Icons.done)), : Icon(Icons.done)),
)), )),
if (_markConfirm) if (_markConfirm)
Container( Container(
width: double.infinity, width: double.infinity,
color: context.primaryColorDark, color: context.primaryColorDark,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
FlatButton( FlatButton(
onPressed: () => setState(() { onPressed: () => setState(() {
_markConfirm = false; _markConfirm = false;
}), }),
child: Text( child: Text(
s.cancel, s.cancel,
style: TextStyle(color: Colors.grey[600]), style: TextStyle(color: Colors.grey[600]),
)), )),
FlatButton( FlatButton(
onPressed: () { onPressed: () {
if (_markStatus != MarkStatus.start) { if (_markStatus != MarkStatus.start) {
_markListened(widget.podcastLocal.id); _markListened(widget.podcastLocal.id);
} }
setState(() { setState(() {
_markConfirm = false; _markConfirm = false;
}); });
}, },
child: Text(s.confirm, child: Text(s.confirm,
style: TextStyle(color: context.accentColor))), style: TextStyle(color: context.accentColor))),
], ],
), ),
), ),
ListTile( ListTile(
onTap: () { onTap: () {
setState(() { setState(() {
_markConfirm = false; _markConfirm = false;
_showStartTimePicker = false; _showStartTimePicker = false;
_showEndTimePicker = false; _showEndTimePicker = false;
_removeConfirm = !_removeConfirm; _removeConfirm = !_removeConfirm;
}); });
}, },
title: Text(s.remove, title: Text(s.remove,
style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold)), style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold)),
leading: Icon(Icons.delete, color: Colors.red), leading: Icon(Icons.delete, color: Colors.red),
), ),
if (_removeConfirm) if (_removeConfirm)
Container( Container(
width: double.infinity, width: double.infinity,
color: context.primaryColorDark, color: context.primaryColorDark,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
FlatButton( FlatButton(
onPressed: () => setState(() { onPressed: () => setState(() {
_removeConfirm = false; _removeConfirm = false;
}), }),
child: child:
Text(s.cancel, style: TextStyle(color: Colors.grey[600])), Text(s.cancel, style: TextStyle(color: Colors.grey[600])),
), ),
FlatButton( FlatButton(
splashColor: Colors.red.withAlpha(70), splashColor: Colors.red.withAlpha(70),
onPressed: () async { onPressed: () async {
await groupList.removePodcast(widget.podcastLocal); await groupList.removePodcast(widget.podcastLocal);
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: child:
Text(s.confirm, style: TextStyle(color: Colors.red))), Text(s.confirm, style: TextStyle(color: Colors.red))),
], ],
), ),
), ),
], ],
); );
} }
} }
class _TimePicker extends StatelessWidget { class _TimePicker extends StatelessWidget {
const _TimePicker({this.onConfirm, this.onCancel, this.onChange, Key key}) const _TimePicker({this.onConfirm, this.onCancel, this.onChange, Key key})
: super(key: key); : super(key: key);
final VoidCallback onConfirm; final VoidCallback onConfirm;
final VoidCallback onCancel; final VoidCallback onCancel;
final ValueChanged<Duration> onChange; final ValueChanged<Duration> onChange;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final s = context.s; final s = context.s;
return Container( return Container(
color: context.primaryColorDark, color: context.primaryColorDark,
child: Column( child: Column(
children: [ children: [
SizedBox(height: 10), SizedBox(height: 10),
DurationPicker( DurationPicker(
onChange: onChange, onChange: onChange,
), ),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
FlatButton( FlatButton(
onPressed: onCancel, onPressed: onCancel,
child: Text( child: Text(
s.cancel, s.cancel,
style: TextStyle(color: Colors.grey[600]), style: TextStyle(color: Colors.grey[600]),
), ),
), ),
FlatButton( FlatButton(
splashColor: context.accentColor.withAlpha(70), splashColor: context.accentColor.withAlpha(70),
onPressed: onConfirm, onPressed: onConfirm,
child: Text( child: Text(
s.confirm, s.confirm,
style: TextStyle(color: context.accentColor), style: TextStyle(color: context.accentColor),
), ),
) )
], ],
) )
], ],
), ),
); );
} }
} }

View File

@ -1,213 +1,213 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_html/flutter_html.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../local_storage/sqflite_localpodcast.dart'; import '../local_storage/sqflite_localpodcast.dart';
import '../state/podcast_group.dart'; import '../state/podcast_group.dart';
import '../type/podcastlocal.dart'; import '../type/podcastlocal.dart';
import '../util/custom_widget.dart'; import '../util/custom_widget.dart';
import '../util/extension_helper.dart'; import '../util/extension_helper.dart';
import '../util/general_dialog.dart'; import '../util/general_dialog.dart';
import '../util/pageroute.dart'; import '../util/pageroute.dart';
import 'podcast_detail.dart'; import 'podcast_detail.dart';
import 'podcast_settings.dart'; import 'podcast_settings.dart';
class AboutPodcast extends StatefulWidget { class AboutPodcast extends StatefulWidget {
final PodcastLocal podcastLocal; final PodcastLocal podcastLocal;
AboutPodcast({this.podcastLocal, Key key}) : super(key: key); AboutPodcast({this.podcastLocal, Key key}) : super(key: key);
@override @override
_AboutPodcastState createState() => _AboutPodcastState(); _AboutPodcastState createState() => _AboutPodcastState();
} }
class _AboutPodcastState extends State<AboutPodcast> { class _AboutPodcastState extends State<AboutPodcast> {
String _description; String _description;
bool _load; bool _load;
void getDescription(String id) async { void getDescription(String id) async {
var dbHelper = DBHelper(); var dbHelper = DBHelper();
var description = await dbHelper.getFeedDescription(id); var description = await dbHelper.getFeedDescription(id);
_description = description; _description = description;
setState(() { setState(() {
_load = true; _load = true;
}); });
} }
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_load = false; _load = false;
getDescription(widget.podcastLocal.id); getDescription(widget.podcastLocal.id);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var _groupList = Provider.of<GroupList>(context, listen: false); var _groupList = Provider.of<GroupList>(context, listen: false);
final s = context.s; final s = context.s;
return AlertDialog( return AlertDialog(
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0))), borderRadius: BorderRadius.all(Radius.circular(10.0))),
titlePadding: EdgeInsets.only( titlePadding: EdgeInsets.only(
top: 20, left: 20, right: context.width / 3, bottom: 20), top: 20, left: 20, right: context.width / 3, bottom: 20),
actions: <Widget>[ actions: <Widget>[
FlatButton( FlatButton(
splashColor: context.accentColor.withAlpha(70), splashColor: context.accentColor.withAlpha(70),
padding: EdgeInsets.all(10.0), padding: EdgeInsets.all(10.0),
onPressed: () { onPressed: () {
_groupList.removePodcast(widget.podcastLocal); _groupList.removePodcast(widget.podcastLocal);
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
textColor: Colors.red, textColor: Colors.red,
child: Text( child: Text(
s.remove, s.remove,
), ),
), ),
], ],
title: Text(widget.podcastLocal.title), title: Text(widget.podcastLocal.title),
content: SingleChildScrollView( content: SingleChildScrollView(
scrollDirection: Axis.vertical, scrollDirection: Axis.vertical,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
!_load !_load
? Center() ? Center()
: _description != null : _description != null
? Html(data: _description) ? Html(data: _description)
: Center(), : Center(),
if (widget.podcastLocal.author != null) if (widget.podcastLocal.author != null)
Text(widget.podcastLocal.author, Text(widget.podcastLocal.author,
style: TextStyle(color: Colors.blue)) style: TextStyle(color: Colors.blue))
], ],
), ),
), ),
); );
} }
} }
class PodcastList extends StatefulWidget { class PodcastList extends StatefulWidget {
@override @override
_PodcastListState createState() => _PodcastListState(); _PodcastListState createState() => _PodcastListState();
} }
class _PodcastListState extends State<PodcastList> { class _PodcastListState extends State<PodcastList> {
Future<List<PodcastLocal>> _getPodcastLocal() async { Future<List<PodcastLocal>> _getPodcastLocal() async {
var dbHelper = DBHelper(); var dbHelper = DBHelper();
var podcastList = await dbHelper.getPodcastLocalAll(); var podcastList = await dbHelper.getPodcastLocalAll();
return podcastList; return podcastList;
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final width = context.width; final width = context.width;
return AnnotatedRegion<SystemUiOverlayStyle>( return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle( value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness, statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: context.primaryColor, systemNavigationBarColor: context.primaryColor,
systemNavigationBarIconBrightness: systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness, Theme.of(context).accentColorBrightness,
), ),
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(context.s.podcast(2)), title: Text(context.s.podcast(2)),
leading: CustomBackButton(), leading: CustomBackButton(),
centerTitle: true, centerTitle: true,
), ),
body: SafeArea( body: SafeArea(
child: Container( child: Container(
color: context.primaryColor, color: context.primaryColor,
child: FutureBuilder<List<PodcastLocal>>( child: FutureBuilder<List<PodcastLocal>>(
future: _getPodcastLocal(), future: _getPodcastLocal(),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData) { if (snapshot.hasData) {
return CustomScrollView( return CustomScrollView(
slivers: <Widget>[ slivers: <Widget>[
SliverPadding( SliverPadding(
padding: const EdgeInsets.all(10.0), padding: const EdgeInsets.all(10.0),
sliver: SliverGrid( sliver: SliverGrid(
gridDelegate: gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount( SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 0.8, childAspectRatio: 0.8,
crossAxisCount: 3, crossAxisCount: 3,
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
return InkWell( return InkWell(
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
ScaleRoute( ScaleRoute(
page: PodcastDetail( page: PodcastDetail(
podcastLocal: snapshot.data[index], podcastLocal: snapshot.data[index],
)), )),
); );
}, },
onLongPress: () async { onLongPress: () async {
generalSheet( generalSheet(
context, context,
title: snapshot.data[index].title, title: snapshot.data[index].title,
child: PodcastSetting( child: PodcastSetting(
podcastLocal: snapshot.data[index]), podcastLocal: snapshot.data[index]),
).then((value) { ).then((value) {
if (mounted) setState(() {}); if (mounted) setState(() {});
}); });
}, },
child: Align( child: Align(
alignment: Alignment.center, alignment: Alignment.center,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[ children: <Widget>[
SizedBox( SizedBox(
height: 10.0, height: 10.0,
), ),
ClipRRect( ClipRRect(
borderRadius: borderRadius:
BorderRadius.circular(width / 8), BorderRadius.circular(width / 8),
child: Container( child: Container(
height: width / 4, height: width / 4,
width: width / 4, width: width / 4,
child: Image.file(File( child: Image.file(File(
"${snapshot.data[index].imagePath}")), "${snapshot.data[index].imagePath}")),
), ),
), ),
Padding( Padding(
padding: const EdgeInsets.all(4.0), padding: const EdgeInsets.all(4.0),
child: Text( child: Text(
snapshot.data[index].title, snapshot.data[index].title,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: context.textTheme.bodyText1, style: context.textTheme.bodyText1,
maxLines: 2, maxLines: 2,
), ),
), ),
], ],
), ),
), ),
); );
}, },
childCount: snapshot.data.length, childCount: snapshot.data.length,
), ),
), ),
), ),
], ],
); );
} }
return Center( return Center(
child: SizedBox( child: SizedBox(
height: 20, height: 20,
width: 20, width: 20,
child: CircularProgressIndicator()), child: CircularProgressIndicator()),
); );
}, },
), ),
), ),
), ),
), ),
); );
} }
} }

View File

@ -1,238 +1,238 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:developer' as developer; import 'dart:developer' as developer;
import 'package:cookie_jar/cookie_jar.dart'; import 'package:cookie_jar/cookie_jar.dart';
import 'package:device_info/device_info.dart'; import 'package:device_info/device_info.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:dio_cookie_manager/dio_cookie_manager.dart'; import 'package:dio_cookie_manager/dio_cookie_manager.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import '../local_storage/key_value_storage.dart'; import '../local_storage/key_value_storage.dart';
import '../local_storage/sqflite_localpodcast.dart'; import '../local_storage/sqflite_localpodcast.dart';
enum GpodderSyncStatus { none, success, fail, authError } enum GpodderSyncStatus { none, success, fail, authError }
class Gpodder { class Gpodder {
final _dio = Dio(BaseOptions( final _dio = Dio(BaseOptions(
connectTimeout: 30000, connectTimeout: 30000,
receiveTimeout: 90000, receiveTimeout: 90000,
sendTimeout: 90000, sendTimeout: 90000,
)); ));
final _storage = KeyValueStorage(gpodderApiKey); final _storage = KeyValueStorage(gpodderApiKey);
final _addStorage = KeyValueStorage(gpodderAddKey); final _addStorage = KeyValueStorage(gpodderAddKey);
final _removeStorage = KeyValueStorage(gpodderRemoveKey); final _removeStorage = KeyValueStorage(gpodderRemoveKey);
final _remoteAddStorage = KeyValueStorage(gpodderRemoteAddKey); final _remoteAddStorage = KeyValueStorage(gpodderRemoteAddKey);
final _remoteRemoveStorage = KeyValueStorage(gpodderRemoteRemoveKey); final _remoteRemoveStorage = KeyValueStorage(gpodderRemoteRemoveKey);
final _dateTimeStorage = KeyValueStorage(gpodderSyncDateTimeKey); final _dateTimeStorage = KeyValueStorage(gpodderSyncDateTimeKey);
final _statusStorage = KeyValueStorage(gpodderSyncStatusKey); final _statusStorage = KeyValueStorage(gpodderSyncStatusKey);
final _baseUrl = "https://gpodder.net"; final _baseUrl = "https://gpodder.net";
Future<void> _initDio() async { Future<void> _initDio() async {
final dir = await getApplicationDocumentsDirectory(); final dir = await getApplicationDocumentsDirectory();
var cookieJar = PersistCookieJar(dir: "${dir.path}/.cookies/"); var cookieJar = PersistCookieJar(dir: "${dir.path}/.cookies/");
_dio.interceptors.add(CookieManager(cookieJar)); _dio.interceptors.add(CookieManager(cookieJar));
} }
Future<int> login({String username, String password}) async { Future<int> login({String username, String password}) async {
final dir = await getApplicationDocumentsDirectory(); final dir = await getApplicationDocumentsDirectory();
var cookieJar = PersistCookieJar(dir: "${dir.path}/.cookies/"); var cookieJar = PersistCookieJar(dir: "${dir.path}/.cookies/");
cookieJar.delete(Uri.parse(_baseUrl)); cookieJar.delete(Uri.parse(_baseUrl));
_dio.interceptors.add(CookieManager(cookieJar)); _dio.interceptors.add(CookieManager(cookieJar));
final basicAuth = final basicAuth =
'Basic ${base64Encode(utf8.encode('$username:$password'))}'; 'Basic ${base64Encode(utf8.encode('$username:$password'))}';
var status; var status;
Response response; Response response;
try { try {
response = await _dio.post('$_baseUrl/api/2/auth/$username/login.json', response = await _dio.post('$_baseUrl/api/2/auth/$username/login.json',
options: options:
Options(headers: <String, String>{'authorization': basicAuth})); Options(headers: <String, String>{'authorization': basicAuth}));
status = response.statusCode; status = response.statusCode;
} catch (e) { } catch (e) {
developer.log(e.toString(), name: 'gpoderr login error'); developer.log(e.toString(), name: 'gpoderr login error');
return 0; return 0;
} }
return status; return status;
} }
Future<int> logout() async { Future<int> logout() async {
final loginInfo = await _storage.getStringList(); final loginInfo = await _storage.getStringList();
final username = loginInfo[0]; final username = loginInfo[0];
await _initDio(); await _initDio();
var status; var status;
try { try {
var response = await _dio.post( var response = await _dio.post(
'$_baseUrl/api/2/auth/$username/logout.json', '$_baseUrl/api/2/auth/$username/logout.json',
); );
status = response.statusCode; status = response.statusCode;
} catch (e) { } catch (e) {
developer.log(e.toString(), name: 'gpoderr logout error'); developer.log(e.toString(), name: 'gpoderr logout error');
if (status == 400) { if (status == 400) {
await _initService(); await _initService();
} }
return 0; return 0;
} }
if (status == 200) { if (status == 200) {
await _initService(); await _initService();
} }
return status; return status;
} }
Future<void> _initService() async { Future<void> _initService() async {
final dir = await getApplicationDocumentsDirectory(); final dir = await getApplicationDocumentsDirectory();
var cookieJar = PersistCookieJar(dir: "${dir.path}/.cookies/"); var cookieJar = PersistCookieJar(dir: "${dir.path}/.cookies/");
cookieJar.delete(Uri.parse(_baseUrl)); cookieJar.delete(Uri.parse(_baseUrl));
await _storage.clearList(); await _storage.clearList();
await _addStorage.clearList(); await _addStorage.clearList();
await _remoteAddStorage.clearList(); await _remoteAddStorage.clearList();
await _removeStorage.clearList(); await _removeStorage.clearList();
await _remoteAddStorage.clearList(); await _remoteAddStorage.clearList();
await _statusStorage.saveInt(0); await _statusStorage.saveInt(0);
await _dateTimeStorage.saveInt(0); await _dateTimeStorage.saveInt(0);
} }
Future<int> checkLogin(String username) async { Future<int> checkLogin(String username) async {
await _initDio(); await _initDio();
var response = await _dio.post( var response = await _dio.post(
'$_baseUrl/api/2/auth/$username/login.json', '$_baseUrl/api/2/auth/$username/login.json',
); );
final status = response.statusCode; final status = response.statusCode;
return status; return status;
} }
Future<int> updateDevice(String username) async { Future<int> updateDevice(String username) async {
await _initDio(); await _initDio();
final deviceId = Uuid().v1(); final deviceId = Uuid().v1();
final androidInfo = await DeviceInfoPlugin().androidInfo; final androidInfo = await DeviceInfoPlugin().androidInfo;
var status = 0; var status = 0;
try { try {
var response = await _dio var response = await _dio
.post("$_baseUrl/api/2/devices/$username/$deviceId.json", data: { .post("$_baseUrl/api/2/devices/$username/$deviceId.json", data: {
"caption": "Tsacdop on ${androidInfo.model}", "caption": "Tsacdop on ${androidInfo.model}",
"type": "mobile" "type": "mobile"
}); });
status = response.statusCode; status = response.statusCode;
} catch (e) { } catch (e) {
developer.log(e.toString(), name: 'gpodder update device error'); developer.log(e.toString(), name: 'gpodder update device error');
return 0; return 0;
} }
if (status == 200) { if (status == 200) {
await _storage.saveStringList([username, deviceId]); await _storage.saveStringList([username, deviceId]);
} }
return status; return status;
} }
Future<String> getAllPodcast() async { Future<String> getAllPodcast() async {
final loginInfo = await _storage.getStringList(); final loginInfo = await _storage.getStringList();
final username = loginInfo[0]; final username = loginInfo[0];
Response response; Response response;
await _initDio(); await _initDio();
try { try {
response = await _dio.get( response = await _dio.get(
'$_baseUrl/subscriptions/$username.opml', '$_baseUrl/subscriptions/$username.opml',
); );
} catch (e) { } catch (e) {
developer.log(e.toString(), name: 'gpodder update podcasts error'); developer.log(e.toString(), name: 'gpodder update podcasts error');
return ''; return '';
} }
return response.data; return response.data;
} }
Future<int> uploadSubscriptions() async { Future<int> uploadSubscriptions() async {
final syncDataTime = DateTime.now().millisecondsSinceEpoch; final syncDataTime = DateTime.now().millisecondsSinceEpoch;
await _dateTimeStorage.saveInt(syncDataTime); await _dateTimeStorage.saveInt(syncDataTime);
final loginInfo = await _storage.getStringList(); final loginInfo = await _storage.getStringList();
final username = loginInfo[0]; final username = loginInfo[0];
final deviceId = loginInfo[1]; final deviceId = loginInfo[1];
await _initDio(); await _initDio();
final dbHelper = DBHelper(); final dbHelper = DBHelper();
final podcasts = await dbHelper.getPodcastLocalAll(); final podcasts = await dbHelper.getPodcastLocalAll();
var subscriptions = ''; var subscriptions = '';
for (var podcast in podcasts) { for (var podcast in podcasts) {
subscriptions += '${podcast.rssUrl}\n'; subscriptions += '${podcast.rssUrl}\n';
} }
var status; var status;
try { try {
final response = await _dio.put( final response = await _dio.put(
'$_baseUrl/subscriptions/$username/$deviceId.txt', '$_baseUrl/subscriptions/$username/$deviceId.txt',
data: subscriptions); data: subscriptions);
status = response.statusCode; status = response.statusCode;
} catch (e) { } catch (e) {
developer.log(e.toString(), name: 'gpodder update podcasts error'); developer.log(e.toString(), name: 'gpodder update podcasts error');
return 0; return 0;
} }
return status; return status;
} }
Future<int> getChanges() async { Future<int> getChanges() async {
final loginInfo = await _storage.getStringList(); final loginInfo = await _storage.getStringList();
final username = loginInfo[0]; final username = loginInfo[0];
final deviceId = loginInfo[1]; final deviceId = loginInfo[1];
final syncDataTime = DateTime.now().millisecondsSinceEpoch; final syncDataTime = DateTime.now().millisecondsSinceEpoch;
await _dateTimeStorage.saveInt(syncDataTime); await _dateTimeStorage.saveInt(syncDataTime);
final timeStamp = loginInfo.length == 3 ? int.parse(loginInfo[2]) : 0; final timeStamp = loginInfo.length == 3 ? int.parse(loginInfo[2]) : 0;
var status; var status;
Response response; Response response;
await _initDio(); await _initDio();
try { try {
response = await _dio.get( response = await _dio.get(
"$_baseUrl/api/2/subscriptions/$username/$deviceId.json", "$_baseUrl/api/2/subscriptions/$username/$deviceId.json",
queryParameters: {'since': timeStamp}); queryParameters: {'since': timeStamp});
status = response.statusCode; status = response.statusCode;
} catch (e) { } catch (e) {
developer.log(e.toString(), name: 'gpodder update podcasts error'); developer.log(e.toString(), name: 'gpodder update podcasts error');
if (status == 401) { if (status == 401) {
_statusStorage.saveInt(3); _statusStorage.saveInt(3);
} else { } else {
_statusStorage.saveInt(2); _statusStorage.saveInt(2);
} }
return 0; return 0;
} }
if (status == 200) { if (status == 200) {
Map changes = jsonDecode(response.toString()); Map changes = jsonDecode(response.toString());
final timeStamp = changes['timestamp']; final timeStamp = changes['timestamp'];
final addList = changes['add'].cast<String>(); final addList = changes['add'].cast<String>();
final removeList = changes['remove'].cast<String>(); final removeList = changes['remove'].cast<String>();
await _storage.saveStringList([username, deviceId, timeStamp.toString()]); await _storage.saveStringList([username, deviceId, timeStamp.toString()]);
await _remoteAddStorage.addList(addList); await _remoteAddStorage.addList(addList);
await _remoteRemoveStorage.addList(removeList); await _remoteRemoveStorage.addList(removeList);
} }
return status; return status;
} }
Future<int> updateChange() async { Future<int> updateChange() async {
final loginInfo = await _storage.getStringList(); final loginInfo = await _storage.getStringList();
final addList = await _addStorage.getStringList(); final addList = await _addStorage.getStringList();
final removeList = await _removeStorage.getStringList(); final removeList = await _removeStorage.getStringList();
final username = loginInfo[0]; final username = loginInfo[0];
final deviceId = loginInfo[1]; final deviceId = loginInfo[1];
await _initDio(); await _initDio();
var status; var status;
Response response; Response response;
try { try {
response = await _dio.post( response = await _dio.post(
'$_baseUrl/api/2/subscriptions/$username/$deviceId.json', '$_baseUrl/api/2/subscriptions/$username/$deviceId.json',
data: {'add': addList, 'remove': removeList}); data: {'add': addList, 'remove': removeList});
status = response.statusCode; status = response.statusCode;
} catch (e) { } catch (e) {
if (status == 401) { if (status == 401) {
_statusStorage.saveInt(3); _statusStorage.saveInt(3);
} else { } else {
_statusStorage.saveInt(2); _statusStorage.saveInt(2);
} }
developer.log(e.toString(), name: 'gpodder update podcasts error'); developer.log(e.toString(), name: 'gpodder update podcasts error');
return 0; return 0;
} }
if (status == 200) { if (status == 200) {
await _addStorage.clearList(); await _addStorage.clearList();
await _removeStorage.clearList(); await _removeStorage.clearList();
await _statusStorage.saveInt(1); await _statusStorage.saveInt(1);
Map changes = jsonDecode(response.toString()); Map changes = jsonDecode(response.toString());
final timeStamp = changes['timestamp'] as int; final timeStamp = changes['timestamp'] as int;
await _storage await _storage
.saveStringList([username, deviceId, (timeStamp + 1).toString()]); .saveStringList([username, deviceId, (timeStamp + 1).toString()]);
} }
return status; return status;
} }
} }

View File

@ -1,84 +1,84 @@
import 'dart:developer' as developer; import 'dart:developer' as developer;
import 'package:xml/xml.dart' as xml; import 'package:xml/xml.dart' as xml;
import '../state/podcast_group.dart'; import '../state/podcast_group.dart';
class OmplOutline { class OmplOutline {
final String text; final String text;
final String xmlUrl; final String xmlUrl;
OmplOutline({this.text, this.xmlUrl}); OmplOutline({this.text, this.xmlUrl});
factory OmplOutline.parse(xml.XmlElement element) { factory OmplOutline.parse(xml.XmlElement element) {
if (element == null) return null; if (element == null) return null;
return OmplOutline( return OmplOutline(
text: element.getAttribute("text")?.trim(), text: element.getAttribute("text")?.trim(),
xmlUrl: element.getAttribute("xmlUrl")?.trim(), xmlUrl: element.getAttribute("xmlUrl")?.trim(),
); );
} }
} }
class PodcastsBackup { class PodcastsBackup {
///Group list for backup. ///Group list for backup.
final List<PodcastGroup> groups; final List<PodcastGroup> groups;
PodcastsBackup(this.groups) : assert(groups.isNotEmpty); PodcastsBackup(this.groups) : assert(groups.isNotEmpty);
xml.XmlNode omplBuilder() { xml.XmlNode omplBuilder() {
var builder = xml.XmlBuilder(); var builder = xml.XmlBuilder();
builder.processing('xml', 'version="1.0" encoding="UTF-8"'); builder.processing('xml', 'version="1.0" encoding="UTF-8"');
builder.element('ompl', nest: () { builder.element('ompl', nest: () {
builder.attribute('version', '1.0'); builder.attribute('version', '1.0');
builder.element('head', nest: () { builder.element('head', nest: () {
builder.element('title', nest: 'Tsacdop Feed Groups'); builder.element('title', nest: 'Tsacdop Feed Groups');
}); });
builder.element('body', nest: () { builder.element('body', nest: () {
for (var group in groups) { for (var group in groups) {
builder.element('outline', nest: () { builder.element('outline', nest: () {
builder.attribute('text', '${group.name}'); builder.attribute('text', '${group.name}');
builder.attribute('title', '${group.name}'); builder.attribute('title', '${group.name}');
for (var e in group.podcasts) { for (var e in group.podcasts) {
builder.element( builder.element(
'outline', 'outline',
nest: () { nest: () {
builder.attribute('type', 'rss'); builder.attribute('type', 'rss');
builder.attribute('text', '${e.title}'); builder.attribute('text', '${e.title}');
builder.attribute('title', '${e.title}'); builder.attribute('title', '${e.title}');
builder.attribute('xmlUrl', '${e.rssUrl}'); builder.attribute('xmlUrl', '${e.rssUrl}');
}, },
isSelfClosing: true, isSelfClosing: true,
); );
} }
}); });
} }
}); });
}); });
return builder.buildDocument(); return builder.buildDocument();
} }
static parseOPML(String opml) { static parseOPML(String opml) {
var data = <String, List<OmplOutline>>{}; var data = <String, List<OmplOutline>>{};
// var opml = file.readAsStringSync(); // var opml = file.readAsStringSync();
var content = xml.XmlDocument.parse(opml); var content = xml.XmlDocument.parse(opml);
var title = var title =
content.findAllElements('head').first.findElements('title').first.text; content.findAllElements('head').first.findElements('title').first.text;
developer.log(title, name: 'Import OPML'); developer.log(title, name: 'Import OPML');
var groups = content.findAllElements('body').first.findElements('outline'); var groups = content.findAllElements('body').first.findElements('outline');
if (title != 'Tsacdop Feed Groups') { if (title != 'Tsacdop Feed Groups') {
var total = content var total = content
.findAllElements('outline') .findAllElements('outline')
.map((ele) => OmplOutline.parse(ele)) .map((ele) => OmplOutline.parse(ele))
.toList(); .toList();
data['Home'] = total; data['Home'] = total;
return data; return data;
} }
for (var element in groups) { for (var element in groups) {
var title = element.getAttribute('title'); var title = element.getAttribute('title');
var total = element var total = element
.findElements('outline') .findElements('outline')
.map((ele) => OmplOutline.parse(ele)) .map((ele) => OmplOutline.parse(ele))
.toList(); .toList();
data[title] = total; data[title] = total;
} }
return data; return data;
} }
} }

View File

@ -1,125 +1,125 @@
import 'dart:convert'; import 'dart:convert';
import 'package:convert/convert.dart'; import 'package:convert/convert.dart';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import '../.env.dart'; import '../.env.dart';
import '../home/about.dart'; import '../home/about.dart';
import '../type/search_api/index_episode.dart'; import '../type/search_api/index_episode.dart';
import '../type/search_api/index_podcast.dart'; import '../type/search_api/index_podcast.dart';
import '../type/search_api/itunes_podcast.dart'; import '../type/search_api/itunes_podcast.dart';
import '../type/search_api/search_top_podcast.dart'; import '../type/search_api/search_top_podcast.dart';
import '../type/search_api/searchepisodes.dart'; import '../type/search_api/searchepisodes.dart';
import '../type/search_api/searchpodcast.dart'; import '../type/search_api/searchpodcast.dart';
enum SearchEngine { podcastIndex, listenNotes } enum SearchEngine { podcastIndex, listenNotes }
class ListenNotesSearch { class ListenNotesSearch {
final _apiKey = environment['apiKey']; final _apiKey = environment['apiKey'];
Future<SearchPodcast<dynamic>> searchPodcasts( Future<SearchPodcast<dynamic>> searchPodcasts(
{String searchText, int nextOffset}) async { {String searchText, int nextOffset}) async {
var url = "https://listen-api.listennotes.com/api/v2/search?q=" var url = "https://listen-api.listennotes.com/api/v2/search?q="
"${Uri.encodeComponent(searchText)}${"&sort_by_date=0&type=podcast&offset=$nextOffset"}"; "${Uri.encodeComponent(searchText)}${"&sort_by_date=0&type=podcast&offset=$nextOffset"}";
var response = await Dio().get(url, var response = await Dio().get(url,
options: Options(headers: { options: Options(headers: {
'X-ListenAPI-Key': "$_apiKey", 'X-ListenAPI-Key': "$_apiKey",
'Accept': "application/json" 'Accept': "application/json"
})); }));
Map searchResultMap = jsonDecode(response.toString()); Map searchResultMap = jsonDecode(response.toString());
var searchResult = SearchPodcast.fromJson(searchResultMap); var searchResult = SearchPodcast.fromJson(searchResultMap);
return searchResult; return searchResult;
} }
Future<SearchEpisodes<dynamic>> fetchEpisode( Future<SearchEpisodes<dynamic>> fetchEpisode(
{String id, int nextEpisodeDate}) async { {String id, int nextEpisodeDate}) async {
var url = var url =
"https://listen-api.listennotes.com/api/v2/podcasts/$id?next_episode_pub_date=$nextEpisodeDate"; "https://listen-api.listennotes.com/api/v2/podcasts/$id?next_episode_pub_date=$nextEpisodeDate";
var response = await Dio().get(url, var response = await Dio().get(url,
options: Options(headers: { options: Options(headers: {
'X-ListenAPI-Key': "$_apiKey", 'X-ListenAPI-Key': "$_apiKey",
'Accept': "application/json" 'Accept': "application/json"
})); }));
Map searchResultMap = jsonDecode(response.toString()); Map searchResultMap = jsonDecode(response.toString());
var searchResult = SearchEpisodes.fromJson(searchResultMap); var searchResult = SearchEpisodes.fromJson(searchResultMap);
return searchResult; return searchResult;
} }
Future<SearchTopPodcast<dynamic>> fetchBestPodcast( Future<SearchTopPodcast<dynamic>> fetchBestPodcast(
{String genre, int page, String region = 'us'}) async { {String genre, int page, String region = 'us'}) async {
var url = var url =
"https://listen-api.listennotes.com/api/v2/best_podcasts?genre_id=$genre&page=$page&region=$region"; "https://listen-api.listennotes.com/api/v2/best_podcasts?genre_id=$genre&page=$page&region=$region";
var response = await Dio().get(url, var response = await Dio().get(url,
options: Options(headers: { options: Options(headers: {
'X-ListenAPI-Key': "$_apiKey", 'X-ListenAPI-Key': "$_apiKey",
'Accept': "application/json" 'Accept': "application/json"
})); }));
Map searchResultMap = jsonDecode(response.toString()); Map searchResultMap = jsonDecode(response.toString());
var searchResult = SearchTopPodcast.fromJson(searchResultMap); var searchResult = SearchTopPodcast.fromJson(searchResultMap);
return searchResult; return searchResult;
} }
} }
class ItunesSearch { class ItunesSearch {
Future<ItunesSearchResult<dynamic>> searchPodcasts( Future<ItunesSearchResult<dynamic>> searchPodcasts(
{String searchText, int limit}) async { {String searchText, int limit}) async {
final url = "https://itunes.apple.com/search?term=" final url = "https://itunes.apple.com/search?term="
"${Uri.encodeComponent(searchText)}&media=podcast&entity=podcast&limit=$limit"; "${Uri.encodeComponent(searchText)}&media=podcast&entity=podcast&limit=$limit";
final response = await Dio() final response = await Dio()
.get(url, options: Options(headers: {'Accept': "application/json"})); .get(url, options: Options(headers: {'Accept': "application/json"}));
print(response.toString()); print(response.toString());
Map searchResultMap = jsonDecode(response.toString()); Map searchResultMap = jsonDecode(response.toString());
final searchResult = ItunesSearchResult.fromJson(searchResultMap); final searchResult = ItunesSearchResult.fromJson(searchResultMap);
return searchResult; return searchResult;
} }
} }
class PodcastsIndexSearch { class PodcastsIndexSearch {
final _dio = Dio(BaseOptions(connectTimeout: 30000, receiveTimeout: 90000)); final _dio = Dio(BaseOptions(connectTimeout: 30000, receiveTimeout: 90000));
final _baseUrl = 'https://api.podcastindex.org'; final _baseUrl = 'https://api.podcastindex.org';
Map<String, String> _initSearch() { Map<String, String> _initSearch() {
final unixTime = final unixTime =
(DateTime.now().millisecondsSinceEpoch / 1000).round().toString(); (DateTime.now().millisecondsSinceEpoch / 1000).round().toString();
final apiKey = environment['podcastIndexApiKey']; final apiKey = environment['podcastIndexApiKey'];
final apiSecret = environment['podcastIndexApiSecret']; final apiSecret = environment['podcastIndexApiSecret'];
final firstChunk = utf8.encode(apiKey); final firstChunk = utf8.encode(apiKey);
final secondChunk = utf8.encode(apiSecret); final secondChunk = utf8.encode(apiSecret);
final thirdChunk = utf8.encode(unixTime); final thirdChunk = utf8.encode(unixTime);
var output = AccumulatorSink<Digest>(); var output = AccumulatorSink<Digest>();
var input = sha1.startChunkedConversion(output); var input = sha1.startChunkedConversion(output);
input.add(firstChunk); input.add(firstChunk);
input.add(secondChunk); input.add(secondChunk);
input.add(thirdChunk); input.add(thirdChunk);
input.close(); input.close();
var digest = output.events.single; var digest = output.events.single;
var headers = <String, String>{ var headers = <String, String>{
"X-Auth-Date": unixTime, "X-Auth-Date": unixTime,
"X-Auth-Key": apiKey, "X-Auth-Key": apiKey,
"Authorization": digest.toString(), "Authorization": digest.toString(),
"User-Agent": "Tsacdop/$version" "User-Agent": "Tsacdop/$version"
}; };
return headers; return headers;
} }
Future<PodcastIndexSearchResult<dynamic>> searchPodcasts( Future<PodcastIndexSearchResult<dynamic>> searchPodcasts(
{String searchText, int limit = 99}) async { {String searchText, int limit = 99}) async {
final url = "$_baseUrl/api/1.0/search/byterm" final url = "$_baseUrl/api/1.0/search/byterm"
"?q=${Uri.encodeComponent(searchText)}&max=$limit&fulltext=true"; "?q=${Uri.encodeComponent(searchText)}&max=$limit&fulltext=true";
final headers = _initSearch(); final headers = _initSearch();
final response = await _dio.get(url, options: Options(headers: headers)); final response = await _dio.get(url, options: Options(headers: headers));
Map searchResultMap = jsonDecode(response.toString()); Map searchResultMap = jsonDecode(response.toString());
final searchResult = PodcastIndexSearchResult.fromJson(searchResultMap); final searchResult = PodcastIndexSearchResult.fromJson(searchResultMap);
return searchResult; return searchResult;
} }
Future<IndexEpisodeResult<dynamic>> fetchEpisode({String rssUrl}) async { Future<IndexEpisodeResult<dynamic>> fetchEpisode({String rssUrl}) async {
final url = "$_baseUrl/api/1.0/episodes/byfeedurl?url=$rssUrl"; final url = "$_baseUrl/api/1.0/episodes/byfeedurl?url=$rssUrl";
final headers = _initSearch(); final headers = _initSearch();
final response = await _dio.get(url, options: Options(headers: headers)); final response = await _dio.get(url, options: Options(headers: headers));
Map searchResultMap = jsonDecode(response.toString()); Map searchResultMap = jsonDecode(response.toString());
final searchResult = IndexEpisodeResult.fromJson(searchResultMap); final searchResult = IndexEpisodeResult.fromJson(searchResultMap);
return searchResult; return searchResult;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,442 +1,442 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:line_icons/line_icons.dart'; import 'package:line_icons/line_icons.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../local_storage/sqflite_localpodcast.dart'; import '../local_storage/sqflite_localpodcast.dart';
import '../state/download_state.dart'; import '../state/download_state.dart';
import '../type/episodebrief.dart'; import '../type/episodebrief.dart';
import '../util/custom_widget.dart'; import '../util/custom_widget.dart';
import '../util/extension_helper.dart'; import '../util/extension_helper.dart';
class DownloadsManage extends StatefulWidget { class DownloadsManage extends StatefulWidget {
@override @override
_DownloadsManageState createState() => _DownloadsManageState(); _DownloadsManageState createState() => _DownloadsManageState();
} }
class _DownloadsManageState extends State<DownloadsManage> { class _DownloadsManageState extends State<DownloadsManage> {
//Downloaded size //Downloaded size
int _size; int _size;
int _mode; int _mode;
//Downloaded files //Downloaded files
int _fileNum; int _fileNum;
bool _clearing; bool _clearing;
bool _onlyListened; bool _onlyListened;
List<EpisodeBrief> _selectedList; List<EpisodeBrief> _selectedList;
Future<List<EpisodeBrief>> _getDownloadedEpisode(int mode) async { Future<List<EpisodeBrief>> _getDownloadedEpisode(int mode) async {
var episodes = <EpisodeBrief>[]; var episodes = <EpisodeBrief>[];
var dbHelper = DBHelper(); var dbHelper = DBHelper();
episodes = await dbHelper.getDownloadedEpisode(mode); episodes = await dbHelper.getDownloadedEpisode(mode);
return episodes; return episodes;
} }
Future<int> _isListened(EpisodeBrief episode) async { Future<int> _isListened(EpisodeBrief episode) async {
var dbHelper = DBHelper(); var dbHelper = DBHelper();
return await dbHelper.isListened(episode.enclosureUrl); return await dbHelper.isListened(episode.enclosureUrl);
} }
Future<void> _getStorageSize() async { Future<void> _getStorageSize() async {
_size = 0; _size = 0;
_fileNum = 0; _fileNum = 0;
final dirs = await getExternalStorageDirectories(); final dirs = await getExternalStorageDirectories();
for (var dir in dirs) { for (var dir in dirs) {
dir.list().forEach((d) { dir.list().forEach((d) {
var fileDir = Directory(d.path); var fileDir = Directory(d.path);
fileDir.list().forEach((file) async { fileDir.list().forEach((file) async {
await File(file.path).stat().then((value) { await File(file.path).stat().then((value) {
_size += value.size; _size += value.size;
_fileNum += 1; _fileNum += 1;
if (mounted) setState(() {}); if (mounted) setState(() {});
}); });
}); });
}); });
} }
} }
Future<void> _delSelectedEpisodes() async { Future<void> _delSelectedEpisodes() async {
setState(() => _clearing = true); setState(() => _clearing = true);
// await Future.forEach(_selectedList, (EpisodeBrief episode) async // await Future.forEach(_selectedList, (EpisodeBrief episode) async
for (var episode in _selectedList) { for (var episode in _selectedList) {
var downloader = Provider.of<DownloadState>(context, listen: false); var downloader = Provider.of<DownloadState>(context, listen: false);
await downloader.delTask(episode); await downloader.delTask(episode);
if (mounted) setState(() {}); if (mounted) setState(() {});
} }
await Future.delayed(Duration(seconds: 1)); await Future.delayed(Duration(seconds: 1));
if (mounted) { if (mounted) {
setState(() { setState(() {
_clearing = false; _clearing = false;
}); });
} }
await Future.delayed(Duration(seconds: 1)); await Future.delayed(Duration(seconds: 1));
if (mounted) setState(() => _selectedList = []); if (mounted) setState(() => _selectedList = []);
_getStorageSize(); _getStorageSize();
} }
String _downloadDateToString(BuildContext context, String _downloadDateToString(BuildContext context,
{int downloadDate, int pubDate}) { {int downloadDate, int pubDate}) {
final s = context.s; final s = context.s;
var date = DateTime.fromMillisecondsSinceEpoch(downloadDate); var date = DateTime.fromMillisecondsSinceEpoch(downloadDate);
var diffrence = DateTime.now().toUtc().difference(date); var diffrence = DateTime.now().toUtc().difference(date);
if (diffrence.inHours < 24) { if (diffrence.inHours < 24) {
return s.hoursAgo(diffrence.inHours); return s.hoursAgo(diffrence.inHours);
} else if (diffrence.inDays < 7) { } else if (diffrence.inDays < 7) {
return s.daysAgo(diffrence.inDays); return s.daysAgo(diffrence.inDays);
} else { } else {
return DateFormat.yMMMd().format( return DateFormat.yMMMd().format(
DateTime.fromMillisecondsSinceEpoch(pubDate, isUtc: true).toLocal()); DateTime.fromMillisecondsSinceEpoch(pubDate, isUtc: true).toLocal());
} }
} }
int sumSelected() { int sumSelected() {
var sum = 0; var sum = 0;
if (_selectedList.length == 0) { if (_selectedList.length == 0) {
return sum; return sum;
} else { } else {
for (var episode in _selectedList) { for (var episode in _selectedList) {
sum += episode.enclosureLength; sum += episode.enclosureLength;
} }
return sum; return sum;
} }
} }
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_clearing = false; _clearing = false;
_selectedList = []; _selectedList = [];
_mode = 0; _mode = 0;
_onlyListened = false; _onlyListened = false;
_getStorageSize(); _getStorageSize();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final s = context.s; final s = context.s;
return AnnotatedRegion<SystemUiOverlayStyle>( return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle( value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness, statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: Theme.of(context).primaryColor, systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness: systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness, Theme.of(context).accentColorBrightness,
), ),
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
leading: CustomBackButton(), leading: CustomBackButton(),
elevation: 0, elevation: 0,
backgroundColor: context.primaryColor, backgroundColor: context.primaryColor,
), ),
body: SafeArea( body: SafeArea(
child: Stack( child: Stack(
children: <Widget>[ children: <Widget>[
Column( Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Container( Container(
height: 140.0, height: 140.0,
color: context.primaryColor, color: context.primaryColor,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.only(left: 10), padding: const EdgeInsets.only(left: 10),
child: RichText( child: RichText(
text: TextSpan( text: TextSpan(
text: 'Total ', text: 'Total ',
style: TextStyle( style: TextStyle(
color: context.accentColor, color: context.accentColor,
fontSize: 20, fontSize: 20,
), ),
children: <TextSpan>[ children: <TextSpan>[
TextSpan( TextSpan(
text: _fileNum.toString(), text: _fileNum.toString(),
style: GoogleFonts.cairo( style: GoogleFonts.cairo(
textStyle: TextStyle( textStyle: TextStyle(
color: context.accentColor, color: context.accentColor,
fontSize: 40, fontSize: 40,
)), )),
), ),
TextSpan( TextSpan(
text: _fileNum < 2 text: _fileNum < 2
? ' episode' ? ' episode'
: ' episodes ', : ' episodes ',
style: TextStyle( style: TextStyle(
color: context.accentColor, color: context.accentColor,
fontSize: 20, fontSize: 20,
)), )),
TextSpan( TextSpan(
text: (_size ~/ 1000000) < 1000 text: (_size ~/ 1000000) < 1000
? (_size ~/ 1000000).toString() ? (_size ~/ 1000000).toString()
: (_size / 1000000000).toStringAsFixed(1), : (_size / 1000000000).toStringAsFixed(1),
style: GoogleFonts.cairo( style: GoogleFonts.cairo(
textStyle: TextStyle( textStyle: TextStyle(
color: Theme.of(context).accentColor, color: Theme.of(context).accentColor,
fontSize: 50, fontSize: 50,
)), )),
), ),
TextSpan( TextSpan(
text: text:
(_size ~/ 1000000) < 1000 ? 'Mb' : 'Gb', (_size ~/ 1000000) < 1000 ? 'Mb' : 'Gb',
style: TextStyle( style: TextStyle(
color: Theme.of(context).accentColor, color: Theme.of(context).accentColor,
fontSize: 20, fontSize: 20,
)), )),
], ],
), ),
), ),
), ),
Spacer(), Spacer(),
SizedBox( SizedBox(
height: 40, height: 40,
child: Row( child: Row(
children: [ children: [
Material( Material(
color: Colors.transparent, color: Colors.transparent,
child: PopupMenuButton<int>( child: PopupMenuButton<int>(
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(
Radius.circular(10))), Radius.circular(10))),
elevation: 1, elevation: 1,
tooltip: s.homeSubMenuSortBy, tooltip: s.homeSubMenuSortBy,
child: Container( child: Container(
height: 40, height: 40,
padding: padding:
EdgeInsets.symmetric(horizontal: 20), EdgeInsets.symmetric(horizontal: 20),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Text(s.homeSubMenuSortBy), Text(s.homeSubMenuSortBy),
Padding( Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: 5), horizontal: 5),
), ),
Icon( Icon(
_mode == 0 _mode == 0
? LineIcons ? LineIcons
.hourglass_start_solid .hourglass_start_solid
: _mode == 1 : _mode == 1
? LineIcons ? LineIcons
.hourglass_half_solid .hourglass_half_solid
: LineIcons.save, : LineIcons.save,
size: 18, size: 18,
) )
], ],
)), )),
itemBuilder: (context) => [ itemBuilder: (context) => [
PopupMenuItem( PopupMenuItem(
value: 0, value: 0,
child: Text(s.newestFirst), child: Text(s.newestFirst),
), ),
PopupMenuItem( PopupMenuItem(
value: 1, value: 1,
child: Text(s.oldestFirst), child: Text(s.oldestFirst),
), ),
PopupMenuItem( PopupMenuItem(
value: 2, value: 2,
child: Text(s.size), child: Text(s.size),
), ),
], ],
onSelected: (value) { onSelected: (value) {
if (value == 0) { if (value == 0) {
setState(() => _mode = 0); setState(() => _mode = 0);
} else if (value == 1) { } else if (value == 1) {
setState(() => _mode = 1); setState(() => _mode = 1);
} else if (value == 2) { } else if (value == 2) {
setState(() => _mode = 2); setState(() => _mode = 2);
} }
}, },
), ),
), ),
//Spacer(), //Spacer(),
Material( Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
onTap: () => setState(() { onTap: () => setState(() {
_onlyListened = !_onlyListened; _onlyListened = !_onlyListened;
}), }),
child: Row( child: Row(
children: [ children: [
Padding( Padding(
padding: padding:
EdgeInsets.symmetric(horizontal: 5), EdgeInsets.symmetric(horizontal: 5),
), ),
Text(s.listened), Text(s.listened),
Transform.scale( Transform.scale(
scale: 0.8, scale: 0.8,
child: Checkbox( child: Checkbox(
value: _onlyListened, value: _onlyListened,
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
_onlyListened = value; _onlyListened = value;
}); });
}), }),
), ),
], ],
), ),
), ),
), ),
], ],
), ),
), ),
], ],
), ),
), ),
Expanded( Expanded(
child: FutureBuilder<List<EpisodeBrief>>( child: FutureBuilder<List<EpisodeBrief>>(
future: _getDownloadedEpisode(_mode), future: _getDownloadedEpisode(_mode),
initialData: [], initialData: [],
builder: (context, snapshot) { builder: (context, snapshot) {
var _episodes = snapshot.data; var _episodes = snapshot.data;
return ListView.builder( return ListView.builder(
itemCount: _episodes.length, itemCount: _episodes.length,
shrinkWrap: true, shrinkWrap: true,
scrollDirection: Axis.vertical, scrollDirection: Axis.vertical,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return FutureBuilder( return FutureBuilder(
future: _isListened(_episodes[index]), future: _isListened(_episodes[index]),
initialData: 0, initialData: 0,
builder: (context, snapshot) { builder: (context, snapshot) {
return (_onlyListened && snapshot.data == 0) return (_onlyListened && snapshot.data == 0)
? Center() ? Center()
: Column( : Column(
children: <Widget>[ children: <Widget>[
ListTile( ListTile(
onTap: () { onTap: () {
if (_selectedList.contains( if (_selectedList.contains(
_episodes[index])) { _episodes[index])) {
setState(() => _selectedList setState(() => _selectedList
.removeWhere((episode) => .removeWhere((episode) =>
episode episode
.enclosureUrl == .enclosureUrl ==
_episodes[index] _episodes[index]
.enclosureUrl)); .enclosureUrl));
} else { } else {
setState(() => _selectedList setState(() => _selectedList
.add(_episodes[index])); .add(_episodes[index]));
} }
}, },
leading: CircleAvatar( leading: CircleAvatar(
backgroundImage: backgroundImage:
_episodes[index] _episodes[index]
.avatarImage), .avatarImage),
title: Text( title: Text(
_episodes[index].title, _episodes[index].title,
maxLines: 1, maxLines: 1,
overflow: overflow:
TextOverflow.ellipsis, TextOverflow.ellipsis,
), ),
subtitle: Row( subtitle: Row(
children: [ children: [
Text(_downloadDateToString( Text(_downloadDateToString(
context, context,
downloadDate: downloadDate:
_episodes[index] _episodes[index]
.downloadDate, .downloadDate,
pubDate: pubDate:
_episodes[index] _episodes[index]
.pubDate)), .pubDate)),
SizedBox(width: 20), SizedBox(width: 20),
if (_episodes[index] if (_episodes[index]
.enclosureLength != .enclosureLength !=
0) 0)
Text( Text(
'${(_episodes[index].enclosureLength) ~/ 1000000} Mb'), '${(_episodes[index].enclosureLength) ~/ 1000000} Mb'),
], ],
), ),
trailing: Checkbox( trailing: Checkbox(
value: _selectedList.contains( value: _selectedList.contains(
_episodes[index]), _episodes[index]),
onChanged: (boo) { onChanged: (boo) {
if (boo) { if (boo) {
setState(() => setState(() =>
_selectedList.add( _selectedList.add(
_episodes[ _episodes[
index])); index]));
} else { } else {
setState(() => _selectedList setState(() => _selectedList
.removeWhere((episode) => .removeWhere((episode) =>
episode episode
.enclosureUrl == .enclosureUrl ==
_episodes[index] _episodes[index]
.enclosureUrl)); .enclosureUrl));
} }
}, },
), ),
), ),
Divider( Divider(
height: 2, height: 2,
), ),
], ],
); );
}); });
}); });
}, },
), ),
) )
], ],
), ),
AnimatedPositioned( AnimatedPositioned(
duration: Duration(milliseconds: 800), duration: Duration(milliseconds: 800),
curve: Curves.elasticInOut, curve: Curves.elasticInOut,
left: context.width / 2 - 50, left: context.width / 2 - 50,
bottom: _selectedList.length == 0 ? -100 : 30, bottom: _selectedList.length == 0 ? -100 : 30,
child: InkWell( child: InkWell(
onTap: _delSelectedEpisodes, onTap: _delSelectedEpisodes,
child: Stack( child: Stack(
alignment: _clearing alignment: _clearing
? Alignment.centerLeft ? Alignment.centerLeft
: Alignment.centerRight, : Alignment.centerRight,
children: <Widget>[ children: <Widget>[
Container( Container(
alignment: Alignment.center, alignment: Alignment.center,
width: 100, width: 100,
height: 40, height: 40,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: borderRadius:
BorderRadius.all(Radius.circular(20.0)), BorderRadius.all(Radius.circular(20.0)),
color: Theme.of(context).accentColor, color: Theme.of(context).accentColor,
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[ children: <Widget>[
Icon( Icon(
LineIcons.trash_alt_solid, LineIcons.trash_alt_solid,
color: Colors.white, color: Colors.white,
), ),
Text('${sumSelected() ~/ 1000000}Mb', Text('${sumSelected() ~/ 1000000}Mb',
style: TextStyle(color: Colors.white)), style: TextStyle(color: Colors.white)),
], ],
), ),
), ),
SingleChildScrollView( SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: AnimatedContainer( child: AnimatedContainer(
duration: Duration(milliseconds: 500), duration: Duration(milliseconds: 500),
alignment: Alignment.center, alignment: Alignment.center,
width: _clearing ? 100 : 0, width: _clearing ? 100 : 0,
height: _clearing ? 40 : 0, height: _clearing ? 40 : 0,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: borderRadius:
BorderRadius.all(Radius.circular(20.0)), BorderRadius.all(Radius.circular(20.0)),
color: Colors.red.withOpacity(0.6), color: Colors.red.withOpacity(0.6),
), ),
), ),
), ),
], ],
)), )),
), ),
], ],
), ),
), ),
), ),
); );
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,148 +1,148 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:intl/intl_standalone.dart'; import 'package:intl/intl_standalone.dart';
import '../generated/l10n.dart'; import '../generated/l10n.dart';
import '../local_storage/key_value_storage.dart'; import '../local_storage/key_value_storage.dart';
import '../util/extension_helper.dart'; import '../util/extension_helper.dart';
class LanguagesSetting extends StatefulWidget { class LanguagesSetting extends StatefulWidget {
const LanguagesSetting({Key key}) : super(key: key); const LanguagesSetting({Key key}) : super(key: key);
@override @override
_LanguagesSettingState createState() => _LanguagesSettingState(); _LanguagesSettingState createState() => _LanguagesSettingState();
} }
class _LanguagesSettingState extends State<LanguagesSetting> { class _LanguagesSettingState extends State<LanguagesSetting> {
_setLocale(Locale locale, {bool systemDefault = false}) async { _setLocale(Locale locale, {bool systemDefault = false}) async {
var localeStorage = KeyValueStorage(localeKey); var localeStorage = KeyValueStorage(localeKey);
if (systemDefault) { if (systemDefault) {
await localeStorage.saveStringList([]); await localeStorage.saveStringList([]);
await findSystemLocale(); await findSystemLocale();
var systemLanCode; var systemLanCode;
final list = Intl.systemLocale.split('_'); final list = Intl.systemLocale.split('_');
if (list.length == 2) { if (list.length == 2) {
systemLanCode = list.first; systemLanCode = list.first;
} else if (list.length == 3) { } else if (list.length == 3) {
systemLanCode = '${list[0]}_${list[1]}'; systemLanCode = '${list[0]}_${list[1]}';
} else { } else {
systemLanCode = 'en'; systemLanCode = 'en';
} }
await S.load(Locale(systemLanCode)); await S.load(Locale(systemLanCode));
if (mounted) { if (mounted) {
setState(() {}); setState(() {});
} }
} else { } else {
await localeStorage await localeStorage
.saveStringList([locale.languageCode, locale.countryCode]); .saveStringList([locale.languageCode, locale.countryCode]);
await S.load(locale); await S.load(locale);
if (mounted) { if (mounted) {
setState(() {}); setState(() {});
} }
} }
} }
@override @override
void initState() { void initState() {
super.initState(); super.initState();
findSystemLocale(); findSystemLocale();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final s = context.s; final s = context.s;
return Column( return Column(
children: [ children: [
ListTile( ListTile(
title: Text( title: Text(
s.systemDefault, s.systemDefault,
style: TextStyle( style: TextStyle(
color: Intl.systemLocale.contains(Intl.getCurrentLocale()) color: Intl.systemLocale.contains(Intl.getCurrentLocale())
? context.accentColor ? context.accentColor
: null), : null),
), ),
onTap: () => onTap: () =>
_setLocale(Locale(Intl.systemLocale), systemDefault: true), _setLocale(Locale(Intl.systemLocale), systemDefault: true),
contentPadding: const EdgeInsets.only(left: 20, right: 20), contentPadding: const EdgeInsets.only(left: 20, right: 20),
), ),
Divider(height: 1), Divider(height: 1),
ListTile( ListTile(
title: Text('English'), title: Text('English'),
onTap: () => _setLocale(Locale('en')), onTap: () => _setLocale(Locale('en')),
contentPadding: const EdgeInsets.only(left: 20, right: 20), contentPadding: const EdgeInsets.only(left: 20, right: 20),
trailing: Radio<Locale>( trailing: Radio<Locale>(
value: Locale('en'), value: Locale('en'),
groupValue: Locale(Intl.getCurrentLocale()), groupValue: Locale(Intl.getCurrentLocale()),
onChanged: _setLocale)), onChanged: _setLocale)),
Divider(height: 1), Divider(height: 1),
ListTile( ListTile(
title: Text('简体中文'), title: Text('简体中文'),
onTap: () => _setLocale(Locale('zh_Hans')), onTap: () => _setLocale(Locale('zh_Hans')),
contentPadding: const EdgeInsets.only(left: 20, right: 20), contentPadding: const EdgeInsets.only(left: 20, right: 20),
trailing: Radio<Locale>( trailing: Radio<Locale>(
value: Locale('zh_Hans'), value: Locale('zh_Hans'),
groupValue: Locale(Intl.getCurrentLocale()), groupValue: Locale(Intl.getCurrentLocale()),
onChanged: _setLocale, onChanged: _setLocale,
)), )),
Divider(height: 1), Divider(height: 1),
ListTile( ListTile(
title: Text('Français'), title: Text('Français'),
onTap: () => _setLocale(Locale('fr')), onTap: () => _setLocale(Locale('fr')),
contentPadding: const EdgeInsets.only(left: 20, right: 20), contentPadding: const EdgeInsets.only(left: 20, right: 20),
trailing: Radio<Locale>( trailing: Radio<Locale>(
value: Locale('fr'), value: Locale('fr'),
groupValue: Locale(Intl.getCurrentLocale()), groupValue: Locale(Intl.getCurrentLocale()),
onChanged: _setLocale), onChanged: _setLocale),
), ),
Divider(height: 1), Divider(height: 1),
ListTile( ListTile(
title: Text('Español'), title: Text('Español'),
onTap: () => _setLocale(Locale('es')), onTap: () => _setLocale(Locale('es')),
contentPadding: const EdgeInsets.only(left: 20, right: 20), contentPadding: const EdgeInsets.only(left: 20, right: 20),
trailing: Radio<Locale>( trailing: Radio<Locale>(
value: Locale('es'), value: Locale('es'),
groupValue: Locale(Intl.getCurrentLocale()), groupValue: Locale(Intl.getCurrentLocale()),
onChanged: _setLocale), onChanged: _setLocale),
), ),
Divider(height: 1), Divider(height: 1),
ListTile( ListTile(
title: Text('Português'), title: Text('Português'),
onTap: () => _setLocale(Locale('pt')), onTap: () => _setLocale(Locale('pt')),
contentPadding: const EdgeInsets.only(left: 20, right: 20), contentPadding: const EdgeInsets.only(left: 20, right: 20),
trailing: Radio<Locale>( trailing: Radio<Locale>(
value: Locale('pt'), value: Locale('pt'),
groupValue: Locale(Intl.getCurrentLocale()), groupValue: Locale(Intl.getCurrentLocale()),
onChanged: _setLocale), onChanged: _setLocale),
), ),
Divider(height: 1), Divider(height: 1),
ListTile( ListTile(
title: Text('Italiano'), title: Text('Italiano'),
onTap: () => _setLocale(Locale('it')), onTap: () => _setLocale(Locale('it')),
contentPadding: const EdgeInsets.only(left: 20, right: 20), contentPadding: const EdgeInsets.only(left: 20, right: 20),
trailing: Radio<Locale>( trailing: Radio<Locale>(
value: Locale('it'), value: Locale('it'),
groupValue: Locale(Intl.getCurrentLocale()), groupValue: Locale(Intl.getCurrentLocale()),
onChanged: _setLocale), onChanged: _setLocale),
), ),
Divider(height: 1), Divider(height: 1),
ListTile( ListTile(
onTap: () => onTap: () =>
'mailto:<tsacdop.app@gmail.com>?subject=Tsacdop localization project' 'mailto:<tsacdop.app@gmail.com>?subject=Tsacdop localization project'
.launchUrl, .launchUrl,
contentPadding: const EdgeInsets.only(left: 20, right: 20), contentPadding: const EdgeInsets.only(left: 20, right: 20),
title: Align( title: Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Image( child: Image(
image: Theme.of(context).brightness == Brightness.light image: Theme.of(context).brightness == Brightness.light
? AssetImage('assets/localizely_logo.png') ? AssetImage('assets/localizely_logo.png')
: AssetImage('assets/localizely_logo_light.png'), : AssetImage('assets/localizely_logo_light.png'),
height: 20), height: 20),
), ),
subtitle: Text( subtitle: Text(
"If you'd like to contribute to localization project, please contact me."), "If you'd like to contribute to localization project, please contact me."),
), ),
], ],
); );
} }
} }

View File

@ -1,366 +1,366 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../local_storage/key_value_storage.dart'; import '../local_storage/key_value_storage.dart';
import '../service/search_api.dart'; import '../service/search_api.dart';
import '../state/audio_state.dart'; import '../state/audio_state.dart';
import '../util/custom_dropdown.dart'; import '../util/custom_dropdown.dart';
import '../util/custom_widget.dart'; import '../util/custom_widget.dart';
import '../util/episodegrid.dart'; import '../util/episodegrid.dart';
import '../util/extension_helper.dart'; import '../util/extension_helper.dart';
import 'popup_menu.dart'; import 'popup_menu.dart';
class LayoutSetting extends StatefulWidget { class LayoutSetting extends StatefulWidget {
const LayoutSetting({Key key}) : super(key: key); const LayoutSetting({Key key}) : super(key: key);
@override @override
_LayoutSettingState createState() => _LayoutSettingState(); _LayoutSettingState createState() => _LayoutSettingState();
} }
class _LayoutSettingState extends State<LayoutSetting> { class _LayoutSettingState extends State<LayoutSetting> {
final _hideDiscoveyStorage = KeyValueStorage(hidePodcastDiscoveryKey); final _hideDiscoveyStorage = KeyValueStorage(hidePodcastDiscoveryKey);
Future<Layout> _getLayout(String key) async { Future<Layout> _getLayout(String key) async {
var keyValueStorage = KeyValueStorage(key); var keyValueStorage = KeyValueStorage(key);
var layout = await keyValueStorage.getInt(); var layout = await keyValueStorage.getInt();
return Layout.values[layout]; return Layout.values[layout];
} }
Future<bool> _getHideDiscovery() async { Future<bool> _getHideDiscovery() async {
return await _hideDiscoveyStorage.getBool(defaultValue: false); return await _hideDiscoveyStorage.getBool(defaultValue: false);
} }
Future<void> _saveHideDiscovery(bool boo) async { Future<void> _saveHideDiscovery(bool boo) async {
await _hideDiscoveyStorage.saveBool(boo); await _hideDiscoveyStorage.saveBool(boo);
if (mounted) setState(() {}); if (mounted) setState(() {});
} }
Future<bool> _hideListened() async { Future<bool> _hideListened() async {
var hideListenedStorage = KeyValueStorage(hideListenedKey); var hideListenedStorage = KeyValueStorage(hideListenedKey);
var hideListened = await hideListenedStorage.getBool(defaultValue: false); var hideListened = await hideListenedStorage.getBool(defaultValue: false);
return hideListened; return hideListened;
} }
Future<void> _saveHideListened(bool boo) async { Future<void> _saveHideListened(bool boo) async {
var hideListenedStorage = KeyValueStorage(hideListenedKey); var hideListenedStorage = KeyValueStorage(hideListenedKey);
await hideListenedStorage.saveBool(boo); await hideListenedStorage.saveBool(boo);
if (mounted) setState(() {}); if (mounted) setState(() {});
} }
Future<SearchEngine> _getSearchEngine() async { Future<SearchEngine> _getSearchEngine() async {
final storage = KeyValueStorage(searchEngineKey); final storage = KeyValueStorage(searchEngineKey);
final index = await storage.getInt(); final index = await storage.getInt();
return SearchEngine.values[index]; return SearchEngine.values[index];
} }
Future<void> _saveSearchEngine(SearchEngine engine) async { Future<void> _saveSearchEngine(SearchEngine engine) async {
final storage = KeyValueStorage(searchEngineKey); final storage = KeyValueStorage(searchEngineKey);
await storage.saveInt(engine.index); await storage.saveInt(engine.index);
if (mounted) setState(() {}); if (mounted) setState(() {});
} }
String _getHeightString(PlayerHeight mode) { String _getHeightString(PlayerHeight mode) {
final s = context.s; final s = context.s;
switch (mode) { switch (mode) {
case PlayerHeight.short: case PlayerHeight.short:
return s.playerHeightShort; return s.playerHeightShort;
break; break;
case PlayerHeight.mid: case PlayerHeight.mid:
return s.playerHeightMed; return s.playerHeightMed;
break; break;
case PlayerHeight.tall: case PlayerHeight.tall:
return s.playerHeightTall; return s.playerHeightTall;
break; break;
default: default:
return ''; return '';
} }
} }
Widget _gridOptions(BuildContext context, Widget _gridOptions(BuildContext context,
{String key, {String key,
Layout layout, Layout layout,
Layout option, Layout option,
double scale, double scale,
BorderRadiusGeometry borderRadius}) => BorderRadiusGeometry borderRadius}) =>
Padding( Padding(
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0), padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
child: InkWell( child: InkWell(
onTap: () async { onTap: () async {
var storage = KeyValueStorage(key); var storage = KeyValueStorage(key);
await storage.saveInt(option.index); await storage.saveInt(option.index);
setState(() {}); setState(() {});
}, },
borderRadius: borderRadius, borderRadius: borderRadius,
child: AnimatedContainer( child: AnimatedContainer(
duration: Duration(milliseconds: 400), duration: Duration(milliseconds: 400),
height: 30, height: 30,
width: 50, width: 50,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: borderRadius, borderRadius: borderRadius,
color: layout == option color: layout == option
? context.accentColor ? context.accentColor
: context.primaryColorDark, : context.primaryColorDark,
), ),
alignment: Alignment.center, alignment: Alignment.center,
child: SizedBox( child: SizedBox(
height: 10, height: 10,
width: 30, width: 30,
child: CustomPaint( child: CustomPaint(
painter: LayoutPainter( painter: LayoutPainter(
scale, scale,
layout == option layout == option
? Colors.white ? Colors.white
: context.textTheme.bodyText1.color), : context.textTheme.bodyText1.color),
), ),
), ),
), ),
), ),
); );
Widget _setDefaultGrid(BuildContext context, {String key}) { Widget _setDefaultGrid(BuildContext context, {String key}) {
return FutureBuilder<Layout>( return FutureBuilder<Layout>(
future: _getLayout(key), future: _getLayout(key),
builder: (context, snapshot) { builder: (context, snapshot) {
return snapshot.hasData return snapshot.hasData
? Row( ? Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
_gridOptions( _gridOptions(
context, context,
key: key, key: key,
layout: snapshot.data, layout: snapshot.data,
option: Layout.one, option: Layout.one,
scale: 4, scale: 4,
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(5), bottomLeft: Radius.circular(5),
topLeft: Radius.circular(5)), topLeft: Radius.circular(5)),
), ),
_gridOptions( _gridOptions(
context, context,
key: key, key: key,
layout: snapshot.data, layout: snapshot.data,
option: Layout.two, option: Layout.two,
scale: 1, scale: 1,
), ),
_gridOptions(context, _gridOptions(context,
key: key, key: key,
layout: snapshot.data, layout: snapshot.data,
option: Layout.three, option: Layout.three,
scale: 0, scale: 0,
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
bottomRight: Radius.circular(5), bottomRight: Radius.circular(5),
topRight: Radius.circular(5))), topRight: Radius.circular(5))),
], ],
) )
: Center(); : Center();
}); });
} }
Widget _setDefaultGridView(BuildContext context, {String text, String key}) { Widget _setDefaultGridView(BuildContext context, {String text, String key}) {
return Padding( return Padding(
padding: EdgeInsets.only(left: 70.0, right: 20, bottom: 10), padding: EdgeInsets.only(left: 70.0, right: 20, bottom: 10),
child: context.width > 360 child: context.width > 360
? Row( ? Row(
children: [ children: [
Text( Text(
text, text,
), ),
Spacer(), Spacer(),
_setDefaultGrid(context, key: key), _setDefaultGrid(context, key: key),
], ],
) )
: Column( : Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
text, text,
), ),
_setDefaultGrid(context, key: key), _setDefaultGrid(context, key: key),
], ],
), ),
); );
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final s = context.s; final s = context.s;
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false); var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
return AnnotatedRegion<SystemUiOverlayStyle>( return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle( value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness, statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: context.primaryColor, systemNavigationBarColor: context.primaryColor,
systemNavigationBarIconBrightness: systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness, Theme.of(context).accentColorBrightness,
), ),
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(s.settingsLayout), title: Text(s.settingsLayout),
leading: CustomBackButton(), leading: CustomBackButton(),
elevation: 0, elevation: 0,
backgroundColor: context.primaryColor, backgroundColor: context.primaryColor,
), ),
body: SingleChildScrollView( body: SingleChildScrollView(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Padding( Padding(
padding: EdgeInsets.all(10.0), padding: EdgeInsets.all(10.0),
), ),
Container( Container(
height: 30.0, height: 30.0,
padding: const EdgeInsets.symmetric(horizontal: 70), padding: const EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text(s.settingsPopupMenu, child: Text(s.settingsPopupMenu,
style: context.textTheme.bodyText1 style: context.textTheme.bodyText1
.copyWith(color: context.accentColor)), .copyWith(color: context.accentColor)),
), ),
ListTile( ListTile(
onTap: () => Navigator.push( onTap: () => Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => PopupMenuSetting())), builder: (context) => PopupMenuSetting())),
contentPadding: EdgeInsets.only(left: 70.0, right: 20), contentPadding: EdgeInsets.only(left: 70.0, right: 20),
title: Text(s.settingsPopupMenu), title: Text(s.settingsPopupMenu),
subtitle: Text(s.settingsPopupMenuDes), subtitle: Text(s.settingsPopupMenuDes),
), ),
Divider(height: 1), Divider(height: 1),
Padding( Padding(
padding: EdgeInsets.all(10.0), padding: EdgeInsets.all(10.0),
), ),
Container( Container(
height: 30.0, height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70), padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text(s.player, child: Text(s.player,
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodyText1 .bodyText1
.copyWith(color: Theme.of(context).accentColor)), .copyWith(color: Theme.of(context).accentColor)),
), ),
ListTile( ListTile(
contentPadding: EdgeInsets.fromLTRB(70, 10, 10, 10), contentPadding: EdgeInsets.fromLTRB(70, 10, 10, 10),
title: Text(s.settingsPlayerHeight), title: Text(s.settingsPlayerHeight),
subtitle: Text(s.settingsPlayerHeightDes), subtitle: Text(s.settingsPlayerHeightDes),
trailing: Selector<AudioPlayerNotifier, PlayerHeight>( trailing: Selector<AudioPlayerNotifier, PlayerHeight>(
selector: (_, audio) => audio.playerHeight, selector: (_, audio) => audio.playerHeight,
builder: (_, data, __) => MyDropdownButton( builder: (_, data, __) => MyDropdownButton(
hint: Text(_getHeightString(data)), hint: Text(_getHeightString(data)),
underline: Center(), underline: Center(),
elevation: 1, elevation: 1,
value: data.index, value: data.index,
items: <int>[0, 1, 2].map<DropdownMenuItem<int>>((e) { items: <int>[0, 1, 2].map<DropdownMenuItem<int>>((e) {
return DropdownMenuItem<int>( return DropdownMenuItem<int>(
value: e, value: e,
child: Text( child: Text(
_getHeightString(PlayerHeight.values[e]))); _getHeightString(PlayerHeight.values[e])));
}).toList(), }).toList(),
onChanged: (index) => onChanged: (index) =>
audio.setPlayerHeight = PlayerHeight.values[index]), audio.setPlayerHeight = PlayerHeight.values[index]),
), ),
), ),
Divider(height: 1), Divider(height: 1),
Padding( Padding(
padding: EdgeInsets.all(10.0), padding: EdgeInsets.all(10.0),
), ),
Container( Container(
height: 30.0, height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70), padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text(s.search, child: Text(s.search,
style: context.textTheme.bodyText1 style: context.textTheme.bodyText1
.copyWith(color: context.accentColor)), .copyWith(color: context.accentColor)),
), ),
FutureBuilder<bool>( FutureBuilder<bool>(
future: _getHideDiscovery(), future: _getHideDiscovery(),
initialData: false, initialData: false,
builder: (context, snapshot) => ListTile( builder: (context, snapshot) => ListTile(
contentPadding: EdgeInsets.fromLTRB(70, 10, 10, 10), contentPadding: EdgeInsets.fromLTRB(70, 10, 10, 10),
onTap: () => _saveHideDiscovery(!snapshot.data), onTap: () => _saveHideDiscovery(!snapshot.data),
title: Text(s.hidePodcastDiscovery), title: Text(s.hidePodcastDiscovery),
subtitle: Text(s.hidePodcastDiscoveryDes), subtitle: Text(s.hidePodcastDiscoveryDes),
trailing: Transform.scale( trailing: Transform.scale(
scale: 0.9, scale: 0.9,
child: Switch( child: Switch(
value: snapshot.data, onChanged: _saveHideDiscovery), value: snapshot.data, onChanged: _saveHideDiscovery),
), ),
), ),
), ),
FutureBuilder( FutureBuilder(
future: _getSearchEngine(), future: _getSearchEngine(),
initialData: SearchEngine.listenNotes, initialData: SearchEngine.listenNotes,
builder: (context, snapshot) => ListTile( builder: (context, snapshot) => ListTile(
contentPadding: EdgeInsets.fromLTRB(70, 10, 10, 10), contentPadding: EdgeInsets.fromLTRB(70, 10, 10, 10),
title: Text(s.defaultSearchEngine), title: Text(s.defaultSearchEngine),
subtitle: Text(s.defaultSearchEngineDes), subtitle: Text(s.defaultSearchEngineDes),
trailing: MyDropdownButton( trailing: MyDropdownButton(
hint: Text(''), hint: Text(''),
underline: Center(), underline: Center(),
elevation: 1, elevation: 1,
value: snapshot.data, value: snapshot.data,
items: [ items: [
DropdownMenuItem<SearchEngine>( DropdownMenuItem<SearchEngine>(
value: SearchEngine.podcastIndex, value: SearchEngine.podcastIndex,
child: Text('Podcastindex')), child: Text('Podcastindex')),
DropdownMenuItem<SearchEngine>( DropdownMenuItem<SearchEngine>(
value: SearchEngine.listenNotes, value: SearchEngine.listenNotes,
child: Text('ListenNotes')), child: Text('ListenNotes')),
], ],
onChanged: (value) => _saveSearchEngine(value)), onChanged: (value) => _saveSearchEngine(value)),
), ),
), ),
Divider(height: 1), Divider(height: 1),
Padding( Padding(
padding: EdgeInsets.all(10.0), padding: EdgeInsets.all(10.0),
), ),
Container( Container(
height: 30.0, height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70), padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text(s.settingsDefaultGrid, child: Text(s.settingsDefaultGrid,
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodyText1 .bodyText1
.copyWith(color: Theme.of(context).accentColor)), .copyWith(color: Theme.of(context).accentColor)),
), ),
ListView( ListView(
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true, shrinkWrap: true,
scrollDirection: Axis.vertical, scrollDirection: Axis.vertical,
children: <Widget>[ children: <Widget>[
FutureBuilder<bool>( FutureBuilder<bool>(
future: _hideListened(), future: _hideListened(),
initialData: false, initialData: false,
builder: (context, snapshot) => ListTile( builder: (context, snapshot) => ListTile(
contentPadding: EdgeInsets.only(left: 70, right: 10), contentPadding: EdgeInsets.only(left: 70, right: 10),
onTap: () => _saveHideListened(!snapshot.data), onTap: () => _saveHideListened(!snapshot.data),
title: Text('Hide listened'), title: Text('Hide listened'),
subtitle: Text('Hide listened episodes by default'), subtitle: Text('Hide listened episodes by default'),
trailing: Transform.scale( trailing: Transform.scale(
scale: 0.9, scale: 0.9,
child: Switch( child: Switch(
value: snapshot.data, value: snapshot.data,
onChanged: _saveHideListened), onChanged: _saveHideListened),
), ),
), ),
), ),
_setDefaultGridView(context, _setDefaultGridView(context,
text: s.settingsDefaultGridPodcast, text: s.settingsDefaultGridPodcast,
key: podcastLayoutKey), key: podcastLayoutKey),
_setDefaultGridView(context, _setDefaultGridView(context,
text: s.settingsDefaultGridRecent, text: s.settingsDefaultGridRecent,
key: recentLayoutKey), key: recentLayoutKey),
_setDefaultGridView(context, _setDefaultGridView(context,
text: s.settingsDefaultGridFavorite, text: s.settingsDefaultGridFavorite,
key: favLayoutKey), key: favLayoutKey),
_setDefaultGridView(context, _setDefaultGridView(context,
text: s.settingsDefaultGridDownload, text: s.settingsDefaultGridDownload,
key: downloadLayoutKey), key: downloadLayoutKey),
]), ]),
Divider(height: 1), Divider(height: 1),
], ],
), ),
)), )),
); );
} }
} }

View File

@ -1,122 +1,122 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../util/custom_widget.dart'; import '../util/custom_widget.dart';
import '../util/extension_helper.dart'; import '../util/extension_helper.dart';
import 'licenses.dart'; import 'licenses.dart';
class Libries extends StatelessWidget { class Libries extends StatelessWidget {
_launchUrl(String url) async { _launchUrl(String url) async {
if (await canLaunch(url)) { if (await canLaunch(url)) {
await launch(url); await launch(url);
} else { } else {
throw 'Could not launch $url'; throw 'Could not launch $url';
} }
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>( return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle( value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness, statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: Theme.of(context).primaryColor, systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness: systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness, Theme.of(context).accentColorBrightness,
), ),
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(context.s.settingsLibraries), title: Text(context.s.settingsLibraries),
leading: CustomBackButton(), leading: CustomBackButton(),
elevation: 0, elevation: 0,
backgroundColor: Theme.of(context).primaryColor, backgroundColor: Theme.of(context).primaryColor,
), ),
body: SafeArea( body: SafeArea(
child: Scrollbar( child: Scrollbar(
child: SingleChildScrollView( child: SingleChildScrollView(
scrollDirection: Axis.vertical, scrollDirection: Axis.vertical,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Padding( Padding(
padding: EdgeInsets.all(10.0), padding: EdgeInsets.all(10.0),
), ),
Container( Container(
height: 30.0, height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70), padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text('Google', child: Text('Google',
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodyText1 .bodyText1
.copyWith(color: Theme.of(context).accentColor)), .copyWith(color: Theme.of(context).accentColor)),
), ),
Column( Column(
children: google.map<Widget>( children: google.map<Widget>(
(e) { (e) {
return ListTile( return ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 80), contentPadding: EdgeInsets.symmetric(horizontal: 80),
onTap: () => _launchUrl(e.link), onTap: () => _launchUrl(e.link),
title: Text(e.name), title: Text(e.name),
subtitle: Text(e.license), subtitle: Text(e.license),
); );
}, },
).toList(), ).toList(),
), ),
Container( Container(
height: 30.0, height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70), padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text(context.s.fonts, child: Text(context.s.fonts,
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodyText1 .bodyText1
.copyWith(color: Theme.of(context).accentColor)), .copyWith(color: Theme.of(context).accentColor)),
), ),
Column( Column(
children: fonts.map<Widget>( children: fonts.map<Widget>(
(e) { (e) {
return ListTile( return ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 80), contentPadding: EdgeInsets.symmetric(horizontal: 80),
onTap: () => _launchUrl(e.link), onTap: () => _launchUrl(e.link),
title: Text(e.name), title: Text(e.name),
subtitle: Text(e.license), subtitle: Text(e.license),
); );
}, },
).toList(), ).toList(),
), ),
Container( Container(
height: 30.0, height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70), padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text(context.s.plugins, child: Text(context.s.plugins,
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodyText1 .bodyText1
.copyWith(color: Theme.of(context).accentColor)), .copyWith(color: Theme.of(context).accentColor)),
), ),
Container( Container(
child: Column( child: Column(
children: plugins.map<Widget>( children: plugins.map<Widget>(
(e) { (e) {
return ListTile( return ListTile(
onTap: () => _launchUrl(e.link), onTap: () => _launchUrl(e.link),
contentPadding: contentPadding:
EdgeInsets.symmetric(horizontal: 80), EdgeInsets.symmetric(horizontal: 80),
title: Text(e.name), title: Text(e.name),
subtitle: Text(e.license), subtitle: Text(e.license),
); );
}, },
).toList(), ).toList(),
), ),
), ),
], ],
), ),
), ),
), ),
), ),
), ),
); );
} }
} }

View File

@ -1,78 +1,78 @@
const String apacheLicense = "Apache License 2.0"; const String apacheLicense = "Apache License 2.0";
const String mit = "MIT License"; const String mit = "MIT License";
const String bsd = "BSD 3-Clause"; const String bsd = "BSD 3-Clause";
const String gpl = "GPL 3.0"; const String gpl = "GPL 3.0";
const String font = "Open Font License"; const String font = "Open Font License";
class Libries { class Libries {
String name; String name;
String license; String license;
String link; String link;
Libries(this.name, this.license, this.link); Libries(this.name, this.license, this.link);
} }
List<Libries> google = [ List<Libries> google = [
Libries('Android X', apacheLicense, Libries('Android X', apacheLicense,
'https://source.android.com/setup/start/licenses'), 'https://source.android.com/setup/start/licenses'),
Libries( Libries(
'Flutter', bsd, 'https://github.com/flutter/flutter/blob/master/LICENSE') 'Flutter', bsd, 'https://github.com/flutter/flutter/blob/master/LICENSE')
]; ];
List<Libries> fonts = [ List<Libries> fonts = [
Libries('Libre Baskerville', font, Libries('Libre Baskerville', font,
"https://fonts.google.com/specimen/Libre+Baskerville"), "https://fonts.google.com/specimen/Libre+Baskerville"),
Libries('Teko', font, "https://fonts.google.com/specimen/Teko"), Libries('Teko', font, "https://fonts.google.com/specimen/Teko"),
Libries('Martel', font, "https://fonts.google.com/specimen/Martel"), Libries('Martel', font, "https://fonts.google.com/specimen/Martel"),
Libries('Bitter', font, "https://fonts.google.com/specimen/Bitter") Libries('Bitter', font, "https://fonts.google.com/specimen/Bitter")
]; ];
List<Libries> plugins = [ List<Libries> plugins = [
Libries('webfeed', mit, 'https://pub.dev/packages/webfeed'), Libries('webfeed', mit, 'https://pub.dev/packages/webfeed'),
Libries('json_annotation', bsd, 'https://pub.dev/packages/json_annotation'), Libries('json_annotation', bsd, 'https://pub.dev/packages/json_annotation'),
Libries('sqflite', mit, 'https://pub.dev/packages/sqflite'), Libries('sqflite', mit, 'https://pub.dev/packages/sqflite'),
Libries('flutter_html', mit, 'https://pub.dev/packages/flutter_html'), Libries('flutter_html', mit, 'https://pub.dev/packages/flutter_html'),
Libries('path_provider', bsd, 'https://pub.dev/packages/path_provider'), Libries('path_provider', bsd, 'https://pub.dev/packages/path_provider'),
Libries('color_thief_flutter', mit, Libries('color_thief_flutter', mit,
'https://pub.dev/packages/color_thief_flutter'), 'https://pub.dev/packages/color_thief_flutter'),
Libries('provider', mit, 'https://pub.dev/packages/provider'), Libries('provider', mit, 'https://pub.dev/packages/provider'),
Libries( Libries(
'google_fonts', apacheLicense, 'https://pub.dev/packages/google_fonts'), 'google_fonts', apacheLicense, 'https://pub.dev/packages/google_fonts'),
Libries('dio', mit, 'https://pub.dev/packages/dio'), Libries('dio', mit, 'https://pub.dev/packages/dio'),
Libries('file_picker', mit, 'https://pub.dev/packages/file_picker'), Libries('file_picker', mit, 'https://pub.dev/packages/file_picker'),
Libries('xml', mit, 'https://pub.dev/packages/xml'), Libries('xml', mit, 'https://pub.dev/packages/xml'),
Libries('marquee', mit, 'https://pub.dev/packages/marquee'), Libries('marquee', mit, 'https://pub.dev/packages/marquee'),
Libries( Libries(
'flutter_downloader', bsd, 'https://pub.dev/packages/flutter_downloader'), 'flutter_downloader', bsd, 'https://pub.dev/packages/flutter_downloader'),
Libries( Libries(
'permission_handler', mit, 'https://pub.dev/packages/permission_handler'), 'permission_handler', mit, 'https://pub.dev/packages/permission_handler'),
Libries('fluttertoast', mit, 'https://pub.dev/packages/fluttertoast'), Libries('fluttertoast', mit, 'https://pub.dev/packages/fluttertoast'),
Libries('intl', bsd, 'https://pub.dev/packages/intl'), Libries('intl', bsd, 'https://pub.dev/packages/intl'),
Libries('url_launcher', bsd, 'https://pub.dev/packages/url_launcher'), Libries('url_launcher', bsd, 'https://pub.dev/packages/url_launcher'),
Libries('image', apacheLicense, 'https://pub.dev/packages/image'), Libries('image', apacheLicense, 'https://pub.dev/packages/image'),
Libries( Libries(
'shared_preferences', bsd, 'https://pub.dev/packages/shared_preferences'), 'shared_preferences', bsd, 'https://pub.dev/packages/shared_preferences'),
Libries('uuid', mit, 'https://pub.dev/packages/uuid'), Libries('uuid', mit, 'https://pub.dev/packages/uuid'),
Libries('tuple', bsd, 'https://pub.dev/packages/tuple'), Libries('tuple', bsd, 'https://pub.dev/packages/tuple'),
Libries('cached_network_image', mit, Libries('cached_network_image', mit,
'https://pub.dev/packages/cached_network_image'), 'https://pub.dev/packages/cached_network_image'),
Libries('workmanager', mit, 'https://pub.dev/packages/workmanager'), Libries('workmanager', mit, 'https://pub.dev/packages/workmanager'),
Libries('app_settings', mit, 'https://pub.dev/packages/app_settings'), Libries('app_settings', mit, 'https://pub.dev/packages/app_settings'),
Libries('fl_chart', bsd, 'https://pub.dev/packages/fl_chart'), Libries('fl_chart', bsd, 'https://pub.dev/packages/fl_chart'),
Libries('audio_service', mit, 'https://pub.dev/packages/audio_service'), Libries('audio_service', mit, 'https://pub.dev/packages/audio_service'),
Libries('just_audio', apacheLicense, 'https://pub.dev/packages/just_audio'), Libries('just_audio', apacheLicense, 'https://pub.dev/packages/just_audio'),
Libries('line_icons', gpl, 'https://pub.dev/packages/line_icons'), Libries('line_icons', gpl, 'https://pub.dev/packages/line_icons'),
Libries('flutter_file_dialog', bsd, Libries('flutter_file_dialog', bsd,
'https://pub.dev/packages/flutter_file_dialog'), 'https://pub.dev/packages/flutter_file_dialog'),
Libries('flutter_linkify', mit, 'https://pub.dev/packages/flutter_linkify'), Libries('flutter_linkify', mit, 'https://pub.dev/packages/flutter_linkify'),
Libries('extended_nested_scroll_view', mit, Libries('extended_nested_scroll_view', mit,
'https://pub.dev/packages/extended_nested_scroll_view'), 'https://pub.dev/packages/extended_nested_scroll_view'),
Libries('connectivity', bsd, 'https://pub.dev/packages/connectivity'), Libries('connectivity', bsd, 'https://pub.dev/packages/connectivity'),
Libries('Rxdart', apacheLicense, 'https://pub.dev/packages/rxdart'), Libries('Rxdart', apacheLicense, 'https://pub.dev/packages/rxdart'),
Libries('flutter_isolate', mit, 'https://pub.dev/packages/flutter_isolate'), Libries('flutter_isolate', mit, 'https://pub.dev/packages/flutter_isolate'),
Libries('auto_animated', mit, 'https://pub.dev/packages/auto_animated'), Libries('auto_animated', mit, 'https://pub.dev/packages/auto_animated'),
Libries('wc_flutter_share', apacheLicense, Libries('wc_flutter_share', apacheLicense,
'https://pub.dev/packages/wc_flutter_share'), 'https://pub.dev/packages/wc_flutter_share'),
Libries('flutter_time_picker_spinner', 'unknow', Libries('flutter_time_picker_spinner', 'unknow',
'https://pub.dev/packages/flutter_time_picker_spinner'), 'https://pub.dev/packages/flutter_time_picker_spinner'),
Libries('focused_menu', mit, 'https://pub.dev/packages/focused_menu') Libries('focused_menu', mit, 'https://pub.dev/packages/focused_menu')
]; ];

File diff suppressed because it is too large Load Diff

View File

@ -1,222 +1,222 @@
import 'package:flare_flutter/flare_actor.dart'; import 'package:flare_flutter/flare_actor.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:line_icons/line_icons.dart'; import 'package:line_icons/line_icons.dart';
import '../local_storage/key_value_storage.dart'; import '../local_storage/key_value_storage.dart';
import '../util/custom_widget.dart'; import '../util/custom_widget.dart';
import '../util/extension_helper.dart'; import '../util/extension_helper.dart';
class PopupMenuSetting extends StatefulWidget { class PopupMenuSetting extends StatefulWidget {
const PopupMenuSetting({Key key}) : super(key: key); const PopupMenuSetting({Key key}) : super(key: key);
@override @override
_PopupMenuSettingState createState() => _PopupMenuSettingState(); _PopupMenuSettingState createState() => _PopupMenuSettingState();
} }
class _PopupMenuSettingState extends State<PopupMenuSetting> { class _PopupMenuSettingState extends State<PopupMenuSetting> {
Future<List<int>> _getEpisodeMenu() async { Future<List<int>> _getEpisodeMenu() async {
var popupMenuStorage = KeyValueStorage(episodePopupMenuKey); var popupMenuStorage = KeyValueStorage(episodePopupMenuKey);
var list = await popupMenuStorage.getMenu(); var list = await popupMenuStorage.getMenu();
return list; return list;
} }
Future<bool> _getTapToOpenPopupMenu() async { Future<bool> _getTapToOpenPopupMenu() async {
var tapToOpenPopupMenuStorage = KeyValueStorage(tapToOpenPopupMenuKey); var tapToOpenPopupMenuStorage = KeyValueStorage(tapToOpenPopupMenuKey);
var boo = await tapToOpenPopupMenuStorage.getBool(defaultValue: false); var boo = await tapToOpenPopupMenuStorage.getBool(defaultValue: false);
return boo; return boo;
} }
_saveEpisodeMene(List<int> list) async { _saveEpisodeMene(List<int> list) async {
var popupMenuStorage = KeyValueStorage(episodePopupMenuKey); var popupMenuStorage = KeyValueStorage(episodePopupMenuKey);
await popupMenuStorage.saveMenu(list); await popupMenuStorage.saveMenu(list);
if (mounted) setState(() {}); if (mounted) setState(() {});
} }
_saveTapToOpenPopupMenu(bool boo) async { _saveTapToOpenPopupMenu(bool boo) async {
var tapToOpenPopupMenuStorage = KeyValueStorage(tapToOpenPopupMenuKey); var tapToOpenPopupMenuStorage = KeyValueStorage(tapToOpenPopupMenuKey);
await tapToOpenPopupMenuStorage.saveBool(boo); await tapToOpenPopupMenuStorage.saveBool(boo);
if (mounted) setState(() {}); if (mounted) setState(() {});
} }
Widget _popupMenuItem(List<int> menu, int e, Widget _popupMenuItem(List<int> menu, int e,
{Widget icon, {Widget icon,
String text, String text,
String description = '', String description = '',
bool enable = false}) { bool enable = false}) {
return Padding( return Padding(
key: ObjectKey(text), key: ObjectKey(text),
padding: EdgeInsets.only(left: 60.0, right: 20), padding: EdgeInsets.only(left: 60.0, right: 20),
child: ListTile( child: ListTile(
leading: icon, leading: icon,
title: Text(text), title: Text(text),
subtitle: Text(description), subtitle: Text(description),
onTap: e == 0 onTap: e == 0
? null ? null
: () { : () {
if (e >= 10) { if (e >= 10) {
var index = menu.indexOf(e); var index = menu.indexOf(e);
menu.remove(e); menu.remove(e);
menu.insert(index, e - 10); menu.insert(index, e - 10);
_saveEpisodeMene(menu); _saveEpisodeMene(menu);
} else if (e < 10) { } else if (e < 10) {
var index = menu.indexOf(e); var index = menu.indexOf(e);
menu.remove(e); menu.remove(e);
menu.insert(index, e + 10); menu.insert(index, e + 10);
_saveEpisodeMene(menu); _saveEpisodeMene(menu);
} }
}, },
trailing: Checkbox( trailing: Checkbox(
value: e < 10, value: e < 10,
onChanged: e == 0 onChanged: e == 0
? null ? null
: (boo) { : (boo) {
if (boo && e >= 10) { if (boo && e >= 10) {
var index = menu.indexOf(e); var index = menu.indexOf(e);
menu.remove(e); menu.remove(e);
menu.insert(index, e - 10); menu.insert(index, e - 10);
_saveEpisodeMene(menu); _saveEpisodeMene(menu);
} else if (e < 10) { } else if (e < 10) {
var index = menu.indexOf(e); var index = menu.indexOf(e);
menu.remove(e); menu.remove(e);
menu.insert(index, e + 10); menu.insert(index, e + 10);
_saveEpisodeMene(menu); _saveEpisodeMene(menu);
} }
})), })),
); );
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final s = context.s; final s = context.s;
return AnnotatedRegion<SystemUiOverlayStyle>( return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle( value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness, statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: context.primaryColor, systemNavigationBarColor: context.primaryColor,
systemNavigationBarIconBrightness: systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness, Theme.of(context).accentColorBrightness,
), ),
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
elevation: 0, elevation: 0,
leading: CustomBackButton(), leading: CustomBackButton(),
backgroundColor: context.primaryColor, backgroundColor: context.primaryColor,
), ),
body: Column( body: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Container( Container(
color: context.primaryColor, color: context.primaryColor,
height: 200, height: 200,
// color: Colors.red, // color: Colors.red,
child: FlareActor( child: FlareActor(
'assets/longtap.flr', 'assets/longtap.flr',
alignment: Alignment.center, alignment: Alignment.center,
animation: 'longtap', animation: 'longtap',
fit: BoxFit.cover, fit: BoxFit.cover,
)), )),
FutureBuilder<List<int>>( FutureBuilder<List<int>>(
future: _getEpisodeMenu(), future: _getEpisodeMenu(),
initialData: [0, 1, 12, 13, 14], initialData: [0, 1, 12, 13, 14],
builder: (context, snapshot) { builder: (context, snapshot) {
var menu = snapshot.data; var menu = snapshot.data;
return Expanded( return Expanded(
child: ListView( child: ListView(
shrinkWrap: true, shrinkWrap: true,
children: [ children: [
Padding( Padding(
padding: EdgeInsets.symmetric(vertical: 10), padding: EdgeInsets.symmetric(vertical: 10),
), ),
Container( Container(
height: 30.0, height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 80), padding: EdgeInsets.symmetric(horizontal: 80),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text(s.settingsPopupMenu, child: Text(s.settingsPopupMenu,
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodyText1 .bodyText1
.copyWith( .copyWith(
color: Theme.of(context).accentColor)), color: Theme.of(context).accentColor)),
), ),
FutureBuilder<bool>( FutureBuilder<bool>(
future: _getTapToOpenPopupMenu(), future: _getTapToOpenPopupMenu(),
initialData: false, initialData: false,
builder: (context, snapshot) => ListTile( builder: (context, snapshot) => ListTile(
contentPadding: EdgeInsets.only( contentPadding: EdgeInsets.only(
left: 80, top: 10, bottom: 10, right: 30), left: 80, top: 10, bottom: 10, right: 30),
onTap: () => onTap: () =>
_saveTapToOpenPopupMenu(!snapshot.data), _saveTapToOpenPopupMenu(!snapshot.data),
title: Text(s.settingsTapToOpenPopupMenu), title: Text(s.settingsTapToOpenPopupMenu),
subtitle: Text(s.settingsTapToOpenPopupMenuDes), subtitle: Text(s.settingsTapToOpenPopupMenuDes),
trailing: Transform.scale( trailing: Transform.scale(
scale: 0.9, scale: 0.9,
child: Switch( child: Switch(
value: snapshot.data, value: snapshot.data,
onChanged: _saveTapToOpenPopupMenu), onChanged: _saveTapToOpenPopupMenu),
), ),
), ),
), ),
...menu.map<Widget>((e) { ...menu.map<Widget>((e) {
var i = e % 10; var i = e % 10;
switch (i) { switch (i) {
case 0: case 0:
return _popupMenuItem(menu, e, return _popupMenuItem(menu, e,
icon: Icon( icon: Icon(
LineIcons.play_circle_solid, LineIcons.play_circle_solid,
color: context.accentColor, color: context.accentColor,
), ),
text: s.play, text: s.play,
description: s.popupMenuPlayDes); description: s.popupMenuPlayDes);
break; break;
case 1: case 1:
return _popupMenuItem(menu, e, return _popupMenuItem(menu, e,
icon: Icon( icon: Icon(
LineIcons.clock_solid, LineIcons.clock_solid,
color: Colors.cyan, color: Colors.cyan,
), ),
text: s.later, text: s.later,
description: s.popupMenuLaterDes); description: s.popupMenuLaterDes);
break; break;
case 2: case 2:
return _popupMenuItem(menu, e, return _popupMenuItem(menu, e,
icon: Icon(LineIcons.heart, icon: Icon(LineIcons.heart,
color: Colors.red, size: 21), color: Colors.red, size: 21),
text: s.like, text: s.like,
description: s.popupMenuLikeDes); description: s.popupMenuLikeDes);
break; break;
case 3: case 3:
return _popupMenuItem(menu, e, return _popupMenuItem(menu, e,
icon: SizedBox( icon: SizedBox(
width: 23, width: 23,
height: 23, height: 23,
child: CustomPaint( child: CustomPaint(
painter: ListenedAllPainter( painter: ListenedAllPainter(
Colors.blue, Colors.blue,
stroke: 1.5)), stroke: 1.5)),
), ),
text: s.markListened, text: s.markListened,
description: s.popupMenuMarkDes); description: s.popupMenuMarkDes);
break; break;
case 4: case 4:
return _popupMenuItem(menu, e, return _popupMenuItem(menu, e,
icon: Icon( icon: Icon(
LineIcons.download_solid, LineIcons.download_solid,
color: Colors.green, color: Colors.green,
), ),
text: s.download, text: s.download,
description: s.popupMenuDownloadDes); description: s.popupMenuDownloadDes);
break; break;
default: default:
return Text('Text'); return Text('Text');
break; break;
} }
}).toList(), }).toList(),
], ],
), ),
); );
}), }),
], ],
)), )),
); );
} }
} }

View File

@ -1,265 +1,265 @@
import 'package:feature_discovery/feature_discovery.dart'; import 'package:feature_discovery/feature_discovery.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:line_icons/line_icons.dart'; import 'package:line_icons/line_icons.dart';
import '../home/home.dart'; import '../home/home.dart';
import '../intro_slider/app_intro.dart'; import '../intro_slider/app_intro.dart';
import '../podcasts/podcast_manage.dart'; import '../podcasts/podcast_manage.dart';
import '../util/custom_widget.dart'; import '../util/custom_widget.dart';
import '../util/extension_helper.dart'; import '../util/extension_helper.dart';
import '../util/general_dialog.dart'; import '../util/general_dialog.dart';
import 'data_backup.dart'; import 'data_backup.dart';
import 'history.dart'; import 'history.dart';
import 'languages.dart'; import 'languages.dart';
import 'layouts.dart'; import 'layouts.dart';
import 'libries.dart'; import 'libries.dart';
import 'play_setting.dart'; import 'play_setting.dart';
import 'storage.dart'; import 'storage.dart';
import 'syncing.dart'; import 'syncing.dart';
import 'theme.dart'; import 'theme.dart';
class Settings extends StatefulWidget { class Settings extends StatefulWidget {
@override @override
_SettingsState createState() => _SettingsState(); _SettingsState createState() => _SettingsState();
} }
class _SettingsState extends State<Settings> { class _SettingsState extends State<Settings> {
Widget _feedbackItem(IconData icon, String name, String url) => ListTile( Widget _feedbackItem(IconData icon, String name, String url) => ListTile(
onTap: () { onTap: () {
url.launchUrl; url.launchUrl;
Navigator.pop(context); Navigator.pop(context);
}, },
leading: Icon( leading: Icon(
icon, icon,
size: 20, size: 20,
), ),
title: Text( title: Text(
name, name,
maxLines: 2, maxLines: 2,
), ),
); );
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final s = context.s; final s = context.s;
return AnnotatedRegion<SystemUiOverlayStyle>( return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle( value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness, statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: Theme.of(context).primaryColor, systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness: systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness, Theme.of(context).accentColorBrightness,
), ),
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(s.settings), title: Text(s.settings),
leading: CustomBackButton(), leading: CustomBackButton(),
elevation: 0, elevation: 0,
backgroundColor: context.primaryColor, backgroundColor: context.primaryColor,
), ),
body: SafeArea( body: SafeArea(
child: SingleChildScrollView( child: SingleChildScrollView(
scrollDirection: Axis.vertical, scrollDirection: Axis.vertical,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Padding( Padding(
padding: EdgeInsets.all(10.0), padding: EdgeInsets.all(10.0),
), ),
Container( Container(
height: 30.0, height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70), padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text(s.settingsPrefrence, child: Text(s.settingsPrefrence,
style: context.textTheme.bodyText1 style: context.textTheme.bodyText1
.copyWith(color: context.accentColor)), .copyWith(color: context.accentColor)),
), ),
ListTile( ListTile(
onTap: () => Navigator.push(context, onTap: () => Navigator.push(context,
MaterialPageRoute(builder: (context) => ThemeSetting())), MaterialPageRoute(builder: (context) => ThemeSetting())),
contentPadding: EdgeInsets.symmetric(horizontal: 25.0), contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
leading: leading:
Icon(LineIcons.adjust_solid, color: context.accentColor), Icon(LineIcons.adjust_solid, color: context.accentColor),
title: Text(s.settingsAppearance), title: Text(s.settingsAppearance),
subtitle: Text(s.settingsAppearanceDes), subtitle: Text(s.settingsAppearanceDes),
), ),
Divider(height: 1), Divider(height: 1),
ListTile( ListTile(
onTap: () => Navigator.push(context, onTap: () => Navigator.push(context,
MaterialPageRoute(builder: (context) => LayoutSetting())), MaterialPageRoute(builder: (context) => LayoutSetting())),
contentPadding: EdgeInsets.symmetric(horizontal: 25.0), contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(LineIcons.stop_circle_solid, leading: Icon(LineIcons.stop_circle_solid,
color: Colors.blueAccent), color: Colors.blueAccent),
title: Text(s.settingsLayout), title: Text(s.settingsLayout),
subtitle: Text(s.settingsLayoutDes), subtitle: Text(s.settingsLayoutDes),
), ),
Divider(height: 1), Divider(height: 1),
ListTile( ListTile(
onTap: () => Navigator.push(context, onTap: () => Navigator.push(context,
MaterialPageRoute(builder: (context) => PlaySetting())), MaterialPageRoute(builder: (context) => PlaySetting())),
contentPadding: EdgeInsets.symmetric(horizontal: 25.0), contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(LineIcons.play_circle, color: Colors.redAccent), leading: Icon(LineIcons.play_circle, color: Colors.redAccent),
title: Text(s.play), title: Text(s.play),
subtitle: Text(s.settingsPlayDes), subtitle: Text(s.settingsPlayDes),
), ),
Divider(height: 1), Divider(height: 1),
ListTile( ListTile(
onTap: () => Navigator.push( onTap: () => Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => SyncingSetting())), builder: (context) => SyncingSetting())),
contentPadding: EdgeInsets.symmetric(horizontal: 25.0), contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(LineIcons.cloud_download_alt_solid, leading: Icon(LineIcons.cloud_download_alt_solid,
color: Colors.yellow[700]), color: Colors.yellow[700]),
title: Text(s.settingsSyncing), title: Text(s.settingsSyncing),
subtitle: Text(s.settingsSyncingDes)), subtitle: Text(s.settingsSyncingDes)),
Divider(height: 1), Divider(height: 1),
ListTile( ListTile(
onTap: () => Navigator.push( onTap: () => Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => StorageSetting())), builder: (context) => StorageSetting())),
contentPadding: EdgeInsets.symmetric(horizontal: 25.0), contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(LineIcons.save, color: Colors.green[700]), leading: Icon(LineIcons.save, color: Colors.green[700]),
title: Text(s.settingStorage), title: Text(s.settingStorage),
subtitle: Text(s.settingsStorageDes), subtitle: Text(s.settingsStorageDes),
), ),
Divider(height: 1), Divider(height: 1),
ListTile( ListTile(
onTap: () => Navigator.push(context, onTap: () => Navigator.push(context,
MaterialPageRoute(builder: (context) => PlayedHistory())), MaterialPageRoute(builder: (context) => PlayedHistory())),
contentPadding: EdgeInsets.symmetric(horizontal: 25.0), contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(Icons.update, color: Colors.indigo[700]), leading: Icon(Icons.update, color: Colors.indigo[700]),
title: Text(s.settingsHistory), title: Text(s.settingsHistory),
subtitle: Text(s.settingsHistoryDes), subtitle: Text(s.settingsHistoryDes),
), ),
Divider(height: 1), Divider(height: 1),
ListTile( ListTile(
onTap: () => generalSheet(context, onTap: () => generalSheet(context,
title: s.settingsLanguages, child: LanguagesSetting()) title: s.settingsLanguages, child: LanguagesSetting())
.then((value) => setState(() {})), .then((value) => setState(() {})),
contentPadding: EdgeInsets.symmetric(horizontal: 25.0), contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(LineIcons.language_solid, leading: Icon(LineIcons.language_solid,
color: Colors.purpleAccent), color: Colors.purpleAccent),
title: Text(s.settingsLanguages), title: Text(s.settingsLanguages),
subtitle: Text(s.settingsLanguagesDes), subtitle: Text(s.settingsLanguagesDes),
), ),
Divider(height: 1), Divider(height: 1),
ListTile( ListTile(
onTap: () { onTap: () {
//_exportOmpl(context); //_exportOmpl(context);
Navigator.push(context, Navigator.push(context,
MaterialPageRoute(builder: (context) => DataBackup())); MaterialPageRoute(builder: (context) => DataBackup()));
}, },
contentPadding: EdgeInsets.symmetric(horizontal: 25.0), contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(LineIcons.file_code_solid, leading: Icon(LineIcons.file_code_solid,
color: Colors.lightGreen[700]), color: Colors.lightGreen[700]),
title: Text(s.settingsBackup), title: Text(s.settingsBackup),
subtitle: Text(s.settingsBackupDes), subtitle: Text(s.settingsBackupDes),
), ),
Divider(height: 1), Divider(height: 1),
Padding( Padding(
padding: EdgeInsets.all(10.0), padding: EdgeInsets.all(10.0),
), ),
Container( Container(
height: 30.0, height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70), padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text(s.settingsInfo, child: Text(s.settingsInfo,
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodyText1 .bodyText1
.copyWith(color: Theme.of(context).accentColor)), .copyWith(color: Theme.of(context).accentColor)),
), ),
ListTile( ListTile(
onTap: () => Navigator.push(context, onTap: () => Navigator.push(context,
MaterialPageRoute(builder: (context) => Libries())), MaterialPageRoute(builder: (context) => Libries())),
contentPadding: EdgeInsets.symmetric(horizontal: 25.0), contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(LineIcons.book_open_solid, leading: Icon(LineIcons.book_open_solid,
color: Colors.purple[700]), color: Colors.purple[700]),
title: Text(s.settingsLibraries), title: Text(s.settingsLibraries),
subtitle: Text(s.settingsLibrariesDes), subtitle: Text(s.settingsLibrariesDes),
), ),
Divider(height: 1), Divider(height: 1),
ListTile( ListTile(
onTap: () => generalSheet( onTap: () => generalSheet(
context, context,
title: s.settingsFeedback, title: s.settingsFeedback,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
_feedbackItem(LineIcons.github, s.feedbackGithub, _feedbackItem(LineIcons.github, s.feedbackGithub,
'https://github.com/stonega/tsacdop/issues'), 'https://github.com/stonega/tsacdop/issues'),
Divider(height: 1), Divider(height: 1),
_feedbackItem(LineIcons.telegram, s.feedbackTelegram, _feedbackItem(LineIcons.telegram, s.feedbackTelegram,
'https://t.me/joinchat/Bk3LkRpTHy40QYC78PK7Qg'), 'https://t.me/joinchat/Bk3LkRpTHy40QYC78PK7Qg'),
Divider(height: 1), Divider(height: 1),
_feedbackItem( _feedbackItem(
LineIcons.envelope_open_text_solid, LineIcons.envelope_open_text_solid,
s.feedbackEmail, s.feedbackEmail,
'mailto:<tsacdop.app@gmail.com>?subject=Tsacdop Feedback'), 'mailto:<tsacdop.app@gmail.com>?subject=Tsacdop Feedback'),
Divider(height: 1), Divider(height: 1),
_feedbackItem(LineIcons.google_play, s.feedbackPlay, _feedbackItem(LineIcons.google_play, s.feedbackPlay,
'https://play.google.com/store/apps/details?id=com.stonegate.tsacdop'), 'https://play.google.com/store/apps/details?id=com.stonegate.tsacdop'),
Divider(height: 1), Divider(height: 1),
], ],
), ),
), ),
contentPadding: EdgeInsets.symmetric(horizontal: 25.0), contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
leading: Icon(LineIcons.bug_solid, color: Colors.pink[700]), leading: Icon(LineIcons.bug_solid, color: Colors.pink[700]),
title: Text(s.settingsFeedback), title: Text(s.settingsFeedback),
subtitle: Text(s.settingsFeedbackDes), subtitle: Text(s.settingsFeedbackDes),
), ),
Divider( Divider(
height: 2, height: 2,
), ),
ListTile( ListTile(
onTap: () { onTap: () {
FeatureDiscovery.clearPreferences(context, const <String>{ FeatureDiscovery.clearPreferences(context, const <String>{
addFeature, addFeature,
menuFeature, menuFeature,
playlistFeature, playlistFeature,
groupsFeature, groupsFeature,
addGroupFeature, addGroupFeature,
configureGroup, configureGroup,
configurePodcast, configurePodcast,
podcastFeature podcastFeature
}); });
Fluttertoast.showToast( Fluttertoast.showToast(
msg: s.toastDiscovery, msg: s.toastDiscovery,
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.BOTTOM,
); );
}, },
contentPadding: EdgeInsets.symmetric(horizontal: 25.0), contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
leading: leading:
Icon(LineIcons.capsules_solid, color: Colors.pinkAccent), Icon(LineIcons.capsules_solid, color: Colors.pinkAccent),
title: Text(s.settingsDiscovery), title: Text(s.settingsDiscovery),
), ),
Divider(height: 1), Divider(height: 1),
ListTile( ListTile(
onTap: () => Navigator.push( onTap: () => Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => builder: (context) =>
SlideIntro(goto: Goto.settings))), SlideIntro(goto: Goto.settings))),
contentPadding: EdgeInsets.symmetric(horizontal: 25.0), contentPadding: EdgeInsets.symmetric(horizontal: 25.0),
leading: leading:
Icon(LineIcons.columns_solid, color: Colors.blueGrey), Icon(LineIcons.columns_solid, color: Colors.blueGrey),
title: Text(s.settingsAppIntro), title: Text(s.settingsAppIntro),
), ),
Divider(height: 1), Divider(height: 1),
Padding( Padding(
padding: EdgeInsets.all(10.0), padding: EdgeInsets.all(10.0),
), ),
], ],
), ),
), ),
), ),
), ),
); );
} }
} }

View File

@ -1,348 +1,348 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tsacdop/util/general_dialog.dart';
import '../local_storage/key_value_storage.dart';
import '../local_storage/key_value_storage.dart'; import '../settings/downloads_manage.dart';
import '../settings/downloads_manage.dart'; import '../state/setting_state.dart';
import '../state/setting_state.dart'; import '../util/custom_dropdown.dart';
import '../util/custom_dropdown.dart'; import '../util/custom_widget.dart';
import '../util/custom_widget.dart'; import '../util/extension_helper.dart';
import '../util/extension_helper.dart'; import '../util/general_dialog.dart';
class StorageSetting extends StatefulWidget { class StorageSetting extends StatefulWidget {
@override @override
_StorageSettingState createState() => _StorageSettingState(); _StorageSettingState createState() => _StorageSettingState();
} }
class _StorageSettingState extends State<StorageSetting> class _StorageSettingState extends State<StorageSetting>
with SingleTickerProviderStateMixin { with SingleTickerProviderStateMixin {
final KeyValueStorage cacheStorage = KeyValueStorage(cacheMaxKey); final KeyValueStorage cacheStorage = KeyValueStorage(cacheMaxKey);
AnimationController _controller; AnimationController _controller;
Animation<double> _animation; Animation<double> _animation;
List<String> _dirs; List<String> _dirs;
Future<void> _getCacheMax() async { Future<void> _getCacheMax() async {
var cache = var cache =
await cacheStorage.getInt(defaultValue: (200 * 1024 * 1024).toInt()); await cacheStorage.getInt(defaultValue: (200 * 1024 * 1024).toInt());
if (cache == 0) { if (cache == 0) {
await cacheStorage.saveInt((200 * 1024 * 1024).toInt()); await cacheStorage.saveInt((200 * 1024 * 1024).toInt());
cache = 200 * 1024 * 1024; cache = 200 * 1024 * 1024;
} }
var value = cache ~/ (1024 * 1024); var value = cache ~/ (1024 * 1024);
if (value > 100) { if (value > 100) {
_controller = AnimationController( _controller = AnimationController(
vsync: this, duration: Duration(milliseconds: value * 2)); vsync: this, duration: Duration(milliseconds: value * 2));
_animation = Tween<double>(begin: 100, end: value.toDouble()).animate( _animation = Tween<double>(begin: 100, end: value.toDouble()).animate(
CurvedAnimation(curve: Curves.easeOutQuart, parent: _controller)) CurvedAnimation(curve: Curves.easeOutQuart, parent: _controller))
..addListener(() { ..addListener(() {
setState(() => _value = _animation.value); setState(() => _value = _animation.value);
}); });
_controller.forward(); _controller.forward();
} }
} }
Future<bool> _getAutoDownloadNetwork() async { Future<bool> _getAutoDownloadNetwork() async {
var storage = KeyValueStorage(autoDownloadNetworkKey); var storage = KeyValueStorage(autoDownloadNetworkKey);
var value = await storage.getBool(defaultValue: false); var value = await storage.getBool(defaultValue: false);
return value; return value;
} }
Future<int> _getAutoDeleteDays() async { Future<int> _getAutoDeleteDays() async {
var storage = KeyValueStorage(autoDeleteKey); var storage = KeyValueStorage(autoDeleteKey);
var days = await storage.getInt(); var days = await storage.getInt();
if (days == 0) { if (days == 0) {
storage.saveInt(30); storage.saveInt(30);
return 30; return 30;
} }
return days; return days;
} }
Future<int> _getDownloadPasition() async { Future<int> _getDownloadPasition() async {
final storage = KeyValueStorage(downloadPositionKey); final storage = KeyValueStorage(downloadPositionKey);
final index = await storage.getInt(); final index = await storage.getInt();
final externalDirs = await getExternalStorageDirectories(); final externalDirs = await getExternalStorageDirectories();
_dirs = [for (var dir in externalDirs) dir.path]; _dirs = [for (var dir in externalDirs) dir.path];
return index; return index;
} }
Future<bool> _getDelteAfterPlayed() async { Future<bool> _getDelteAfterPlayed() async {
final storage = KeyValueStorage(deleteAfterPlayedKey); final storage = KeyValueStorage(deleteAfterPlayedKey);
return await storage.getBool(defaultValue: false); return await storage.getBool(defaultValue: false);
} }
Future<void> _setAutoDeleteDays(int days) async { Future<void> _setAutoDeleteDays(int days) async {
var storage = KeyValueStorage(autoDeleteKey); var storage = KeyValueStorage(autoDeleteKey);
await storage.saveInt(days); await storage.saveInt(days);
setState(() {}); setState(() {});
} }
Future<void> _setAudtDownloadNetwork(bool boo) async { Future<void> _setAudtDownloadNetwork(bool boo) async {
var storage = KeyValueStorage(autoDownloadNetworkKey); var storage = KeyValueStorage(autoDownloadNetworkKey);
await storage.saveBool(boo); await storage.saveBool(boo);
} }
Future<void> _setDownloadPosition(int index) async { Future<void> _setDownloadPosition(int index) async {
final storage = KeyValueStorage(downloadPositionKey); final storage = KeyValueStorage(downloadPositionKey);
await storage.saveInt(index); await storage.saveInt(index);
} }
Future<void> _setDeleteAfterPlayed(bool boo) async { Future<void> _setDeleteAfterPlayed(bool boo) async {
final storage = KeyValueStorage(deleteAfterPlayedKey); final storage = KeyValueStorage(deleteAfterPlayedKey);
await storage.saveBool(boo); await storage.saveBool(boo);
} }
double _value; double _value;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_value = 100; _value = 100;
_getCacheMax(); _getCacheMax();
} }
@override @override
void dispose() { void dispose() {
_controller?.dispose(); _controller?.dispose();
super.dispose(); super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final s = context.s; final s = context.s;
var settings = Provider.of<SettingState>(context, listen: false); var settings = Provider.of<SettingState>(context, listen: false);
return AnnotatedRegion<SystemUiOverlayStyle>( return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle( value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness, statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: Theme.of(context).primaryColor, systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness: systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness, Theme.of(context).accentColorBrightness,
), ),
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(s.settingStorage), title: Text(s.settingStorage),
leading: CustomBackButton(), leading: CustomBackButton(),
elevation: 0, elevation: 0,
backgroundColor: Theme.of(context).primaryColor, backgroundColor: Theme.of(context).primaryColor,
), ),
body: SafeArea( body: SafeArea(
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Padding( Padding(
padding: EdgeInsets.all(10.0), padding: EdgeInsets.all(10.0),
), ),
Container( Container(
height: 30.0, height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70), padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text(s.network, child: Text(s.network,
style: context.textTheme.bodyText1 style: context.textTheme.bodyText1
.copyWith(color: context.accentColor)), .copyWith(color: context.accentColor)),
), ),
Selector<SettingState, bool>( Selector<SettingState, bool>(
selector: (_, settings) => settings.downloadUsingData, selector: (_, settings) => settings.downloadUsingData,
builder: (_, data, __) { builder: (_, data, __) {
return ListTile( return ListTile(
onTap: () => settings.downloadUsingData = !data, onTap: () => settings.downloadUsingData = !data,
contentPadding: EdgeInsets.only( contentPadding: EdgeInsets.only(
left: 70.0, right: 25, bottom: 10, top: 10), left: 70.0, right: 25, bottom: 10, top: 10),
title: Text(s.settingsNetworkCellular), title: Text(s.settingsNetworkCellular),
subtitle: Text(s.settingsNetworkCellularDes), subtitle: Text(s.settingsNetworkCellularDes),
trailing: Transform.scale( trailing: Transform.scale(
scale: 0.9, scale: 0.9,
child: Switch( child: Switch(
value: data, value: data,
onChanged: (value) => onChanged: (value) =>
settings.downloadUsingData = value, settings.downloadUsingData = value,
), ),
), ),
); );
}, },
), ),
FutureBuilder<bool>( FutureBuilder<bool>(
future: _getAutoDownloadNetwork(), future: _getAutoDownloadNetwork(),
initialData: false, initialData: false,
builder: (context, snapshot) { builder: (context, snapshot) {
return ListTile( return ListTile(
onTap: () async { onTap: () async {
_setAudtDownloadNetwork(!snapshot.data); _setAudtDownloadNetwork(!snapshot.data);
setState(() {}); setState(() {});
}, },
contentPadding: EdgeInsets.only( contentPadding: EdgeInsets.only(
left: 70.0, right: 25, bottom: 10, top: 10), left: 70.0, right: 25, bottom: 10, top: 10),
title: Text(s.settingsNetworkCellularAuto), title: Text(s.settingsNetworkCellularAuto),
subtitle: Text(s.settingsNetworkCellularAutoDes), subtitle: Text(s.settingsNetworkCellularAutoDes),
trailing: Transform.scale( trailing: Transform.scale(
scale: 0.9, scale: 0.9,
child: Switch( child: Switch(
value: snapshot.data, value: snapshot.data,
onChanged: (value) async { onChanged: (value) async {
await _setAudtDownloadNetwork(value); await _setAudtDownloadNetwork(value);
setState(() {}); setState(() {});
}, },
), ),
), ),
); );
}), }),
Divider(height: 1), Divider(height: 1),
Padding( Padding(
padding: EdgeInsets.all(10.0), padding: EdgeInsets.all(10.0),
), ),
Container( Container(
height: 30.0, height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70), padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text(s.settingStorage, child: Text(s.settingStorage,
style: context.textTheme.bodyText1 style: context.textTheme.bodyText1
.copyWith(color: context.accentColor)), .copyWith(color: context.accentColor)),
), ),
ListTile( ListTile(
onTap: () => Navigator.push( onTap: () => Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => DownloadsManage())), builder: (context) => DownloadsManage())),
contentPadding: EdgeInsets.symmetric(horizontal: 70.0), contentPadding: EdgeInsets.symmetric(horizontal: 70.0),
title: Text(s.download), title: Text(s.download),
subtitle: Text(s.settingsManageDownloadDes), subtitle: Text(s.settingsManageDownloadDes),
), ),
FutureBuilder<int>( FutureBuilder<int>(
future: _getDownloadPasition(), future: _getDownloadPasition(),
initialData: 0, initialData: 0,
builder: (context, snapshot) { builder: (context, snapshot) {
return ListTile( return ListTile(
contentPadding: EdgeInsets.fromLTRB(70, 10, 20, 10), contentPadding: EdgeInsets.fromLTRB(70, 10, 20, 10),
title: Text(s.settingsDownloadPosition), title: Text(s.settingsDownloadPosition),
subtitle: Text( subtitle: Text(
_dirs == null ? '' : _dirs[snapshot.data], _dirs == null ? '' : _dirs[snapshot.data],
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis), overflow: TextOverflow.ellipsis),
onTap: () => generalSheet( onTap: () => generalSheet(
context, context,
title: s.settingsDownloadPosition, title: s.settingsDownloadPosition,
child: Column(children: [ child: Column(children: [
SizedBox( SizedBox(
height: 10, height: 10,
), ),
for (var dir in _dirs) for (var dir in _dirs)
ListTile( ListTile(
title: Text(dir), title: Text(dir),
onTap: () => onTap: () =>
_setDownloadPosition(_dirs.indexOf(dir)), _setDownloadPosition(_dirs.indexOf(dir)),
trailing: Radio<int>( trailing: Radio<int>(
value: _dirs.indexOf(dir), value: _dirs.indexOf(dir),
groupValue: snapshot.data, groupValue: snapshot.data,
onChanged: _setDownloadPosition), onChanged: _setDownloadPosition),
), ),
SizedBox( SizedBox(
height: 30, height: 30,
) )
]), ]),
), ),
); );
}), }),
FutureBuilder<int>( FutureBuilder<int>(
future: _getAutoDeleteDays(), future: _getAutoDeleteDays(),
initialData: 30, initialData: 30,
builder: (context, snapshot) { builder: (context, snapshot) {
return ListTile( return ListTile(
contentPadding: EdgeInsets.only(left: 70.0, right: 20), contentPadding: EdgeInsets.only(left: 70.0, right: 20),
title: Text(s.settingsAutoDelete), title: Text(s.settingsAutoDelete),
subtitle: Text(s.settingsAutoDeleteDes), subtitle: Text(s.settingsAutoDeleteDes),
trailing: MyDropdownButton( trailing: MyDropdownButton(
hint: snapshot.data == -1 hint: snapshot.data == -1
? Text(s.daysCount(0)) ? Text(s.daysCount(0))
: Text(s.daysCount(snapshot.data)), : Text(s.daysCount(snapshot.data)),
underline: Center(), underline: Center(),
elevation: 1, elevation: 1,
value: snapshot.data, value: snapshot.data,
onChanged: (value) async { onChanged: (value) async {
await _setAutoDeleteDays(value); await _setAutoDeleteDays(value);
}, },
items: <int>[-1, 5, 10, 15, 30] items: <int>[-1, 5, 10, 15, 30]
.map<DropdownMenuItem<int>>((e) { .map<DropdownMenuItem<int>>((e) {
return DropdownMenuItem<int>( return DropdownMenuItem<int>(
value: e, value: e,
child: e == -1 child: e == -1
? Text(s.daysCount(0)) ? Text(s.daysCount(0))
: Text(s.daysCount(e))); : Text(s.daysCount(e)));
}).toList()), }).toList()),
); );
}, },
), ),
FutureBuilder<bool>( FutureBuilder<bool>(
future: _getDelteAfterPlayed(), future: _getDelteAfterPlayed(),
initialData: false, initialData: false,
builder: (context, snapshot) { builder: (context, snapshot) {
return ListTile( return ListTile(
onTap: () async { onTap: () async {
_setDeleteAfterPlayed(snapshot.data); _setDeleteAfterPlayed(snapshot.data);
setState(() {}); setState(() {});
}, },
contentPadding: EdgeInsets.only(left: 70.0, right: 25), contentPadding: EdgeInsets.only(left: 70.0, right: 25),
title: Text('Delete download after played'), title: Text('Delete download after played'),
subtitle: Text('Delete after played'), subtitle: Text('Delete after played'),
trailing: Transform.scale( trailing: Transform.scale(
scale: 0.9, scale: 0.9,
child: Switch( child: Switch(
value: snapshot.data, value: snapshot.data,
onChanged: (value) async { onChanged: (value) async {
await _setDeleteAfterPlayed(value); await _setDeleteAfterPlayed(value);
setState(() {}); setState(() {});
}, },
), ),
), ),
); );
}), }),
ListTile( ListTile(
contentPadding: EdgeInsets.only(left: 70.0, right: 25), contentPadding: EdgeInsets.only(left: 70.0, right: 25),
// leading: Icon(Icons.colorize), // leading: Icon(Icons.colorize),
title: Text(s.settingsAudioCache), title: Text(s.settingsAudioCache),
subtitle: Text(s.settingsAudioCacheDes), subtitle: Text(s.settingsAudioCacheDes),
trailing: Text.rich(TextSpan( trailing: Text.rich(TextSpan(
text: '${(_value ~/ 100) * 100}', text: '${(_value ~/ 100) * 100}',
style: GoogleFonts.teko( style: GoogleFonts.teko(
textStyle: context.textTheme.headline6 textStyle: context.textTheme.headline6
.copyWith(color: context.accentColor)), .copyWith(color: context.accentColor)),
children: [ children: [
TextSpan( TextSpan(
text: ' Mb', style: context.textTheme.subtitle2), text: ' Mb', style: context.textTheme.subtitle2),
])), ])),
), ),
Padding( Padding(
padding: padding:
EdgeInsets.only(left: 50.0, right: 20.0, bottom: 10.0), EdgeInsets.only(left: 50.0, right: 20.0, bottom: 10.0),
child: SliderTheme( child: SliderTheme(
data: Theme.of(context).sliderTheme.copyWith( data: Theme.of(context).sliderTheme.copyWith(
showValueIndicator: ShowValueIndicator.always, showValueIndicator: ShowValueIndicator.always,
trackHeight: 2, trackHeight: 2,
thumbShape: thumbShape:
RoundSliderThumbShape(enabledThumbRadius: 6)), RoundSliderThumbShape(enabledThumbRadius: 6)),
child: Slider( child: Slider(
label: '${_value ~/ 100 * 100} Mb', label: '${_value ~/ 100 * 100} Mb',
activeColor: context.accentColor, activeColor: context.accentColor,
inactiveColor: context.primaryColorDark, inactiveColor: context.primaryColorDark,
value: _value, value: _value,
min: 100, min: 100,
max: 1000, max: 1000,
divisions: 9, divisions: 9,
onChanged: (val) { onChanged: (val) {
setState(() { setState(() {
_value = val; _value = val;
}); });
cacheStorage.saveInt((val * 1024 * 1024).toInt()); cacheStorage.saveInt((val * 1024 * 1024).toInt());
}), }),
), ),
), ),
Divider(height: 1), Divider(height: 1),
], ],
), ),
), ),
), ),
), ),
); );
} }
} }

View File

@ -1,103 +1,103 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import '../state/setting_state.dart'; import '../state/setting_state.dart';
import '../util/custom_dropdown.dart'; import '../util/custom_dropdown.dart';
import '../util/custom_widget.dart'; import '../util/custom_widget.dart';
import '../util/extension_helper.dart'; import '../util/extension_helper.dart';
class SyncingSetting extends StatelessWidget { class SyncingSetting extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final s = context.s; final s = context.s;
var settings = Provider.of<SettingState>(context, listen: false); var settings = Provider.of<SettingState>(context, listen: false);
return AnnotatedRegion<SystemUiOverlayStyle>( return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle( value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness, statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: Theme.of(context).primaryColor, systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness: systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness, Theme.of(context).accentColorBrightness,
), ),
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(s.settingsSyncing), title: Text(s.settingsSyncing),
leading: CustomBackButton(), leading: CustomBackButton(),
elevation: 0, elevation: 0,
backgroundColor: Theme.of(context).primaryColor, backgroundColor: Theme.of(context).primaryColor,
), ),
body: SingleChildScrollView( body: SingleChildScrollView(
child: Selector<SettingState, Tuple2<bool, int>>( child: Selector<SettingState, Tuple2<bool, int>>(
selector: (_, settings) => selector: (_, settings) =>
Tuple2(settings.autoUpdate, settings.updateInterval), Tuple2(settings.autoUpdate, settings.updateInterval),
builder: (_, data, __) => Column( builder: (_, data, __) => Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Padding( Padding(
padding: const EdgeInsets.fromLTRB(70, 20, 70, 10), padding: const EdgeInsets.fromLTRB(70, 20, 70, 10),
child: Text(s.settingsSyncing, child: Text(s.settingsSyncing,
style: context.textTheme.bodyText1 style: context.textTheme.bodyText1
.copyWith(color: context.accentColor)), .copyWith(color: context.accentColor)),
), ),
ListTile( ListTile(
onTap: () { onTap: () {
if (settings.autoUpdate) { if (settings.autoUpdate) {
settings.autoUpdate = false; settings.autoUpdate = false;
settings.cancelWork(); settings.cancelWork();
} else { } else {
settings.autoUpdate = true; settings.autoUpdate = true;
settings.setWorkManager(data.item2); settings.setWorkManager(data.item2);
} }
}, },
contentPadding: contentPadding:
const EdgeInsets.only(left: 70.0, right: 20, bottom: 10), const EdgeInsets.only(left: 70.0, right: 20, bottom: 10),
title: Text(s.settingsEnableSyncing), title: Text(s.settingsEnableSyncing),
subtitle: Text(s.settingsEnableSyncingDes), subtitle: Text(s.settingsEnableSyncingDes),
trailing: Transform.scale( trailing: Transform.scale(
scale: 0.9, scale: 0.9,
child: Switch( child: Switch(
value: data.item1, value: data.item1,
onChanged: (boo) async { onChanged: (boo) async {
settings.autoUpdate = boo; settings.autoUpdate = boo;
if (boo) { if (boo) {
settings.setWorkManager(data.item2); settings.setWorkManager(data.item2);
} else { } else {
settings.cancelWork(); settings.cancelWork();
} }
}), }),
), ),
), ),
ListTile( ListTile(
contentPadding: const EdgeInsets.only(left: 70.0, right: 20), contentPadding: const EdgeInsets.only(left: 70.0, right: 20),
title: Text(s.settingsUpdateInterval), title: Text(s.settingsUpdateInterval),
subtitle: Text(s.settingsUpdateIntervalDes), subtitle: Text(s.settingsUpdateIntervalDes),
trailing: MyDropdownButton( trailing: MyDropdownButton(
hint: Text(s.hoursCount(data.item2)), hint: Text(s.hoursCount(data.item2)),
underline: Center(), underline: Center(),
elevation: 1, elevation: 1,
displayItemCount: 5, displayItemCount: 5,
value: data.item2, value: data.item2,
onChanged: data.item1 onChanged: data.item1
? (value) async { ? (value) async {
await settings.cancelWork(); await settings.cancelWork();
settings.setWorkManager(value); settings.setWorkManager(value);
} }
: null, : null,
items: <int>[1, 2, 4, 8, 24, 48] items: <int>[1, 2, 4, 8, 24, 48]
.map<DropdownMenuItem<int>>((e) { .map<DropdownMenuItem<int>>((e) {
return DropdownMenuItem<int>( return DropdownMenuItem<int>(
value: e, child: Text(s.hoursCount(e))); value: e, child: Text(s.hoursCount(e)));
}).toList()), }).toList()),
), ),
Divider(height: 1), Divider(height: 1),
], ],
), ),
), ),
), ),
), ),
); );
} }
} }

View File

@ -1,404 +1,404 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../state/setting_state.dart'; import '../state/setting_state.dart';
import '../util/custom_widget.dart'; import '../util/custom_widget.dart';
import '../util/extension_helper.dart'; import '../util/extension_helper.dart';
import '../util/general_dialog.dart'; import '../util/general_dialog.dart';
class ThemeSetting extends StatelessWidget { class ThemeSetting extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final s = context.s; final s = context.s;
var settings = Provider.of<SettingState>(context, listen: false); var settings = Provider.of<SettingState>(context, listen: false);
return AnnotatedRegion<SystemUiOverlayStyle>( return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle( value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness, statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: Theme.of(context).primaryColor, systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness: systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness, Theme.of(context).accentColorBrightness,
), ),
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(s.settingsAppearance), title: Text(s.settingsAppearance),
leading: CustomBackButton(), leading: CustomBackButton(),
elevation: 0, elevation: 0,
backgroundColor: Theme.of(context).primaryColor, backgroundColor: Theme.of(context).primaryColor,
), ),
body: Column( body: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Padding( Padding(
padding: EdgeInsets.all(10.0), padding: EdgeInsets.all(10.0),
), ),
Container( Container(
height: 30.0, height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70), padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text(s.settingsInterface, child: Text(s.settingsInterface,
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodyText1 .bodyText1
.copyWith(color: Theme.of(context).accentColor)), .copyWith(color: Theme.of(context).accentColor)),
), ),
ListTile( ListTile(
onTap: () => showGeneralDialog( onTap: () => showGeneralDialog(
context: context, context: context,
barrierDismissible: true, barrierDismissible: true,
barrierLabel: MaterialLocalizations.of(context) barrierLabel: MaterialLocalizations.of(context)
.modalBarrierDismissLabel, .modalBarrierDismissLabel,
barrierColor: Colors.black54, barrierColor: Colors.black54,
transitionDuration: const Duration(milliseconds: 200), transitionDuration: const Duration(milliseconds: 200),
pageBuilder: (context, animaiton, secondaryAnimation) => pageBuilder: (context, animaiton, secondaryAnimation) =>
AnnotatedRegion<SystemUiOverlayStyle>( AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle( value: SystemUiOverlayStyle(
statusBarIconBrightness: Brightness.light, statusBarIconBrightness: Brightness.light,
systemNavigationBarColor: systemNavigationBarColor:
Theme.of(context).brightness == Brightness.light Theme.of(context).brightness == Brightness.light
? Color.fromRGBO(113, 113, 113, 1) ? Color.fromRGBO(113, 113, 113, 1)
: Color.fromRGBO(15, 15, 15, 1), : Color.fromRGBO(15, 15, 15, 1),
), ),
child: AlertDialog( child: AlertDialog(
titlePadding: EdgeInsets.only( titlePadding: EdgeInsets.only(
top: 20, top: 20,
left: 40, left: 40,
right: context.width / 3, right: context.width / 3,
), ),
elevation: 1, elevation: 1,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: borderRadius:
BorderRadius.all(Radius.circular(10.0))), BorderRadius.all(Radius.circular(10.0))),
title: Text(s.settingsTheme), title: Text(s.settingsTheme),
content: SingleChildScrollView( content: SingleChildScrollView(
scrollDirection: Axis.vertical, scrollDirection: Axis.vertical,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[ children: <Widget>[
ClipRRect( ClipRRect(
borderRadius: borderRadius:
BorderRadius.all(Radius.circular(5)), BorderRadius.all(Radius.circular(5)),
child: Material( child: Material(
color: Colors.transparent, color: Colors.transparent,
child: RadioListTile( child: RadioListTile(
title: Text(s.systemDefault), title: Text(s.systemDefault),
value: ThemeMode.system, value: ThemeMode.system,
groupValue: settings.theme, groupValue: settings.theme,
onChanged: (value) { onChanged: (value) {
settings.setTheme = value; settings.setTheme = value;
Navigator.of(context).pop(); Navigator.of(context).pop();
}), }),
), ),
), ),
ClipRRect( ClipRRect(
borderRadius: borderRadius:
BorderRadius.all(Radius.circular(5)), BorderRadius.all(Radius.circular(5)),
child: Material( child: Material(
color: Colors.transparent, color: Colors.transparent,
child: RadioListTile( child: RadioListTile(
title: Text(s.darkMode), title: Text(s.darkMode),
value: ThemeMode.dark, value: ThemeMode.dark,
groupValue: settings.theme, groupValue: settings.theme,
onChanged: (value) { onChanged: (value) {
settings.setTheme = value; settings.setTheme = value;
Navigator.of(context).pop(); Navigator.of(context).pop();
}), }),
), ),
), ),
ClipRRect( ClipRRect(
borderRadius: borderRadius:
BorderRadius.all(Radius.circular(5)), BorderRadius.all(Radius.circular(5)),
child: Material( child: Material(
color: Colors.transparent, color: Colors.transparent,
child: RadioListTile( child: RadioListTile(
title: Text(s.lightMode), title: Text(s.lightMode),
value: ThemeMode.light, value: ThemeMode.light,
groupValue: settings.theme, groupValue: settings.theme,
onChanged: (value) { onChanged: (value) {
settings.setTheme = value; settings.setTheme = value;
Navigator.of(context).pop(); Navigator.of(context).pop();
}), }),
), ),
), ),
], ],
), ),
), ),
), ),
)), )),
contentPadding: EdgeInsets.symmetric(horizontal: 70.0), contentPadding: EdgeInsets.symmetric(horizontal: 70.0),
// leading: Icon(Icons.colorize), // leading: Icon(Icons.colorize),
title: Text(s.settingsTheme), title: Text(s.settingsTheme),
subtitle: Text(s.systemDefault), subtitle: Text(s.systemDefault),
), ),
Selector<SettingState, bool>( Selector<SettingState, bool>(
selector: (_, setting) => setting.realDark, selector: (_, setting) => setting.realDark,
builder: (_, data, __) => ListTile( builder: (_, data, __) => ListTile(
onTap: () => settings.setRealDark = !data, onTap: () => settings.setRealDark = !data,
contentPadding: const EdgeInsets.only( contentPadding: const EdgeInsets.only(
left: 70.0, right: 20, bottom: 10, top: 10), left: 70.0, right: 20, bottom: 10, top: 10),
// leading: Icon(Icons.colorize), // leading: Icon(Icons.colorize),
title: Text( title: Text(
s.settingsRealDark, s.settingsRealDark,
), ),
subtitle: Text(s.settingsRealDarkDes), subtitle: Text(s.settingsRealDarkDes),
trailing: Transform.scale( trailing: Transform.scale(
scale: 0.9, scale: 0.9,
child: Switch( child: Switch(
value: data, value: data,
onChanged: (boo) async { onChanged: (boo) async {
settings.setRealDark = boo; settings.setRealDark = boo;
}), }),
), ),
), ),
), ),
ListTile( ListTile(
onTap: () => generalDialog( onTap: () => generalDialog(
context, context,
title: Text.rich(TextSpan(text: s.chooseA, children: [ title: Text.rich(TextSpan(text: s.chooseA, children: [
TextSpan( TextSpan(
text: ' ${s.color}', text: ' ${s.color}',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: context.accentColor)) color: context.accentColor))
])), ])),
content: _ColorPicker( content: _ColorPicker(
onColorChanged: (value) => settings.setAccentColor = value, onColorChanged: (value) => settings.setAccentColor = value,
), ),
), ),
contentPadding: EdgeInsets.only(left: 70.0, right: 35), contentPadding: EdgeInsets.only(left: 70.0, right: 35),
title: Text(s.settingsAccentColor), title: Text(s.settingsAccentColor),
subtitle: Text(s.settingsAccentColorDes), subtitle: Text(s.settingsAccentColorDes),
trailing: Container( trailing: Container(
height: 25, height: 25,
width: 25, width: 25,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, color: context.accentColor), shape: BoxShape.circle, color: context.accentColor),
), ),
), ),
Divider(height: 1), Divider(height: 1),
Padding( Padding(
padding: EdgeInsets.all(10.0), padding: EdgeInsets.all(10.0),
), ),
Container( Container(
height: 30.0, height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70), padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text(s.fontStyle, child: Text(s.fontStyle,
style: context.textTheme.bodyText1 style: context.textTheme.bodyText1
.copyWith(color: context.accentColor)), .copyWith(color: context.accentColor)),
), ),
Selector<SettingState, int>( Selector<SettingState, int>(
selector: (_, setting) => setting.showNotesFontIndex, selector: (_, setting) => setting.showNotesFontIndex,
builder: (_, data, __) => ListTile( builder: (_, data, __) => ListTile(
contentPadding: const EdgeInsets.only( contentPadding: const EdgeInsets.only(
left: 70.0, right: 20, bottom: 10, top: 10), left: 70.0, right: 20, bottom: 10, top: 10),
title: Text(s.showNotesFonts), title: Text(s.showNotesFonts),
subtitle: Row( subtitle: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: showNotesFontStyles.map<Widget>((textStyle) { children: showNotesFontStyles.map<Widget>((textStyle) {
final index = showNotesFontStyles.indexOf(textStyle); final index = showNotesFontStyles.indexOf(textStyle);
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 10), padding: const EdgeInsets.symmetric(vertical: 10),
child: InkWell( child: InkWell(
onTap: () => settings.setShowNoteFontStyle = index, onTap: () => settings.setShowNoteFontStyle = index,
borderRadius: BorderRadius.circular(10.0), borderRadius: BorderRadius.circular(10.0),
child: Container( child: Container(
height: 60, height: 60,
width: 80, width: 80,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
border: Border.all( border: Border.all(
color: data == index color: data == index
? context.accentColor.withAlpha(70) ? context.accentColor.withAlpha(70)
: context.primaryColorDark), : context.primaryColorDark),
color: data == index color: data == index
? context.accentColor.withAlpha(70) ? context.accentColor.withAlpha(70)
: Colors.transparent), : Colors.transparent),
alignment: Alignment.center, alignment: Alignment.center,
child: Text( child: Text(
'Show notes', 'Show notes',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: textStyle, style: textStyle,
), ),
), ),
), ),
); );
}).toList(), }).toList(),
), ),
), ),
), ),
Divider(height: 1) Divider(height: 1)
], ],
), ),
), ),
); );
} }
} }
class _ColorPicker extends StatefulWidget { class _ColorPicker extends StatefulWidget {
final ValueChanged<Color> onColorChanged; final ValueChanged<Color> onColorChanged;
_ColorPicker({Key key, this.onColorChanged}) : super(key: key); _ColorPicker({Key key, this.onColorChanged}) : super(key: key);
@override @override
__ColorPickerState createState() => __ColorPickerState(); __ColorPickerState createState() => __ColorPickerState();
} }
class __ColorPickerState extends State<_ColorPicker> class __ColorPickerState extends State<_ColorPicker>
with SingleTickerProviderStateMixin { with SingleTickerProviderStateMixin {
TabController _controller; TabController _controller;
int _index; int _index;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_index = 0; _index = 0;
_controller = TabController(length: Colors.primaries.length, vsync: this) _controller = TabController(length: Colors.primaries.length, vsync: this)
..addListener(() { ..addListener(() {
setState(() => _index = _controller.index); setState(() => _index = _controller.index);
}); });
} }
Widget _colorCircle(Color color) => Material( Widget _colorCircle(Color color) => Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
borderRadius: BorderRadius.all(Radius.circular(10)), borderRadius: BorderRadius.all(Radius.circular(10)),
onTap: () => widget.onColorChanged(color), onTap: () => widget.onColorChanged(color),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
border: color == context.accentColor border: color == context.accentColor
? Border.all(color: Colors.grey[400], width: 4) ? Border.all(color: Colors.grey[400], width: 4)
: null, : null,
borderRadius: BorderRadius.all(Radius.circular(10)), borderRadius: BorderRadius.all(Radius.circular(10)),
color: color), color: color),
), ),
), ),
); );
List<Widget> _accentList(MaterialAccentColor color) => [ List<Widget> _accentList(MaterialAccentColor color) => [
_colorCircle(color.shade100), _colorCircle(color.shade100),
_colorCircle(color.shade200), _colorCircle(color.shade200),
_colorCircle(color.shade400), _colorCircle(color.shade400),
_colorCircle(color.shade700) _colorCircle(color.shade700)
]; ];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
width: 400, width: 400,
height: 400, height: 400,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Container( Container(
height: 40, height: 40,
color: Theme.of(context).dialogBackgroundColor, color: Theme.of(context).dialogBackgroundColor,
child: TabBar( child: TabBar(
labelPadding: EdgeInsets.symmetric(horizontal: 10), labelPadding: EdgeInsets.symmetric(horizontal: 10),
controller: _controller, controller: _controller,
indicatorColor: Colors.transparent, indicatorColor: Colors.transparent,
indicatorSize: TabBarIndicatorSize.tab, indicatorSize: TabBarIndicatorSize.tab,
isScrollable: true, isScrollable: true,
tabs: Colors.primaries tabs: Colors.primaries
.map<Widget>((color) => Tab( .map<Widget>((color) => Tab(
child: Container( child: Container(
height: 20, height: 20,
width: 40, width: 40,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Colors.primaries.indexOf(color) == _index border: Colors.primaries.indexOf(color) == _index
? Border.all( ? Border.all(
color: Colors.grey[400], width: 2) color: Colors.grey[400], width: 2)
: null, : null,
borderRadius: borderRadius:
BorderRadius.all(Radius.circular(10)), BorderRadius.all(Radius.circular(10)),
color: color), color: color),
), ),
)) ))
.toList(), .toList(),
), ),
), ),
Expanded( Expanded(
child: TabBarView( child: TabBarView(
physics: const ClampingScrollPhysics(), physics: const ClampingScrollPhysics(),
key: UniqueKey(), key: UniqueKey(),
controller: _controller, controller: _controller,
children: Colors.primaries children: Colors.primaries
.map<Widget>((color) => ScrollConfiguration( .map<Widget>((color) => ScrollConfiguration(
behavior: NoGrowBehavior(), behavior: NoGrowBehavior(),
child: GridView.count( child: GridView.count(
primary: false, primary: false,
padding: const EdgeInsets.fromLTRB(2, 10, 2, 10), padding: const EdgeInsets.fromLTRB(2, 10, 2, 10),
crossAxisSpacing: 4, crossAxisSpacing: 4,
mainAxisSpacing: 4, mainAxisSpacing: 4,
crossAxisCount: 3, crossAxisCount: 3,
children: <Widget>[ children: <Widget>[
_colorCircle(color.shade100), _colorCircle(color.shade100),
_colorCircle(color.shade200), _colorCircle(color.shade200),
_colorCircle(color.shade300), _colorCircle(color.shade300),
_colorCircle(color.shade400), _colorCircle(color.shade400),
_colorCircle(color.shade500), _colorCircle(color.shade500),
_colorCircle(color.shade600), _colorCircle(color.shade600),
_colorCircle(color.shade700), _colorCircle(color.shade700),
_colorCircle(color.shade800), _colorCircle(color.shade800),
_colorCircle(color.shade900), _colorCircle(color.shade900),
...color == Colors.red ...color == Colors.red
? _accentList(Colors.redAccent) ? _accentList(Colors.redAccent)
: color == Colors.pink : color == Colors.pink
? _accentList(Colors.pinkAccent) ? _accentList(Colors.pinkAccent)
: color == Colors.deepOrange : color == Colors.deepOrange
? _accentList(Colors.deepOrangeAccent) ? _accentList(Colors.deepOrangeAccent)
: color == Colors.orange : color == Colors.orange
? _accentList(Colors.orangeAccent) ? _accentList(Colors.orangeAccent)
: color == Colors.amber : color == Colors.amber
? _accentList( ? _accentList(
Colors.amberAccent) Colors.amberAccent)
: color == Colors.yellow : color == Colors.yellow
? _accentList( ? _accentList(
Colors.yellowAccent) Colors.yellowAccent)
: color == Colors.lime : color == Colors.lime
? _accentList( ? _accentList(
Colors.limeAccent) Colors.limeAccent)
: color == : color ==
Colors Colors
.lightGreen .lightGreen
? _accentList(Colors ? _accentList(Colors
.lightGreenAccent) .lightGreenAccent)
: color == : color ==
Colors.green Colors.green
? _accentList(Colors ? _accentList(Colors
.greenAccent) .greenAccent)
: color == : color ==
Colors Colors
.teal .teal
? _accentList( ? _accentList(
Colors Colors
.tealAccent) .tealAccent)
: color == : color ==
Colors Colors
.cyan .cyan
? _accentList(Colors ? _accentList(Colors
.cyanAccent) .cyanAccent)
: color == : color ==
Colors.lightBlue Colors.lightBlue
? _accentList(Colors.lightBlueAccent) ? _accentList(Colors.lightBlueAccent)
: color == Colors.blue : color == Colors.blue
? _accentList(Colors.blueAccent) ? _accentList(Colors.blueAccent)
: color == Colors.indigo : color == Colors.indigo
? _accentList(Colors.indigoAccent) ? _accentList(Colors.indigoAccent)
: color == Colors.purple : color == Colors.purple
? _accentList(Colors.purpleAccent) ? _accentList(Colors.purpleAccent)
: color == Colors.deepPurple : color == Colors.deepPurple
? _accentList(Colors.deepPurpleAccent) ? _accentList(Colors.deepPurpleAccent)
: [] : []
], ],
), ),
)) ))
.toList(), .toList(),
), ),
), ),
], ],
), ),
); );
} }
} }

Some files were not shown because too many files have changed in this diff Show More