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

24
.github/FUNDING.yml vendored
View File

@ -1,12 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4buy-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
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
liberapay: stonegate # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt 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']
# These are supported funding model platforms
github: # Replace with up to 4buy-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
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
liberapay: stonegate # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt 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']

View File

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

82
.gitignore vendored
View File

@ -1,41 +1,41 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# 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
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/lib/.env.dart
/build/
pubspec.lock
.vscode
analysis_options.yaml
# Web related
lib/generated_plugin_registrant.dart
# Exceptions to above rules.
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# 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
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/lib/.env.dart
/build/
pubspec.lock
.vscode
analysis_options.yaml
# Web related
lib/generated_plugin_registrant.dart
# Exceptions to above rules.
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages

View File

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

View File

@ -1,331 +1,331 @@
# Tsacdop Changelog
## 0.5.0
Release date 2020/10/13
### New fewtures
* Support multi select on recent and favorite tab.
* Select all/ select before/ select after.
* Option to delete episode download file when played.
* OPtion to mark as listened after skipped.
### Bug fixed
* Feed pubdate parse error.
* Episodes load with initial position failed.
### Minor changes
* Single colume layout update.
* About page UI update.
* More smooth animation when open podcast detail page.
* Change sort by button style in podcast detail page.
* Auto rewind 3 seconds when resuming from paused state.
## 0.4.20
Release date 2020/10/3
### Bug fixed
* Rss feed parse error.
## 0.4.19
Release date 2020/10/1
### New features
* Set podcastindex as default search engine.
* Option to hide podcast discovery in search page.
* Italian translation support, thanks Edoardo.
### Bug fixed
* Mark all listened error.
## 0.4.18
Release date 2020/9/27
### New features
* Support gpodder.net sync.
* Portuguese translation, thanks Bruno.
* Turn off auto update for podcast.
* Pull to refresh in recent tab, supports group update.
### Minor changes
* Longpress 'see all' to open full podcast list.
## 0.4.17
Release date 2020/9/16
### Bug fixed
* Remove notification after app removed from recent.
## 0.4.16
Release date 2020/9/15
### New features
* Discovery feature in search page.
* Multi select in podcast page.
* Customize the speed options available.
### Bugs fixed
* Fix download error when podcast name includes /.
* Make the group name editable directly.
* Fixed shownote timestamp click error.
### Minor changes
* Update donate button UI.
## 0.4.15
Release date 2020/8/30
### New features
* Option to change notification panel layout.
* Option to change show notes font style.
* Option to hide listened default.
* Change skip next/previous to fastForward/rewind on headset click.
### Bugs fixed
* Download error when filename too long.
### Minor change
* Update download button style and downloaded indicator style.
* Add 1.1 to speed setting.
* Add 5s to skip seconds setting.
## 0.4.14
Release date 2020/8/20
Only for izzyonandroid.
## 0.4.13
Release date 2020/8/19
### Bugs fixed
* Downloaded episode play error, you might need to redownload the episode.
## 0.4.12
Release date 2020/8/15
### Bugs fixed
* Crash when reorder episodes or podcasts.
* Popup menu setting import bug.
* Default language failed to load.
### Minor changes
* Change language|feedback|podcast settings to button sheet.
* Add history in home playlist button.
* History page UI improved.
## 0.4.11
Release date 2020/8/12
### New features
* Boost volume. You can change boost level in settings.
* You can tap time stamp to skip instantly in shownote when the episode is playing.
* Add history list in playlist page.
* You can also mark not listened now.
### Minor change
* Improved time picker UI.
* Add episode setting page.
### Bugs fix
* Play record didn't saved after stop playing.
* Network error message didn't disapear after skip to next.
* Fireside avatar load error.
## 0.4.10
Release date 2020/8/6
### Bugs fixed
* Episdoe date parse error.
* Play from start after interrupt.
* Playlist in player unstable.
* Language setting not saved after restart app.
### Minor change
* Fast forward and rewind buttons UI changed.
## v0.4.9
Release date 2020/8/1
### New features
* Player UI redesign.
* Added player height setting.
* Added skip silence feature.
### Bugs fixed
* Language fixs. (Thanks to Atrate)
* Make app movable to SD card. (Thanks to Atrate)
### Minor change
* Episode page open animation improved.
* Podcast page load faster than before.
* Removed unnecessary scroll overlay effect.
* Episode page bottom menu hide when scroll down.
## v0.4.8
Release date 2020/7/25
### New features
* Filter in podcast detail page, you can also hide listened episodes.
* Search result ui improved, you can see more info for result.
* Update audio service to latest version.
* Support fast forward seconds and rewind seconds customize.
* Add Franch language support(beta).
* Add translators in about page.
### Bugs fixed
* Icon issue on below android 8 devices.
### Minor change
* Download button ui improved.
* Title changed to scrollable in episode detail page.
* Real dark theme improved.
* Add dot indicator in popup menu.
* Tap logo in homepage to toggle theme.
## v0.4.7
Release date 2020/7/18
### Bugs fixed
* Ompl files form other platform import error.
* Audio cache did't work.
## v0.4.6
Release date 2020/7/17
### Bugs fixed
* Mark listened not work.
* Recover subscribe wrong group.
## v0.4.5
Release date 2020/7/16
## New features
* OPML backup file supports group.
* Add settings backup and restore.
* Enable R8 and dart obfuscate.
## Bugs fixed
* OPML import not shown in group.
## Minor UI change
* Tap logo in homepage to toggle theme mode.
* Change subscribe button style.
* Improve history chart style.
## v0.4.0
Release date 2020/7/9
### New features
* 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.
### Minor UI change
* Improve dropdown menu UI.
* Change icons color in setting page.
* Improve player panel animation.
* Add scroll bar in libraries page.
## v0.3.6
Release date 2020/6/30
### New feature
* Add sleep timer settings. include default time, auto start sleep timer, etc.
### Bug fixed
* Crash on stop player.
* Some download file didn't auto deleted.
## v0.3.5
Release date 2020/6/20
This is a energency release.
### Bugs fixed
* Crashed in download page or button after remove a podcast. Add episode check when load tasks from flutterdownloader.
### Minor UI change
* Add buy me a coffee in about page.
* Remove progress number in download list in failed task, change refresh icon color to red.
## v0.3.4
Release date 2020/6/16
### 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 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.
* Improved downloaded file manager, you can now sort downloads by date or size, you can also only view listened downloads.
### Minor UI Change
* Removed the listened indicator, increased the color difference for listened episodes.
* Add text in podcast manage page menu.
* Change episode shownote font to Martel.
### Bugs Fixed
* Auto play when receive notification.
* Lose podcast when import OMPL file.
### Other
* Add privacy policy.
# Tsacdop Changelog
## 0.5.0
Release date 2020/10/13
### New fewtures
* Support multi select on recent and favorite tab.
* Select all/ select before/ select after.
* Option to delete episode download file when played.
* OPtion to mark as listened after skipped.
### Bug fixed
* Feed pubdate parse error.
* Episodes load with initial position failed.
### Minor changes
* Single colume layout update.
* About page UI update.
* More smooth animation when open podcast detail page.
* Change sort by button style in podcast detail page.
* Auto rewind 3 seconds when resuming from paused state.
## 0.4.20
Release date 2020/10/3
### Bug fixed
* Rss feed parse error.
## 0.4.19
Release date 2020/10/1
### New features
* Set podcastindex as default search engine.
* Option to hide podcast discovery in search page.
* Italian translation support, thanks Edoardo.
### Bug fixed
* Mark all listened error.
## 0.4.18
Release date 2020/9/27
### New features
* Support gpodder.net sync.
* Portuguese translation, thanks Bruno.
* Turn off auto update for podcast.
* Pull to refresh in recent tab, supports group update.
### Minor changes
* Longpress 'see all' to open full podcast list.
## 0.4.17
Release date 2020/9/16
### Bug fixed
* Remove notification after app removed from recent.
## 0.4.16
Release date 2020/9/15
### New features
* Discovery feature in search page.
* Multi select in podcast page.
* Customize the speed options available.
### Bugs fixed
* Fix download error when podcast name includes /.
* Make the group name editable directly.
* Fixed shownote timestamp click error.
### Minor changes
* Update donate button UI.
## 0.4.15
Release date 2020/8/30
### New features
* Option to change notification panel layout.
* Option to change show notes font style.
* Option to hide listened default.
* Change skip next/previous to fastForward/rewind on headset click.
### Bugs fixed
* Download error when filename too long.
### Minor change
* Update download button style and downloaded indicator style.
* Add 1.1 to speed setting.
* Add 5s to skip seconds setting.
## 0.4.14
Release date 2020/8/20
Only for izzyonandroid.
## 0.4.13
Release date 2020/8/19
### Bugs fixed
* Downloaded episode play error, you might need to redownload the episode.
## 0.4.12
Release date 2020/8/15
### Bugs fixed
* Crash when reorder episodes or podcasts.
* Popup menu setting import bug.
* Default language failed to load.
### Minor changes
* Change language|feedback|podcast settings to button sheet.
* Add history in home playlist button.
* History page UI improved.
## 0.4.11
Release date 2020/8/12
### New features
* Boost volume. You can change boost level in settings.
* You can tap time stamp to skip instantly in shownote when the episode is playing.
* Add history list in playlist page.
* You can also mark not listened now.
### Minor change
* Improved time picker UI.
* Add episode setting page.
### Bugs fix
* Play record didn't saved after stop playing.
* Network error message didn't disapear after skip to next.
* Fireside avatar load error.
## 0.4.10
Release date 2020/8/6
### Bugs fixed
* Episdoe date parse error.
* Play from start after interrupt.
* Playlist in player unstable.
* Language setting not saved after restart app.
### Minor change
* Fast forward and rewind buttons UI changed.
## v0.4.9
Release date 2020/8/1
### New features
* Player UI redesign.
* Added player height setting.
* Added skip silence feature.
### Bugs fixed
* Language fixs. (Thanks to Atrate)
* Make app movable to SD card. (Thanks to Atrate)
### Minor change
* Episode page open animation improved.
* Podcast page load faster than before.
* Removed unnecessary scroll overlay effect.
* Episode page bottom menu hide when scroll down.
## v0.4.8
Release date 2020/7/25
### New features
* Filter in podcast detail page, you can also hide listened episodes.
* Search result ui improved, you can see more info for result.
* Update audio service to latest version.
* Support fast forward seconds and rewind seconds customize.
* Add Franch language support(beta).
* Add translators in about page.
### Bugs fixed
* Icon issue on below android 8 devices.
### Minor change
* Download button ui improved.
* Title changed to scrollable in episode detail page.
* Real dark theme improved.
* Add dot indicator in popup menu.
* Tap logo in homepage to toggle theme.
## v0.4.7
Release date 2020/7/18
### Bugs fixed
* Ompl files form other platform import error.
* Audio cache did't work.
## v0.4.6
Release date 2020/7/17
### Bugs fixed
* Mark listened not work.
* Recover subscribe wrong group.
## v0.4.5
Release date 2020/7/16
## New features
* OPML backup file supports group.
* Add settings backup and restore.
* Enable R8 and dart obfuscate.
## Bugs fixed
* OPML import not shown in group.
## Minor UI change
* Tap logo in homepage to toggle theme mode.
* Change subscribe button style.
* Improve history chart style.
## v0.4.0
Release date 2020/7/9
### New features
* 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.
### Minor UI change
* Improve dropdown menu UI.
* Change icons color in setting page.
* Improve player panel animation.
* Add scroll bar in libraries page.
## v0.3.6
Release date 2020/6/30
### New feature
* Add sleep timer settings. include default time, auto start sleep timer, etc.
### Bug fixed
* Crash on stop player.
* Some download file didn't auto deleted.
## v0.3.5
Release date 2020/6/20
This is a energency release.
### Bugs fixed
* Crashed in download page or button after remove a podcast. Add episode check when load tasks from flutterdownloader.
### Minor UI change
* Add buy me a coffee in about page.
* Remove progress number in download list in failed task, change refresh icon color to red.
## v0.3.4
Release date 2020/6/16
### 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 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.
* Improved downloaded file manager, you can now sort downloads by date or size, you can also only view listened downloads.
### Minor UI Change
* Removed the listened indicator, increased the color difference for listened episodes.
* Add text in podcast manage page menu.
* Change episode shownote font to Martel.
### Bugs Fixed
* Auto play when receive notification.
* Lose podcast when import OMPL file.
### Other
* 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]
[![github action][]][github action link]
[![Build Status - Cirrus][]][build status]
[![GitHub Release][]][github release - recent]
[![Github Downloads][]][github release - recent]
[![Localizely][]][localizely - website]
[![style: effective dart][]][effective dart pub]
[![License badge][]][license]
## About
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.
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).
## Features
* Podcast group management
* Playlist support
* Sleep timer / speed setting
* OPML file export and import
* Auto syncing in background
* Listening and subscription history record
* Dark mode / accent color
* Download for offline play
* Auto download new episodes / auto delete outdated downloads
* Settings backup
* Skip silence
* Boost volume
More to come...
## Preview
| Home Page | Group | Podcast | Episode| Dark Mode |
| ----- | ----- | ----- | ------ | ----- |
|![][Homepage ScreenShot]|![][Group Screenshot] | ![][Podcast Screenshot] | ![][Episode Screenshot]| ![][Darkmode Screenshot] |
## Localization
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.
### ![English]
### ![Chinese Simplified]
### ![French]
### ![Spanish]
### ![Portuguese]
## License
Tsacdop is licensed under the [GPL v3.0](https://github.com/stonega/tsacdop/blob/master/LICENSE) license.
## Build
1. If you don't have Flutter SDK installed, please visit offcial [Flutter][Flutter Install] site.
2. Fetch latest sorce code from master branch.
```
git clone https://github.com/stonega/tsacdop.git
```
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.
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.
``` dart
final environment = {"apiKey":"APIKEY"};
```
4. Run the app with Android Studio or Visual Studio. Or the command line.
```
flutter pub get
flutter run
```
## Contribute
If you have an issue or found a bug, please raise a GitHub issue. Pull requests are also welcome.
## Archetecture
### Plugins
* Local storage
+ sqflite
+ shared_preferences
* Audio
+ just_audio
+ audio_service
* State management
+ provider
* Download
+ flutter_downloader
* Background task
+ workmanager
### Directory Structure
```
UI
src
├──home
├──home.dart [Homepage]
├──searc_podcast.dart [Search Page]
└──playlist.dart [Playlist Page]
├──podcasts
├──podcast_manage.dart [Group Page]
└──podcast_detail.dart [Podcast Page]
├──episodes
└──episode_detail.dart [Episode Page]
├──settings
└──setting.dart [Setting Page]
STATE
src
├──state
├──audio_state.dart [Audio State]
├──download_state.dart [Episode Download]
├──podcast_group.dart [Podcast Groups]
├──refresh_podcast.dart [Episode Refresh]
└──setting_state.dart [Setting]
Service
src
├──service
├──api_service.dart [Podcast Search]
└──ompl_builde.dart [OMPL export]
```
## Known Issue
* Playlist is unstable
## Contact
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).
## Getting Started with Flutter
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:
* [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
* [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
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.
[Flutter Install]: https://flutter.dev/docs/get-started/install
[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
[github action]: https://github.com/stonega/tsacdop/workflows/Flutter%20Build/badge.svg
[github action link]: https://github.com/stonega/tsacdop/actions
[build status ]: https://circleci.com/gh/stonega/tsacdop/tree/master
[github release]: https://img.shields.io/github/v/release/stonega/tsacdop
[github release - recent]: https://github.com/stonega/tsacdop/releases
[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
[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=%
[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=%
[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/
[google play - icon]: https://img.shields.io/badge/google-playStore-%2323CCC6
[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
[Group Screenshot]: https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585894051734.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
[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
[effective dart pub]: https://pub.dev/packages/effective_dart
[license]: https://github.com/stonega/tsacdop/blob/master/LICENSE
[License badge]: https://img.shields.io/badge/license-GPLv3-yellow.svg
[![Tsacdop Banner][]][google play]
[![github action][]][github action link]
[![Build Status - Cirrus][]][build status]
[![GitHub Release][]][github release - recent]
[![Github Downloads][]][github release - recent]
[![Localizely][]][localizely - website]
[![style: effective dart][]][effective dart pub]
[![License badge][]][license]
## About
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.
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).
## Features
* Podcast group management
* Playlist support
* Sleep timer / speed setting
* OPML file export and import
* Auto syncing in background
* Listening and subscription history record
* Dark mode / accent color
* Download for offline play
* Auto download new episodes / auto delete outdated downloads
* Settings backup
* Skip silence
* Boost volume
More to come...
## Preview
| Home Page | Group | Podcast | Episode| Dark Mode |
| ----- | ----- | ----- | ------ | ----- |
|![][Homepage ScreenShot]|![][Group Screenshot] | ![][Podcast Screenshot] | ![][Episode Screenshot]| ![][Darkmode Screenshot] |
## Localization
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.
### ![English]
### ![Chinese Simplified]
### ![French]
### ![Spanish]
### ![Portuguese]
## License
Tsacdop is licensed under the [GPL v3.0](https://github.com/stonega/tsacdop/blob/master/LICENSE) license.
## Build
1. If you don't have Flutter SDK installed, please visit offcial [Flutter][Flutter Install] site.
2. Fetch latest sorce code from master branch.
```
git clone https://github.com/stonega/tsacdop.git
```
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.
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.
``` dart
final environment = {"apiKey":"APIKEY"};
```
4. Run the app with Android Studio or Visual Studio. Or the command line.
```
flutter pub get
flutter run
```
## Contribute
If you have an issue or found a bug, please raise a GitHub issue. Pull requests are also welcome.
## Archetecture
### Plugins
* Local storage
+ sqflite
+ shared_preferences
* Audio
+ just_audio
+ audio_service
* State management
+ provider
* Download
+ flutter_downloader
* Background task
+ workmanager
### Directory Structure
```
UI
src
├──home
├──home.dart [Homepage]
├──searc_podcast.dart [Search Page]
└──playlist.dart [Playlist Page]
├──podcasts
├──podcast_manage.dart [Group Page]
└──podcast_detail.dart [Podcast Page]
├──episodes
└──episode_detail.dart [Episode Page]
├──settings
└──setting.dart [Setting Page]
STATE
src
├──state
├──audio_state.dart [Audio State]
├──download_state.dart [Episode Download]
├──podcast_group.dart [Podcast Groups]
├──refresh_podcast.dart [Episode Refresh]
└──setting_state.dart [Setting]
Service
src
├──service
├──api_service.dart [Podcast Search]
└──ompl_builde.dart [OMPL export]
```
## Known Issue
* Playlist is unstable
## Contact
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).
## Getting Started with Flutter
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:
* [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
* [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
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.
[Flutter Install]: https://flutter.dev/docs/get-started/install
[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
[github action]: https://github.com/stonega/tsacdop/workflows/Flutter%20Build/badge.svg
[github action link]: https://github.com/stonega/tsacdop/actions
[build status ]: https://circleci.com/gh/stonega/tsacdop/tree/master
[github release]: https://img.shields.io/github/v/release/stonega/tsacdop
[github release - recent]: https://github.com/stonega/tsacdop/releases
[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
[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=%
[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=%
[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/
[google play - icon]: https://img.shields.io/badge/google-playStore-%2323CCC6
[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
[Group Screenshot]: https://raw.githubusercontent.com/stonega/tsacdop/master/preview/1585894051734.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
[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
[effective dart pub]: https://pub.dev/packages/effective_dart
[license]: https://github.com/stonega/tsacdop/blob/master/LICENSE
[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
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java

View File

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

View File

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

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<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.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<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.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/>
</classpath>

View File

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

View File

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

View File

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

View File

@ -1,90 +1,90 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
compileSdkVersion 29
ndkVersion "21.3.6528147"
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.stonegate.tsacdop"
minSdkVersion 19
targetSdkVersion 29
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
signingConfigs {
release {
storeFile file(System.getenv("KEYSTORE") ?:"keystore.jks")
storePassword System.getenv("KEYSTORE_PASSWORD")
keyAlias System.getenv("KEY_ALIAS")
keyPassword System.getenv("KEY_PASSWORD")
// keyAlias keystoreProperties['keyAlias']
// keyPassword keystoreProperties['keyPassword']
// storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
// storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
shrinkResources false
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
def appcompat_version = "1.1.0"
implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
compileSdkVersion 29
ndkVersion "21.3.6528147"
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.stonegate.tsacdop"
minSdkVersion 19
targetSdkVersion 29
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
signingConfigs {
release {
storeFile file(System.getenv("KEYSTORE") ?:"keystore.jks")
storePassword System.getenv("KEYSTORE_PASSWORD")
keyAlias System.getenv("KEY_ALIAS")
keyPassword System.getenv("KEY_PASSWORD")
// keyAlias keystoreProperties['keyAlias']
// keyPassword keystoreProperties['keyPassword']
// storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
// storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
shrinkResources false
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
def appcompat_version = "1.1.0"
implementation "androidx.appcompat:appcompat:$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"
package="com.stonegate.tsacdop">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.stonegate.tsacdop">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</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">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
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
FlutterApplication and put your custom class here. -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.INTERNET" />
<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">
<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.SplashScreenDrawable" android:resource="@drawable/normal_background" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- <meta-data android:name="io.flutter.embedding.android.SplashScreenUntilFirstFrame" android:value="true" /> -->
</activity>
<service android:name="com.ryanheise.audioservice.AudioService">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
<receiver android:name="com.ryanheise.audioservice.MediaButtonReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data android:name="flutterEmbedding" android:value="2" />
</application>
</manifest>
<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
calls FlutterMain.startInitialization(this); in its onCreate method.
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
FlutterApplication and put your custom class here. -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.INTERNET" />
<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">
<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.SplashScreenDrawable" android:resource="@drawable/normal_background" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- <meta-data android:name="io.flutter.embedding.android.SplashScreenUntilFirstFrame" android:value="true" /> -->
</activity>
<service android:name="com.ryanheise.audioservice.AudioService">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
<receiver android:name="com.ryanheise.audioservice.MediaButtonReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data android:name="flutterEmbedding" android:value="2" />
</application>
</manifest>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

64
ios/.gitignore vendored
View File

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

View File

@ -1,26 +1,26 @@
<?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">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
</dict>
</plist>
<?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">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
</dict>
</plist>

View File

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

View File

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

View File

@ -1,88 +1,88 @@
# Uncomment this line to define a global platform for your project
platform :ios, '11.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
source 'https://cdn.cocoapods.org/'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def parse_KV_file(file, separator='=')
file_abs_path = File.expand_path(file)
if !File.exists? file_abs_path
return [];
end
generated_key_values = {}
skip_line_start_symbols = ["#", "/"]
File.foreach(file_abs_path) do |line|
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
plugin = line.split(pattern=separator)
if plugin.length == 2
podname = plugin[0].strip()
path = plugin[1].strip()
podpath = File.expand_path("#{path}", file_abs_path)
generated_key_values[podname] = podpath
else
puts "Invalid plugin specification: #{line}"
end
end
generated_key_values
end
target 'Runner' do
use_frameworks!
use_modular_headers!
# Flutter Pod
copied_flutter_dir = File.join(__dir__, 'Flutter')
copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
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.
# 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.
generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig')
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"
end
generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path)
cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR'];
unless File.exist?(copied_framework_path)
FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
end
unless File.exist?(copied_podspec_path)
FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
end
end
# Keep pod path relative so it can be checked into Podfile.lock.
pod 'Flutter', :path => 'Flutter'
# Plugin Pods
# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
# referring to absolute paths on developers' machines.
system('rm -rf .symlinks')
system('mkdir -p .symlinks/plugins')
plugin_pods = parse_KV_file('../.flutter-plugins')
plugin_pods.each do |name, path|
symlink = File.join('.symlinks', 'plugins', name)
File.symlink(path, symlink)
pod name, :path => File.join(symlink, 'ios')
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
end
end
# Uncomment this line to define a global platform for your project
platform :ios, '11.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
source 'https://cdn.cocoapods.org/'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def parse_KV_file(file, separator='=')
file_abs_path = File.expand_path(file)
if !File.exists? file_abs_path
return [];
end
generated_key_values = {}
skip_line_start_symbols = ["#", "/"]
File.foreach(file_abs_path) do |line|
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
plugin = line.split(pattern=separator)
if plugin.length == 2
podname = plugin[0].strip()
path = plugin[1].strip()
podpath = File.expand_path("#{path}", file_abs_path)
generated_key_values[podname] = podpath
else
puts "Invalid plugin specification: #{line}"
end
end
generated_key_values
end
target 'Runner' do
use_frameworks!
use_modular_headers!
# Flutter Pod
copied_flutter_dir = File.join(__dir__, 'Flutter')
copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
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.
# 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.
generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig')
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"
end
generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path)
cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR'];
unless File.exist?(copied_framework_path)
FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
end
unless File.exist?(copied_podspec_path)
FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
end
end
# Keep pod path relative so it can be checked into Podfile.lock.
pod 'Flutter', :path => 'Flutter'
# Plugin Pods
# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
# referring to absolute paths on developers' machines.
system('rm -rf .symlinks')
system('mkdir -p .symlinks/plugins')
plugin_pods = parse_KV_file('../.flutter-plugins')
plugin_pods.each do |name, path|
symlink = File.join('.symlinks', 'plugins', name)
File.symlink(path, symlink)
pod name, :path => File.join(symlink, 'ios')
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
end
end

View File

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

View File

@ -1,8 +1,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">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
<?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">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -1,8 +1,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">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
<?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">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

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

View File

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

View File

@ -1,8 +1,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">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
<?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">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -1,8 +1,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">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
<?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">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
# Launch Screen Assets
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.

View File

@ -1,37 +1,37 @@
<?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">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<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"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>
<?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">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<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"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

View File

@ -1,26 +1,26 @@
<?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">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
<?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">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View File

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

View File

@ -1,334 +1,334 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:line_icons/line_icons.dart';
import '../util/custom_widget.dart';
import '../util/extension_helper.dart';
const String version = '0.5.0';
class AboutApp extends StatefulWidget {
@override
_AboutAppState createState() => _AboutAppState();
}
class _AboutAppState extends State<AboutApp> {
ScrollController _scrollController;
bool _scroll;
@override
void initState() {
super.initState();
_scroll = false;
_scrollController = ScrollController()
..addListener(() {
if (_scrollController.offset > 0 && !_scroll && mounted) {
setState(() => _scroll = true);
}
if (_scrollController.offset <= 0 && _scroll && mounted) {
setState(() => _scroll = false);
}
});
}
Widget _listItem(
BuildContext context, String text, IconData icons, String url) =>
InkWell(
onTap: () => url.launchUrl,
child: Container(
height: 50.0,
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment.centerLeft,
decoration: BoxDecoration(
border: Border(
bottom: Divider.createBorderSide(context),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(icons, color: Theme.of(context).accentColor),
Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
),
Text(text),
],
),
),
);
Widget _translatorInfo(BuildContext context, {String name, String flag}) =>
Container(
height: 50.0,
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment.centerLeft,
decoration: BoxDecoration(
border: Border(
bottom: Divider.createBorderSide(context),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(LineIcons.user, color: Theme.of(context).accentColor),
Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
),
Expanded(
child: Text(
name,
maxLines: 1,
overflow: TextOverflow.fade,
)),
if (flag != null)
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: Image(
image: AssetImage('assets/$flag.png'),
height: 20,
width: 30,
fit: BoxFit.cover,
),
),
],
),
);
@override
Widget build(BuildContext context) {
OverlayEntry _createOverlayEntry(TapDownDetails detail) {
// RenderBox renderBox = context.findRenderObject();
var offset = detail.globalPosition;
return OverlayEntry(
builder: (constext) => Positioned(
left: offset.dx - 5,
top: offset.dy - 120,
child: Container(
width: 20,
height: 120,
color: Colors.transparent,
alignment: Alignment.topCenter,
child: HeartSet(height: 120, width: 20)),
),
);
}
final s = context.s;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness,
),
child: Scaffold(
backgroundColor: Theme.of(context).primaryColor,
appBar: AppBar(
title: Text(s.homeToprightMenuAbout),
leading: CustomBackButton(),
elevation: _scroll ? 1 : 0,
),
body: SafeArea(
child: ScrollConfiguration(
behavior: NoGrowBehavior(),
child: SingleChildScrollView(
controller: _scrollController,
scrollDirection: Axis.vertical,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
height: 110.0,
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Image(
image: AssetImage('assets/logo.png'),
height: 80,
),
Text(s.version(version)),
],
),
),
Padding(
padding: const EdgeInsets.all(20),
child: Text(
'Tsacdop is a podcast player built with flutter, a clean, simply beautiful and friendly app.',
textAlign: TextAlign.center,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TextButton(
onPressed: () =>
'https://tsacdop.stonegate.me/#/privacy'
.launchUrl,
style: TextButton.styleFrom(
primary: context.accentColor,
textStyle:
TextStyle(fontWeight: FontWeight.bold)),
child: Text(
s.privacyPolicy,
),
),
Container(
margin: const EdgeInsets.symmetric(horizontal: 5),
height: 4,
width: 4,
decoration: BoxDecoration(
color: context.accentColor,
shape: BoxShape.circle),
),
TextButton(
onPressed: () =>
'https://tsacdop.stonegate.me/#/changelog'
.launchUrl,
style: TextButton.styleFrom(
primary: context.accentColor,
textStyle:
TextStyle(fontWeight: FontWeight.bold)),
child: Text(s.changelog,
style: TextStyle(color: context.accentColor)),
),
],
),
Padding(
padding: EdgeInsets.only(top: 20.0, bottom: 10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
_listItem(context, 'Twitter @tsacdop',
LineIcons.twitter, 'https://twitter.com/tsacdop'),
_listItem(context, 'GitHub', LineIcons.github_alt,
'https://github.com/stonega/tsacdop'),
_listItem(context, 'Telegram', LineIcons.telegram,
'https://t.me/joinchat/Bk3LkRpTHy40QYC78PK7Qg'),
Center(
child: SizedBox(
width: 200,
child: ElevatedButton(
onPressed: () =>
'https://www.buymeacoffee.com/stonegate'
.launchUrl,
style: ElevatedButton.styleFrom(
primary: context.accentColor),
child: Container(
height: 30.0,
padding:
EdgeInsets.symmetric(horizontal: 4.0),
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Buy Me A Coffee',
style: TextStyle(
fontWeight: FontWeight.bold)),
SizedBox(width: 10),
Image(
image: AssetImage(
'assets/buymeacoffee.png'),
height: 20,
fit: BoxFit.fitHeight,
),
],
),
),
),
),
),
],
),
),
Padding(
padding: EdgeInsets.all(10.0),
),
Padding(
padding: EdgeInsets.only(top: 20.0, bottom: 10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Row(
children: [
SizedBox(width: 25),
Text(
s.translators,
style: TextStyle(
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold),
),
SizedBox(width: 2),
Icon(Icons.favorite, color: Colors.red, size: 20),
],
),
_translatorInfo(context, name: 'Atrate'),
_translatorInfo(context, name: 'ppp', flag: 'fr'),
_translatorInfo(context,
name: 'Joel Israel', flag: 'mx'),
_translatorInfo(context,
name: 'Bruno Pinheiro', flag: 'pt'),
_translatorInfo(context,
name: 'Edoardo Maria Elidoro', flag: 'it'),
],
),
),
//Spacer(),
Padding(
padding: EdgeInsets.symmetric(vertical: 10),
),
Container(
height: 50,
alignment: Alignment.center,
child: GestureDetector(
onTapDown: (detail) async {
OverlayEntry _overlayEntry;
_overlayEntry = _createOverlayEntry(detail);
Overlay.of(context).insert(_overlayEntry);
await Future.delayed(Duration(seconds: 2));
_overlayEntry?.remove();
},
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.asset(
'assets/text.png',
height: 25,
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 5),
),
Icon(
Icons.favorite,
color: Colors.blue,
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 5),
),
FlutterLogo(
size: 18,
),
],
),
),
),
],
),
),
),
),
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:line_icons/line_icons.dart';
import '../util/custom_widget.dart';
import '../util/extension_helper.dart';
const String version = '0.5.0';
class AboutApp extends StatefulWidget {
@override
_AboutAppState createState() => _AboutAppState();
}
class _AboutAppState extends State<AboutApp> {
ScrollController _scrollController;
bool _scroll;
@override
void initState() {
super.initState();
_scroll = false;
_scrollController = ScrollController()
..addListener(() {
if (_scrollController.offset > 0 && !_scroll && mounted) {
setState(() => _scroll = true);
}
if (_scrollController.offset <= 0 && _scroll && mounted) {
setState(() => _scroll = false);
}
});
}
Widget _listItem(
BuildContext context, String text, IconData icons, String url) =>
InkWell(
onTap: () => url.launchUrl,
child: Container(
height: 50.0,
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment.centerLeft,
decoration: BoxDecoration(
border: Border(
bottom: Divider.createBorderSide(context),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(icons, color: Theme.of(context).accentColor),
Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
),
Text(text),
],
),
),
);
Widget _translatorInfo(BuildContext context, {String name, String flag}) =>
Container(
height: 50.0,
padding: EdgeInsets.symmetric(horizontal: 20.0),
alignment: Alignment.centerLeft,
decoration: BoxDecoration(
border: Border(
bottom: Divider.createBorderSide(context),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(LineIcons.user, color: Theme.of(context).accentColor),
Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
),
Expanded(
child: Text(
name,
maxLines: 1,
overflow: TextOverflow.fade,
)),
if (flag != null)
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: Image(
image: AssetImage('assets/$flag.png'),
height: 20,
width: 30,
fit: BoxFit.cover,
),
),
],
),
);
@override
Widget build(BuildContext context) {
OverlayEntry _createOverlayEntry(TapDownDetails detail) {
// RenderBox renderBox = context.findRenderObject();
var offset = detail.globalPosition;
return OverlayEntry(
builder: (constext) => Positioned(
left: offset.dx - 5,
top: offset.dy - 120,
child: Container(
width: 20,
height: 120,
color: Colors.transparent,
alignment: Alignment.topCenter,
child: HeartSet(height: 120, width: 20)),
),
);
}
final s = context.s;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness,
),
child: Scaffold(
backgroundColor: Theme.of(context).primaryColor,
appBar: AppBar(
title: Text(s.homeToprightMenuAbout),
leading: CustomBackButton(),
elevation: _scroll ? 1 : 0,
),
body: SafeArea(
child: ScrollConfiguration(
behavior: NoGrowBehavior(),
child: SingleChildScrollView(
controller: _scrollController,
scrollDirection: Axis.vertical,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
height: 110.0,
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Image(
image: AssetImage('assets/logo.png'),
height: 80,
),
Text(s.version(version)),
],
),
),
Padding(
padding: const EdgeInsets.all(20),
child: Text(
'Tsacdop is a podcast player built with flutter, a clean, simply beautiful and friendly app.',
textAlign: TextAlign.center,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TextButton(
onPressed: () =>
'https://tsacdop.stonegate.me/#/privacy'
.launchUrl,
style: TextButton.styleFrom(
primary: context.accentColor,
textStyle:
TextStyle(fontWeight: FontWeight.bold)),
child: Text(
s.privacyPolicy,
),
),
Container(
margin: const EdgeInsets.symmetric(horizontal: 5),
height: 4,
width: 4,
decoration: BoxDecoration(
color: context.accentColor,
shape: BoxShape.circle),
),
TextButton(
onPressed: () =>
'https://tsacdop.stonegate.me/#/changelog'
.launchUrl,
style: TextButton.styleFrom(
primary: context.accentColor,
textStyle:
TextStyle(fontWeight: FontWeight.bold)),
child: Text(s.changelog,
style: TextStyle(color: context.accentColor)),
),
],
),
Padding(
padding: EdgeInsets.only(top: 20.0, bottom: 10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
_listItem(context, 'Twitter @tsacdop',
LineIcons.twitter, 'https://twitter.com/tsacdop'),
_listItem(context, 'GitHub', LineIcons.github_alt,
'https://github.com/stonega/tsacdop'),
_listItem(context, 'Telegram', LineIcons.telegram,
'https://t.me/joinchat/Bk3LkRpTHy40QYC78PK7Qg'),
Center(
child: SizedBox(
width: 200,
child: ElevatedButton(
onPressed: () =>
'https://www.buymeacoffee.com/stonegate'
.launchUrl,
style: ElevatedButton.styleFrom(
primary: context.accentColor),
child: Container(
height: 30.0,
padding:
EdgeInsets.symmetric(horizontal: 4.0),
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Buy Me A Coffee',
style: TextStyle(
fontWeight: FontWeight.bold)),
SizedBox(width: 10),
Image(
image: AssetImage(
'assets/buymeacoffee.png'),
height: 20,
fit: BoxFit.fitHeight,
),
],
),
),
),
),
),
],
),
),
Padding(
padding: EdgeInsets.all(10.0),
),
Padding(
padding: EdgeInsets.only(top: 20.0, bottom: 10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Row(
children: [
SizedBox(width: 25),
Text(
s.translators,
style: TextStyle(
color: Theme.of(context).accentColor,
fontWeight: FontWeight.bold),
),
SizedBox(width: 2),
Icon(Icons.favorite, color: Colors.red, size: 20),
],
),
_translatorInfo(context, name: 'Atrate'),
_translatorInfo(context, name: 'ppp', flag: 'fr'),
_translatorInfo(context,
name: 'Joel Israel', flag: 'mx'),
_translatorInfo(context,
name: 'Bruno Pinheiro', flag: 'pt'),
_translatorInfo(context,
name: 'Edoardo Maria Elidoro', flag: 'it'),
],
),
),
//Spacer(),
Padding(
padding: EdgeInsets.symmetric(vertical: 10),
),
Container(
height: 50,
alignment: Alignment.center,
child: GestureDetector(
onTapDown: (detail) async {
OverlayEntry _overlayEntry;
_overlayEntry = _createOverlayEntry(detail);
Overlay.of(context).insert(_overlayEntry);
await Future.delayed(Duration(seconds: 2));
_overlayEntry?.remove();
},
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.asset(
'assets/text.png',
height: 25,
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 5),
),
Icon(
Icons.favorite,
color: Colors.blue,
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 5),
),
FlutterLogo(
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_downloader/flutter_downloader.dart';
import 'package:provider/provider.dart';
import '../episodes/episode_detail.dart';
import '../state/download_state.dart';
import '../type/episode_task.dart';
import '../util/pageroute.dart';
class DownloadList extends StatefulWidget {
DownloadList({Key key}) : super(key: key);
@override
_DownloadListState createState() => _DownloadListState();
}
Widget _downloadButton(EpisodeTask task, BuildContext context) {
var downloader = Provider.of<DownloadState>(context, listen: false);
switch (task.status.value) {
case 2:
return Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(
Icons.pause_circle_filled,
),
onPressed: () => downloader.pauseTask(task.episode),
),
IconButton(
icon: Icon(Icons.close),
onPressed: () => downloader.delTask(task.episode),
),
],
);
case 4:
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: Icon(Icons.refresh, color: Colors.red),
onPressed: () => downloader.retryTask(task.episode),
),
IconButton(
icon: Icon(Icons.close),
onPressed: () => downloader.delTask(task.episode),
),
],
);
case 6:
return Row(mainAxisSize: MainAxisSize.min, children: [
IconButton(
icon: Icon(Icons.play_circle_filled),
onPressed: () => downloader.resumeTask(task.episode),
),
IconButton(
icon: Icon(Icons.close),
onPressed: () => downloader.delTask(task.episode),
),
]);
break;
default:
return SizedBox(
width: 10,
height: 10,
);
}
}
class _DownloadListState extends State<DownloadList> {
@override
Widget build(BuildContext context) {
return Consumer<DownloadState>(builder: (_, downloader, __) {
final tasks = downloader.episodeTasks
.where((task) => task.status.value != 3)
.toList();
return tasks.length > 0
? SliverPadding(
padding: EdgeInsets.symmetric(vertical: 5.0),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return ListTile(
onTap: () => Navigator.push(
context,
ScaleRoute(
page: EpisodeDetail(
episodeItem: tasks[index].episode,
)),
),
title: SizedBox(
height: 40,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
flex: 5,
child: Text(
tasks[index].episode.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
Expanded(
flex: 1,
child: tasks[index].progress >= 0 &&
tasks[index].status !=
DownloadTaskStatus.failed
? Container(
width: 40.0,
height: 20.0,
padding:
EdgeInsets.symmetric(horizontal: 2),
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(6)),
color: Colors.red),
child: Text(
'${tasks[index].progress}%',
textAlign: TextAlign.center,
maxLines: 1,
style: TextStyle(color: Colors.white),
))
: Container(
height: 40,
),
),
],
),
),
subtitle: SizedBox(
height: 2,
child: LinearProgressIndicator(
value: tasks[index].progress / 100,
),
),
leading: CircleAvatar(
radius: 20,
backgroundImage: tasks[index].episode.avatarImage),
trailing: _downloadButton(tasks[index], context),
);
},
childCount: tasks.length,
),
),
)
: SliverToBoxAdapter(
child: Center(),
);
});
}
}
import 'package:flutter/material.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:provider/provider.dart';
import '../episodes/episode_detail.dart';
import '../state/download_state.dart';
import '../type/episode_task.dart';
import '../util/pageroute.dart';
class DownloadList extends StatefulWidget {
DownloadList({Key key}) : super(key: key);
@override
_DownloadListState createState() => _DownloadListState();
}
Widget _downloadButton(EpisodeTask task, BuildContext context) {
var downloader = Provider.of<DownloadState>(context, listen: false);
switch (task.status.value) {
case 2:
return Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(
Icons.pause_circle_filled,
),
onPressed: () => downloader.pauseTask(task.episode),
),
IconButton(
icon: Icon(Icons.close),
onPressed: () => downloader.delTask(task.episode),
),
],
);
case 4:
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: Icon(Icons.refresh, color: Colors.red),
onPressed: () => downloader.retryTask(task.episode),
),
IconButton(
icon: Icon(Icons.close),
onPressed: () => downloader.delTask(task.episode),
),
],
);
case 6:
return Row(mainAxisSize: MainAxisSize.min, children: [
IconButton(
icon: Icon(Icons.play_circle_filled),
onPressed: () => downloader.resumeTask(task.episode),
),
IconButton(
icon: Icon(Icons.close),
onPressed: () => downloader.delTask(task.episode),
),
]);
break;
default:
return SizedBox(
width: 10,
height: 10,
);
}
}
class _DownloadListState extends State<DownloadList> {
@override
Widget build(BuildContext context) {
return Consumer<DownloadState>(builder: (_, downloader, __) {
final tasks = downloader.episodeTasks
.where((task) => task.status.value != 3)
.toList();
return tasks.length > 0
? SliverPadding(
padding: EdgeInsets.symmetric(vertical: 5.0),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return ListTile(
onTap: () => Navigator.push(
context,
ScaleRoute(
page: EpisodeDetail(
episodeItem: tasks[index].episode,
)),
),
title: SizedBox(
height: 40,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
flex: 5,
child: Text(
tasks[index].episode.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
Expanded(
flex: 1,
child: tasks[index].progress >= 0 &&
tasks[index].status !=
DownloadTaskStatus.failed
? Container(
width: 40.0,
height: 20.0,
padding:
EdgeInsets.symmetric(horizontal: 2),
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(6)),
color: Colors.red),
child: Text(
'${tasks[index].progress}%',
textAlign: TextAlign.center,
maxLines: 1,
style: TextStyle(color: Colors.white),
))
: Container(
height: 40,
),
),
],
),
),
subtitle: SizedBox(
height: 2,
child: LinearProgressIndicator(
value: tasks[index].progress / 100,
),
),
leading: CircleAvatar(
radius: 20,
backgroundImage: tasks[index].episode.avatarImage),
trailing: _downloadButton(tasks[index], context),
);
},
childCount: tasks.length,
),
),
)
: SliverToBoxAdapter(
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:developer' as developer;
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:line_icons/line_icons.dart';
import 'package:provider/provider.dart';
import '../local_storage/key_value_storage.dart';
import '../service/opml_build.dart';
import '../settings/settting.dart';
import '../state/podcast_group.dart';
import '../state/refresh_podcast.dart';
import '../util/extension_helper.dart';
import 'about.dart';
class PopupMenu extends StatefulWidget {
@override
_PopupMenuState createState() => _PopupMenuState();
}
class _PopupMenuState extends State<PopupMenu> {
Future<String> _getRefreshDate(BuildContext context) async {
int refreshDate;
var refreshstorage = KeyValueStorage('refreshdate');
var i = await refreshstorage.getInt();
if (i == 0) {
var refreshstorage = KeyValueStorage('refreshdate');
await refreshstorage.saveInt(DateTime.now().millisecondsSinceEpoch);
refreshDate = DateTime.now().millisecondsSinceEpoch;
} else {
refreshDate = i;
}
return refreshDate.toDate(context);
// var date = DateTime.fromMillisecondsSinceEpoch(refreshDate);
// var difference = DateTime.now().difference(date);
// if (difference.inSeconds < 60) {
// return s.secondsAgo(difference.inSeconds);
// } else if (difference.inMinutes < 60) {
// return s.minsAgo(difference.inMinutes);
// } else if (difference.inHours < 24) {
// return s.hoursAgo(difference.inHours);
// } else if (difference.inDays < 7) {
// return s.daysAgo(difference.inDays);
// } else {
// return DateFormat.yMMMd()
// .format(DateTime.fromMillisecondsSinceEpoch(refreshDate));
// }
}
void _saveOmpl(String path) async {
var subscribeWorker = Provider.of<GroupList>(context, listen: false);
var rssExp = RegExp(r'^(https?):\/\/(.*)');
final s = context.s;
var file = File(path);
try {
final opml = file.readAsStringSync();
Map<String, List<OmplOutline>> data = PodcastsBackup.parseOPML(opml);
for (var entry in data.entries) {
var title = entry.key;
var list = entry.value.reversed;
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);
await Future.delayed(Duration(milliseconds: 200));
}
}
}
} catch (e) {
developer.log(e, name: 'OMPL parse error');
Fluttertoast.showToast(
msg: s.toastFileError,
gravity: ToastGravity.TOP,
);
}
}
void _getFilePath() async {
final s = context.s;
try {
var filePickResult =
await FilePicker.platform.pickFiles(type: FileType.any);
if (filePickResult == null) {
return;
}
Fluttertoast.showToast(
msg: s.toastReadFile,
gravity: ToastGravity.TOP,
);
final filePath = filePickResult.files.first.path;
_saveOmpl(filePath);
} on PlatformException catch (e) {
developer.log(e.toString(), name: 'Get OMPL file');
}
}
@override
Widget build(BuildContext context) {
var refreshWorker = Provider.of<RefreshWorker>(context, listen: false);
final s = context.s;
return Material(
color: Colors.transparent,
borderRadius: BorderRadius.circular(100),
clipBehavior: Clip.hardEdge,
child: PopupMenuButton<int>(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10))),
elevation: 1,
tooltip: s.menu,
itemBuilder: (context) => [
PopupMenuItem(
value: 1,
child: Container(
padding: EdgeInsets.only(left: 10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Icon(LineIcons.cloud_download_alt_solid),
Padding(
padding: EdgeInsets.symmetric(horizontal: 5.0),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
s.homeToprightMenuRefreshAll,
),
FutureBuilder<String>(
future: _getRefreshDate(context),
builder: (_, snapshot) {
if (snapshot.hasData) {
return Text(
snapshot.data,
style:
TextStyle(color: Colors.red, fontSize: 12),
);
} else {
return Center();
}
})
],
),
],
),
),
),
PopupMenuItem(
value: 2,
child: Container(
padding: EdgeInsets.only(left: 10),
child: Row(
children: <Widget>[
Icon(LineIcons.paperclip_solid),
Padding(
padding: EdgeInsets.symmetric(horizontal: 5.0),
),
Text(s.homeToprightMenuImportOMPL),
],
),
),
),
PopupMenuItem(
value: 4,
child: Container(
padding: EdgeInsets.only(left: 10),
child: Row(
children: <Widget>[
Icon(LineIcons.cog_solid),
Padding(
padding: EdgeInsets.symmetric(horizontal: 5.0),
),
Text(s.settings),
],
),
),
),
PopupMenuItem(
value: 5,
child: Container(
padding: EdgeInsets.only(left: 10),
child: Row(
children: <Widget>[
Icon(LineIcons.info_circle_solid),
Padding(
padding: EdgeInsets.symmetric(horizontal: 5.0),
),
Text(s.homeToprightMenuAbout),
],
),
),
),
],
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()));
}
},
),
);
}
}
import 'dart:async';
import 'dart:developer' as developer;
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:line_icons/line_icons.dart';
import 'package:provider/provider.dart';
import '../local_storage/key_value_storage.dart';
import '../service/opml_build.dart';
import '../settings/settting.dart';
import '../state/podcast_group.dart';
import '../state/refresh_podcast.dart';
import '../util/extension_helper.dart';
import 'about.dart';
class PopupMenu extends StatefulWidget {
@override
_PopupMenuState createState() => _PopupMenuState();
}
class _PopupMenuState extends State<PopupMenu> {
Future<String> _getRefreshDate(BuildContext context) async {
int refreshDate;
var refreshstorage = KeyValueStorage('refreshdate');
var i = await refreshstorage.getInt();
if (i == 0) {
var refreshstorage = KeyValueStorage('refreshdate');
await refreshstorage.saveInt(DateTime.now().millisecondsSinceEpoch);
refreshDate = DateTime.now().millisecondsSinceEpoch;
} else {
refreshDate = i;
}
return refreshDate.toDate(context);
}
void _saveOmpl(String path) async {
var subscribeWorker = Provider.of<GroupList>(context, listen: false);
var rssExp = RegExp(r'^(https?):\/\/(.*)');
final s = context.s;
var file = File(path);
try {
final opml = file.readAsStringSync();
Map<String, List<OmplOutline>> data = PodcastsBackup.parseOPML(opml);
for (var entry in data.entries) {
var title = entry.key;
var list = entry.value.reversed;
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);
await Future.delayed(Duration(milliseconds: 200));
}
}
}
} catch (e) {
developer.log(e, name: 'OMPL parse error');
Fluttertoast.showToast(
msg: s.toastFileError,
gravity: ToastGravity.TOP,
);
}
}
void _getFilePath() async {
final s = context.s;
try {
var filePickResult =
await FilePicker.platform.pickFiles(type: FileType.any);
if (filePickResult == null) {
return;
}
Fluttertoast.showToast(
msg: s.toastReadFile,
gravity: ToastGravity.TOP,
);
final filePath = filePickResult.files.first.path;
_saveOmpl(filePath);
} on PlatformException catch (e) {
developer.log(e.toString(), name: 'Get OMPL file');
}
}
@override
Widget build(BuildContext context) {
var refreshWorker = Provider.of<RefreshWorker>(context, listen: false);
final s = context.s;
return Material(
color: Colors.transparent,
borderRadius: BorderRadius.circular(100),
clipBehavior: Clip.hardEdge,
child: PopupMenuButton<int>(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10))),
elevation: 1,
tooltip: s.menu,
itemBuilder: (context) => [
PopupMenuItem(
value: 1,
child: Container(
padding: EdgeInsets.only(left: 10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Icon(LineIcons.cloud_download_alt_solid),
Padding(
padding: EdgeInsets.symmetric(horizontal: 5.0),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
s.homeToprightMenuRefreshAll,
),
FutureBuilder<String>(
future: _getRefreshDate(context),
builder: (_, snapshot) {
if (snapshot.hasData) {
return Text(
snapshot.data,
style:
TextStyle(color: Colors.red, fontSize: 12),
);
} else {
return Center();
}
})
],
),
],
),
),
),
PopupMenuItem(
value: 2,
child: Container(
padding: EdgeInsets.only(left: 10),
child: Row(
children: <Widget>[
Icon(LineIcons.paperclip_solid),
Padding(
padding: EdgeInsets.symmetric(horizontal: 5.0),
),
Text(s.homeToprightMenuImportOMPL),
],
),
),
),
PopupMenuItem(
value: 4,
child: Container(
padding: EdgeInsets.only(left: 10),
child: Row(
children: <Widget>[
Icon(LineIcons.cog_solid),
Padding(
padding: EdgeInsets.symmetric(horizontal: 5.0),
),
Text(s.settings),
],
),
),
),
PopupMenuItem(
value: 5,
child: Container(
padding: EdgeInsets.only(left: 10),
child: Row(
children: <Widget>[
Icon(LineIcons.info_circle_solid),
Padding(
padding: EdgeInsets.symmetric(horizontal: 5.0),
),
Text(s.homeToprightMenuAbout),
],
),
),
),
],
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:flutter/material.dart';
import 'package:provider/provider.dart';
import '../local_storage/key_value_storage.dart';
import '../local_storage/sqflite_localpodcast.dart';
import '../state/download_state.dart';
import '../state/podcast_group.dart';
import '../state/refresh_podcast.dart';
import '../util/extension_helper.dart';
class Import extends StatelessWidget {
Widget importColumn(String text, BuildContext context) {
return Container(
color: context.primaryColorDark,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBox(height: 2.0, child: LinearProgressIndicator()),
Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
height: 20.0,
alignment: Alignment.centerLeft,
child: Text(text),
),
]),
);
}
_autoDownloadNew(BuildContext context) async {
final dbHelper = DBHelper();
var downloader = Provider.of<DownloadState>(context, listen: false);
var result = await Connectivity().checkConnectivity();
var autoDownloadStorage = KeyValueStorage(autoDownloadNetworkKey);
var autoDownloadNetwork = await autoDownloadStorage.getInt();
if (autoDownloadNetwork == 1) {
var episodes = await dbHelper.getNewEpisodes('all');
// For safety
if (episodes.length < 100 && episodes.length > 0) {
for (var episode in episodes) {
await downloader.startTask(episode, showNotification: true);
}
}
} else if (result == ConnectivityResult.wifi) {
var episodes = await dbHelper.getNewEpisodes('all');
//For safety
if (episodes.length < 100 && episodes.length > 0) {
for (var episode in episodes) {
await downloader.startTask(episode, showNotification: true);
}
}
}
}
@override
Widget build(BuildContext context) {
final s = context.s;
var groupList = Provider.of<GroupList>(context, listen: false);
return Column(
children: <Widget>[
Consumer<GroupList>(
builder: (_, subscribeWorker, __) {
var item = subscribeWorker.currentSubscribeItem;
switch (item.subscribeState) {
case SubscribeState.start:
return importColumn(
s.notificationSubscribe(item.title), context);
case SubscribeState.subscribe:
return importColumn(s.notificaitonFatch(item.title), context);
case SubscribeState.fetch:
return importColumn(s.notificationSuccess(item.title), context);
case SubscribeState.exist:
return importColumn(
s.notificationSubscribeExisted(item.title), context);
case SubscribeState.error:
return importColumn(
s.notificationNetworkError(item.title), context);
default:
return Center();
}
},
),
Consumer<RefreshWorker>(
builder: (context, refreshWorker, child) {
var item = refreshWorker.currentRefreshItem;
if (refreshWorker.complete) {
groupList.updateGroups();
_autoDownloadNew(context);
}
switch (item.refreshState) {
case RefreshState.fetch:
return importColumn(s.notificationUpdate(item.title), context);
case RefreshState.error:
return importColumn(
s.notificationUpdateError(item.title), context);
default:
return Center();
}
},
)
],
);
}
}
import 'package:connectivity/connectivity.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../local_storage/key_value_storage.dart';
import '../local_storage/sqflite_localpodcast.dart';
import '../state/download_state.dart';
import '../state/podcast_group.dart';
import '../state/refresh_podcast.dart';
import '../util/extension_helper.dart';
class Import extends StatelessWidget {
Widget importColumn(String text, BuildContext context) {
return Container(
color: context.primaryColorDark,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBox(height: 2.0, child: LinearProgressIndicator()),
Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
height: 20.0,
alignment: Alignment.centerLeft,
child: Text(text),
),
]),
);
}
_autoDownloadNew(BuildContext context) async {
final dbHelper = DBHelper();
var downloader = Provider.of<DownloadState>(context, listen: false);
var result = await Connectivity().checkConnectivity();
var autoDownloadStorage = KeyValueStorage(autoDownloadNetworkKey);
var autoDownloadNetwork = await autoDownloadStorage.getInt();
if (autoDownloadNetwork == 1) {
var episodes = await dbHelper.getNewEpisodes('all');
// For safety
if (episodes.length < 100 && episodes.length > 0) {
for (var episode in episodes) {
await downloader.startTask(episode, showNotification: true);
}
}
} else if (result == ConnectivityResult.wifi) {
var episodes = await dbHelper.getNewEpisodes('all');
//For safety
if (episodes.length < 100 && episodes.length > 0) {
for (var episode in episodes) {
await downloader.startTask(episode, showNotification: true);
}
}
}
}
@override
Widget build(BuildContext context) {
final s = context.s;
var groupList = Provider.of<GroupList>(context, listen: false);
return Column(
children: <Widget>[
Consumer<GroupList>(
builder: (_, subscribeWorker, __) {
var item = subscribeWorker.currentSubscribeItem;
switch (item.subscribeState) {
case SubscribeState.start:
return importColumn(
s.notificationSubscribe(item.title), context);
case SubscribeState.subscribe:
return importColumn(s.notificaitonFatch(item.title), context);
case SubscribeState.fetch:
return importColumn(s.notificationSuccess(item.title), context);
case SubscribeState.exist:
return importColumn(
s.notificationSubscribeExisted(item.title), context);
case SubscribeState.error:
return importColumn(
s.notificationNetworkError(item.title), context);
default:
return Center();
}
},
),
Consumer<RefreshWorker>(
builder: (context, refreshWorker, child) {
var item = refreshWorker.currentRefreshItem;
if (refreshWorker.complete) {
groupList.updateGroups();
_autoDownloadNew(context);
}
switch (item.refreshState) {
case RefreshState.fetch:
return importColumn(s.notificationUpdate(item.title), context);
case RefreshState.error:
return importColumn(
s.notificationUpdateError(item.title), context);
default:
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:line_icons/line_icons.dart';
import 'package:provider/provider.dart';
import '../local_storage/key_value_storage.dart';
import '../service/search_api.dart';
import '../state/search_state.dart';
import '../type/search_api/search_genre.dart';
import '../type/search_api/searchpodcast.dart';
import '../util/custom_widget.dart';
import '../util/extension_helper.dart';
import 'search_podcast.dart';
class DiscoveryPage extends StatefulWidget {
DiscoveryPage({this.onTap, Key key}) : super(key: key);
final ValueChanged<String> onTap;
@override
DiscoveryPageState createState() => DiscoveryPageState();
}
class DiscoveryPageState extends State<DiscoveryPage> {
Genre _selectedGenre;
Genre get selectedGenre => _selectedGenre;
final List<OnlinePodcast> _podcastList = [];
Future _searchTopPodcast;
Future<List<String>> _getSearchHistory() {
final storage = KeyValueStorage(searchHistoryKey);
final history = storage.getStringList();
return history;
}
void backToHome() {
setState(() {
_selectedGenre = null;
});
}
@override
void initState() {
super.initState();
_searchTopPodcast = _getTopPodcasts(page: 1);
}
Widget _loadTopPodcasts() => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10), color: context.primaryColor),
width: 120,
margin: EdgeInsets.fromLTRB(10, 10, 0, 10),
padding: EdgeInsets.all(4),
alignment: Alignment.topCenter,
child: Column(
children: [
Expanded(
flex: 2,
child: Center(
child: Container(
height: 50,
width: 50,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: context.primaryColorDark,
),
alignment: Alignment.center,
child: SizedBox(
width: 20,
height: 2,
child: LinearProgressIndicator(value: 0),
),
),
),
),
Expanded(
flex: 1,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 80,
height: context.textTheme.bodyText1.fontSize,
decoration: BoxDecoration(
color: context.primaryColorDark,
borderRadius: BorderRadius.circular(4)),
),
SizedBox(height: 10),
Container(
width: 40,
height: context.textTheme.bodyText1.fontSize,
decoration: BoxDecoration(
color: context.primaryColorDark,
borderRadius: BorderRadius.circular(4)),
),
],
),
),
Expanded(
flex: 1,
child: Center(
child: SizedBox(
height: 32,
child: OutlineButton(
color: context.accentColor.withOpacity(0.5),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100.0),
side: BorderSide(color: Colors.grey[500])),
highlightedBorderColor: Colors.grey[500],
disabledTextColor: Colors.grey[500],
child: Text(context.s.subscribe),
disabledBorderColor: Colors.grey[500],
onPressed: () {}),
),
),
),
],
));
Widget _historyList() => FutureBuilder<List<String>>(
future: _getSearchHistory(),
initialData: [],
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data.isNotEmpty) {
final history = snapshot.data;
return SizedBox(
child: Wrap(
direction: Axis.horizontal,
children: history
.map<Widget>((e) => Padding(
padding: const EdgeInsets.fromLTRB(8, 2, 0, 0),
child: FlatButton.icon(
color:
Colors.accents[history.indexOf(e)].withAlpha(70),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100.0),
),
onPressed: () => widget.onTap(e),
label: Text(e),
icon: Icon(
Icons.search,
size: 20,
),
),
))
.toList(),
),
);
}
return SizedBox(
height: 0,
);
});
Future<List<OnlinePodcast>> _getTopPodcasts({int page}) async {
final searchEngine = ListenNotesSearch();
var searchResult = await searchEngine.fetchBestPodcast(
genre: '',
page: page,
);
final podcastTopList =
searchResult.podcasts.map((e) => e?.toOnlinePodcast).toList();
_podcastList.addAll(podcastTopList.cast());
return _podcastList;
}
Future<bool> _getHideDiscovery() async {
final storage = KeyValueStorage(hidePodcastDiscoveryKey);
return await storage.getBool(defaultValue: false);
}
@override
Widget build(BuildContext context) {
final searchState = context.watch<SearchState>();
return FutureBuilder<bool>(
future: _getHideDiscovery(),
initialData: true,
builder: (context, snapshot) => snapshot.data
? ScrollConfiguration(
behavior: NoGrowBehavior(),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_historyList(),
SizedBox(
height: 150,
child: Center(
child: Icon(
Icons.search,
size: 80,
color: Colors.grey[400],
),
),
),
SizedBox(
height: 50,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
LineIcons.microphone_solid,
size: 30,
color: Colors.lightBlue,
),
SizedBox(width: 50),
Icon(
LineIcons.broadcast_tower_solid,
size: 30,
color: Colors.deepPurple,
),
SizedBox(width: 50),
Icon(
LineIcons.rss_square_solid,
size: 30,
color: Colors.blueGrey,
),
],
),
),
Padding(
padding: EdgeInsets.fromLTRB(50, 20, 50, 20),
child: Center(
child: Text(
context.s.searchHelper,
textAlign: TextAlign.center,
style: context.textTheme.headline6
.copyWith(color: Colors.grey[400]),
),
),
),
],
),
),
)
: PodcastSlideup(
searchEngine: SearchEngine.listenNotes,
child: _selectedGenre == null
? SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_historyList(),
Padding(
padding: EdgeInsets.fromLTRB(20, 10, 10, 4),
child: Text('Popular',
style: context.textTheme.headline6
.copyWith(color: context.accentColor)),
),
SizedBox(
height: 200,
child: FutureBuilder<List<OnlinePodcast>>(
future: _searchTopPodcast,
builder: (context, snapshot) {
return ScrollConfiguration(
behavior: NoGrowBehavior(),
child: ListView(
scrollDirection: Axis.horizontal,
children: snapshot.hasData
? snapshot.data
.map<Widget>((podcast) {
return Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(
10),
color:
context.primaryColor),
width: 120,
margin: EdgeInsets.fromLTRB(
10, 10, 0, 10),
child: Material(
color: Colors.transparent,
borderRadius:
BorderRadius.circular(
10),
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: () {
searchState
.selectedPodcast =
podcast;
widget.onTap('');
},
child: Padding(
padding:
EdgeInsets.all(4.0),
child: Column(
children: [
Expanded(
flex: 2,
child: Center(
child: PodcastAvatar(
podcast)),
),
Expanded(
flex: 1,
child: Text(
podcast.title,
textAlign:
TextAlign
.center,
maxLines: 2,
overflow:
TextOverflow
.fade,
style: TextStyle(
fontWeight:
FontWeight
.bold),
),
),
Expanded(
flex: 1,
child: Center(
child: SizedBox(
height: 32,
child: SubscribeButton(
podcast)),
),
),
],
),
),
),
),
);
}).toList()
: [
_loadTopPodcasts(),
_loadTopPodcasts(),
_loadTopPodcasts(),
_loadTopPodcasts(),
]),
);
}),
),
Padding(
padding: EdgeInsets.fromLTRB(20, 10, 10, 4),
child: Text('Categories',
style: context.textTheme.headline6
.copyWith(color: context.accentColor)),
),
ListView(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
children: genres
.map<Widget>((e) => ListTile(
contentPadding:
EdgeInsets.fromLTRB(20, 0, 20, 0),
onTap: () {
widget.onTap('');
setState(() => _selectedGenre = e);
},
title: Text(e.name),
))
.toList(),
),
SizedBox(
height: 40,
child: Center(
child: Image(
image: context.brightness == Brightness.light
? AssetImage('assets/listennotes.png')
: AssetImage(
'assets/listennotes_light.png'),
height: 15,
),
),
)
],
),
)
: _TopPodcastList(genre: _selectedGenre),
),
);
}
}
class _TopPodcastList extends StatefulWidget {
final Genre genre;
_TopPodcastList({this.genre, Key key}) : super(key: key);
@override
__TopPodcastListState createState() => __TopPodcastListState();
}
class __TopPodcastListState extends State<_TopPodcastList> {
final List<OnlinePodcast> _podcastList = [];
Future _searchFuture;
bool _loading;
int _page;
Future<List<OnlinePodcast>> _getTopPodcasts({Genre genre, int page}) async {
final searchEngine = ListenNotesSearch();
var searchResult = await searchEngine.fetchBestPodcast(
genre: genre.id,
page: page,
);
final podcastTopList =
searchResult.podcasts.map((e) => e?.toOnlinePodcast).toList();
_podcastList.addAll(podcastTopList.cast());
_loading = false;
return _podcastList;
}
@override
void initState() {
_page = 1;
_searchFuture = _getTopPodcasts(genre: widget.genre, page: _page);
super.initState();
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: _searchFuture,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Container(
padding: EdgeInsets.only(top: 200),
alignment: Alignment.topCenter,
child: CircularProgressIndicator(),
);
}
final content = snapshot.data;
return CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.fromLTRB(20, 10, 10, 4),
child: Text(widget.genre.name,
style: context.textTheme.headline6
.copyWith(color: context.accentColor)),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return SearchResult(
onlinePodcast: content[index],
);
},
childCount: content.length,
),
),
SliverToBoxAdapter(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(top: 10.0, bottom: 20.0),
child: OutlineButton(
highlightedBorderColor: context.accentColor,
splashColor: context.accentColor.withOpacity(0.5),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(100))),
child: _loading
? SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
))
: Text(context.s.loadMore),
onPressed: () => _loading
? null
: setState(
() {
_loading = true;
_page++;
_searchFuture = _getTopPodcasts(
genre: widget.genre, page: _page);
},
),
),
)
],
),
)
],
);
},
);
}
}
import 'package:flutter/material.dart';
import 'package:line_icons/line_icons.dart';
import 'package:provider/provider.dart';
import '../local_storage/key_value_storage.dart';
import '../service/search_api.dart';
import '../state/search_state.dart';
import '../type/search_api/search_genre.dart';
import '../type/search_api/searchpodcast.dart';
import '../util/custom_widget.dart';
import '../util/extension_helper.dart';
import 'search_podcast.dart';
class DiscoveryPage extends StatefulWidget {
DiscoveryPage({this.onTap, Key key}) : super(key: key);
final ValueChanged<String> onTap;
@override
DiscoveryPageState createState() => DiscoveryPageState();
}
class DiscoveryPageState extends State<DiscoveryPage> {
Genre _selectedGenre;
Genre get selectedGenre => _selectedGenre;
final List<OnlinePodcast> _podcastList = [];
Future _searchTopPodcast;
Future<List<String>> _getSearchHistory() {
final storage = KeyValueStorage(searchHistoryKey);
final history = storage.getStringList();
return history;
}
void backToHome() {
setState(() {
_selectedGenre = null;
});
}
@override
void initState() {
super.initState();
_searchTopPodcast = _getTopPodcasts(page: 1);
}
Widget _loadTopPodcasts() => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10), color: context.primaryColor),
width: 120,
margin: EdgeInsets.fromLTRB(10, 10, 0, 10),
padding: EdgeInsets.all(4),
alignment: Alignment.topCenter,
child: Column(
children: [
Expanded(
flex: 2,
child: Center(
child: Container(
height: 50,
width: 50,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: context.primaryColorDark,
),
alignment: Alignment.center,
child: SizedBox(
width: 20,
height: 2,
child: LinearProgressIndicator(value: 0),
),
),
),
),
Expanded(
flex: 1,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 80,
height: context.textTheme.bodyText1.fontSize,
decoration: BoxDecoration(
color: context.primaryColorDark,
borderRadius: BorderRadius.circular(4)),
),
SizedBox(height: 10),
Container(
width: 40,
height: context.textTheme.bodyText1.fontSize,
decoration: BoxDecoration(
color: context.primaryColorDark,
borderRadius: BorderRadius.circular(4)),
),
],
),
),
Expanded(
flex: 1,
child: Center(
child: SizedBox(
height: 32,
child: OutlineButton(
color: context.accentColor.withOpacity(0.5),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100.0),
side: BorderSide(color: Colors.grey[500])),
highlightedBorderColor: Colors.grey[500],
disabledTextColor: Colors.grey[500],
child: Text(context.s.subscribe),
disabledBorderColor: Colors.grey[500],
onPressed: () {}),
),
),
),
],
));
Widget _historyList() => FutureBuilder<List<String>>(
future: _getSearchHistory(),
initialData: [],
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data.isNotEmpty) {
final history = snapshot.data;
return SizedBox(
child: Wrap(
direction: Axis.horizontal,
children: history
.map<Widget>((e) => Padding(
padding: const EdgeInsets.fromLTRB(8, 2, 0, 0),
child: FlatButton.icon(
color:
Colors.accents[history.indexOf(e)].withAlpha(70),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100.0),
),
onPressed: () => widget.onTap(e),
label: Text(e),
icon: Icon(
Icons.search,
size: 20,
),
),
))
.toList(),
),
);
}
return SizedBox(
height: 0,
);
});
Future<List<OnlinePodcast>> _getTopPodcasts({int page}) async {
final searchEngine = ListenNotesSearch();
var searchResult = await searchEngine.fetchBestPodcast(
genre: '',
page: page,
);
final podcastTopList =
searchResult.podcasts.map((e) => e?.toOnlinePodcast).toList();
_podcastList.addAll(podcastTopList.cast());
return _podcastList;
}
Future<bool> _getHideDiscovery() async {
final storage = KeyValueStorage(hidePodcastDiscoveryKey);
return await storage.getBool(defaultValue: false);
}
@override
Widget build(BuildContext context) {
final searchState = context.watch<SearchState>();
return FutureBuilder<bool>(
future: _getHideDiscovery(),
initialData: true,
builder: (context, snapshot) => snapshot.data
? ScrollConfiguration(
behavior: NoGrowBehavior(),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_historyList(),
SizedBox(
height: 150,
child: Center(
child: Icon(
Icons.search,
size: 80,
color: Colors.grey[400],
),
),
),
SizedBox(
height: 50,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
LineIcons.microphone_solid,
size: 30,
color: Colors.lightBlue,
),
SizedBox(width: 50),
Icon(
LineIcons.broadcast_tower_solid,
size: 30,
color: Colors.deepPurple,
),
SizedBox(width: 50),
Icon(
LineIcons.rss_square_solid,
size: 30,
color: Colors.blueGrey,
),
],
),
),
Padding(
padding: EdgeInsets.fromLTRB(50, 20, 50, 20),
child: Center(
child: Text(
context.s.searchHelper,
textAlign: TextAlign.center,
style: context.textTheme.headline6
.copyWith(color: Colors.grey[400]),
),
),
),
],
),
),
)
: PodcastSlideup(
searchEngine: SearchEngine.listenNotes,
child: _selectedGenre == null
? SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_historyList(),
Padding(
padding: EdgeInsets.fromLTRB(20, 10, 10, 4),
child: Text('Popular',
style: context.textTheme.headline6
.copyWith(color: context.accentColor)),
),
SizedBox(
height: 200,
child: FutureBuilder<List<OnlinePodcast>>(
future: _searchTopPodcast,
builder: (context, snapshot) {
return ScrollConfiguration(
behavior: NoGrowBehavior(),
child: ListView(
scrollDirection: Axis.horizontal,
children: snapshot.hasData
? snapshot.data
.map<Widget>((podcast) {
return Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(
10),
color:
context.primaryColor),
width: 120,
margin: EdgeInsets.fromLTRB(
10, 10, 0, 10),
child: Material(
color: Colors.transparent,
borderRadius:
BorderRadius.circular(
10),
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: () {
searchState
.selectedPodcast =
podcast;
widget.onTap('');
},
child: Padding(
padding:
EdgeInsets.all(4.0),
child: Column(
children: [
Expanded(
flex: 2,
child: Center(
child: PodcastAvatar(
podcast)),
),
Expanded(
flex: 1,
child: Text(
podcast.title,
textAlign:
TextAlign
.center,
maxLines: 2,
overflow:
TextOverflow
.fade,
style: TextStyle(
fontWeight:
FontWeight
.bold),
),
),
Expanded(
flex: 1,
child: Center(
child: SizedBox(
height: 32,
child: SubscribeButton(
podcast)),
),
),
],
),
),
),
),
);
}).toList()
: [
_loadTopPodcasts(),
_loadTopPodcasts(),
_loadTopPodcasts(),
_loadTopPodcasts(),
]),
);
}),
),
Padding(
padding: EdgeInsets.fromLTRB(20, 10, 10, 4),
child: Text('Categories',
style: context.textTheme.headline6
.copyWith(color: context.accentColor)),
),
ListView(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
children: genres
.map<Widget>((e) => ListTile(
contentPadding:
EdgeInsets.fromLTRB(20, 0, 20, 0),
onTap: () {
widget.onTap('');
setState(() => _selectedGenre = e);
},
title: Text(e.name),
))
.toList(),
),
SizedBox(
height: 40,
child: Center(
child: Image(
image: context.brightness == Brightness.light
? AssetImage('assets/listennotes.png')
: AssetImage(
'assets/listennotes_light.png'),
height: 15,
),
),
)
],
),
)
: _TopPodcastList(genre: _selectedGenre),
),
);
}
}
class _TopPodcastList extends StatefulWidget {
final Genre genre;
_TopPodcastList({this.genre, Key key}) : super(key: key);
@override
__TopPodcastListState createState() => __TopPodcastListState();
}
class __TopPodcastListState extends State<_TopPodcastList> {
final List<OnlinePodcast> _podcastList = [];
Future _searchFuture;
bool _loading;
int _page;
Future<List<OnlinePodcast>> _getTopPodcasts({Genre genre, int page}) async {
final searchEngine = ListenNotesSearch();
var searchResult = await searchEngine.fetchBestPodcast(
genre: genre.id,
page: page,
);
final podcastTopList =
searchResult.podcasts.map((e) => e?.toOnlinePodcast).toList();
_podcastList.addAll(podcastTopList.cast());
_loading = false;
return _podcastList;
}
@override
void initState() {
_page = 1;
_searchFuture = _getTopPodcasts(genre: widget.genre, page: _page);
super.initState();
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: _searchFuture,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Container(
padding: EdgeInsets.only(top: 200),
alignment: Alignment.topCenter,
child: CircularProgressIndicator(),
);
}
final content = snapshot.data;
return CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.fromLTRB(20, 10, 10, 4),
child: Text(widget.genre.name,
style: context.textTheme.headline6
.copyWith(color: context.accentColor)),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return SearchResult(
onlinePodcast: content[index],
);
},
childCount: content.length,
),
),
SliverToBoxAdapter(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(top: 10.0, bottom: 20.0),
child: OutlineButton(
highlightedBorderColor: context.accentColor,
splashColor: context.accentColor.withOpacity(0.5),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(100))),
child: _loading
? SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
))
: Text(context.s.loadMore),
onPressed: () => _loading
? null
: setState(
() {
_loading = true;
_page++;
_searchFuture = _getTopPodcasts(
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/services.dart';
import 'package:provider/provider.dart';
import '../home/home.dart';
import '../state/setting_state.dart';
import '../util/extension_helper.dart';
import '../util/pageroute.dart';
import 'firstpage.dart';
import 'fourthpage.dart';
import 'secondpage.dart';
import 'thirdpage.dart';
enum Goto { home, settings }
class SlideIntro extends StatefulWidget {
final Goto goto;
SlideIntro({this.goto, Key key}) : super(key: key);
@override
_SlideIntroState createState() => _SlideIntroState();
}
class _SlideIntroState extends State<SlideIntro> {
final List<BoxShadow> _customShadow = [
BoxShadow(blurRadius: 2, offset: Offset(-2, -2), color: Colors.white54),
BoxShadow(
blurRadius: 8,
offset: Offset(2, 2),
color: Colors.grey[600].withOpacity(0.4))
];
PageController _controller;
double _position;
@override
void initState() {
super.initState();
_position = 0;
_controller = PageController()
..addListener(() {
setState(() {
_position = _controller.page;
});
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarBrightness: Brightness.light,
systemNavigationBarIconBrightness: Brightness.dark),
child: Scaffold(
backgroundColor: Colors.grey[100],
body: Container(
child: Stack(
children: <Widget>[
PageView(
physics: const PageScrollPhysics(),
controller: _controller,
scrollDirection: Axis.horizontal,
children: <Widget>[
FirstPage(),
SecondPage(),
ThirdPage(),
FourthPage(),
],
),
Positioned(
bottom: 0,
left: 0,
child: Container(
color: Colors.grey[100].withOpacity(0.5),
width: MediaQuery.of(context).size.width,
// alignment: Alignment.center,
padding:
EdgeInsets.only(left: 40, right: 20, bottom: 30, top: 20),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
child: Container(
alignment: Alignment.centerLeft,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
alignment: Alignment.center,
child: _position < 0.2
? Text(
'1',
style: TextStyle(
color: Color.fromRGBO(
35, 204, 198, 1)),
)
: Center(),
margin: EdgeInsets.symmetric(horizontal: 10),
height: _position > 1
? 10
: (1 - _position) * 10 + 10,
width: _position > 1
? 10
: (1 - _position) * 10 + 10,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
boxShadow: _customShadow),
),
Container(
child: _position < 1.2 && _position > 0.8
? Text('2',
style: TextStyle(
color: Color.fromRGBO(
77, 145, 190, 1)))
: Center(),
alignment: Alignment.center,
margin: EdgeInsets.symmetric(horizontal: 10),
height: _position > 2
? 10
: 20 - (_position - 1).abs() * 10,
width: _position > 2
? 10
: 20 - (_position - 1).abs() * 10,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
boxShadow: _customShadow),
),
Container(
child: _position < 2.2 && _position > 1.8
? Text('3',
style: TextStyle(
color: Color.fromRGBO(
35, 204, 198, 1)))
: Center(),
alignment: Alignment.center,
margin: EdgeInsets.symmetric(horizontal: 10),
height: _position < 1
? 10
: 20 - (_position - 2).abs() * 10,
width: _position < 1
? 10
: 20 - (_position - 2).abs() * 10,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
boxShadow: _customShadow),
),
Container(
child: _position > 2.8
? Text(
'4',
style: TextStyle(
color: Color.fromRGBO(
77, 145, 190, 1)),
)
: Center(),
alignment: Alignment.center,
margin: EdgeInsets.symmetric(horizontal: 10),
height: _position < 2
? 10
: 20 - (3 - _position) * 10,
width: _position < 2
? 10
: 20 - (3 - _position) * 10,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
boxShadow: _customShadow),
),
],
),
),
),
Container(
alignment: Alignment.center,
height: 40,
width: 80,
decoration: BoxDecoration(
border: Border.all(width: 1, color: Colors.white),
borderRadius: BorderRadius.all(Radius.circular(20)),
color: Colors.white,
boxShadow: _customShadow,
),
child: Material(
color: Colors.transparent,
child: _position < 2.5
? InkWell(
borderRadius:
BorderRadius.all(Radius.circular(20)),
onTap: () => _controller.animateToPage(
_position.toInt() + 1,
duration: Duration(milliseconds: 200),
curve: Curves.linear),
child: SizedBox(
height: 40,
width: 80,
child: Center(
child: Text(context.s.next,
style: TextStyle(
color: Colors.black)))))
: InkWell(
borderRadius:
BorderRadius.all(Radius.circular(20)),
onTap: () {
if (widget.goto == Goto.home) {
Navigator.push(context,
SlideLeftRoute(page: Home()));
Provider.of<SettingState>(context,
listen: false)
.saveShowIntro(1);
} else if (widget.goto == Goto.settings) {
Navigator.pop(context);
}
},
child: SizedBox(
height: 40,
width: 80,
child: Center(
child: Text(context.s.done,
style: TextStyle(
color: Colors.black))))),
),
),
],
),
),
),
],
),
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import '../home/home.dart';
import '../state/setting_state.dart';
import '../util/extension_helper.dart';
import '../util/pageroute.dart';
import 'firstpage.dart';
import 'fourthpage.dart';
import 'secondpage.dart';
import 'thirdpage.dart';
enum Goto { home, settings }
class SlideIntro extends StatefulWidget {
final Goto goto;
SlideIntro({this.goto, Key key}) : super(key: key);
@override
_SlideIntroState createState() => _SlideIntroState();
}
class _SlideIntroState extends State<SlideIntro> {
final List<BoxShadow> _customShadow = [
BoxShadow(blurRadius: 2, offset: Offset(-2, -2), color: Colors.white54),
BoxShadow(
blurRadius: 8,
offset: Offset(2, 2),
color: Colors.grey[600].withOpacity(0.4))
];
PageController _controller;
double _position;
@override
void initState() {
super.initState();
_position = 0;
_controller = PageController()
..addListener(() {
setState(() {
_position = _controller.page;
});
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarBrightness: Brightness.light,
systemNavigationBarIconBrightness: Brightness.dark),
child: Scaffold(
backgroundColor: Colors.grey[100],
body: Container(
child: Stack(
children: <Widget>[
PageView(
physics: const PageScrollPhysics(),
controller: _controller,
scrollDirection: Axis.horizontal,
children: <Widget>[
FirstPage(),
SecondPage(),
ThirdPage(),
FourthPage(),
],
),
Positioned(
bottom: 0,
left: 0,
child: Container(
color: Colors.grey[100].withOpacity(0.5),
width: MediaQuery.of(context).size.width,
// alignment: Alignment.center,
padding:
EdgeInsets.only(left: 40, right: 20, bottom: 30, top: 20),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
child: Container(
alignment: Alignment.centerLeft,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
alignment: Alignment.center,
child: _position < 0.2
? Text(
'1',
style: TextStyle(
color: Color.fromRGBO(
35, 204, 198, 1)),
)
: Center(),
margin: EdgeInsets.symmetric(horizontal: 10),
height: _position > 1
? 10
: (1 - _position) * 10 + 10,
width: _position > 1
? 10
: (1 - _position) * 10 + 10,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
boxShadow: _customShadow),
),
Container(
child: _position < 1.2 && _position > 0.8
? Text('2',
style: TextStyle(
color: Color.fromRGBO(
77, 145, 190, 1)))
: Center(),
alignment: Alignment.center,
margin: EdgeInsets.symmetric(horizontal: 10),
height: _position > 2
? 10
: 20 - (_position - 1).abs() * 10,
width: _position > 2
? 10
: 20 - (_position - 1).abs() * 10,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
boxShadow: _customShadow),
),
Container(
child: _position < 2.2 && _position > 1.8
? Text('3',
style: TextStyle(
color: Color.fromRGBO(
35, 204, 198, 1)))
: Center(),
alignment: Alignment.center,
margin: EdgeInsets.symmetric(horizontal: 10),
height: _position < 1
? 10
: 20 - (_position - 2).abs() * 10,
width: _position < 1
? 10
: 20 - (_position - 2).abs() * 10,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
boxShadow: _customShadow),
),
Container(
child: _position > 2.8
? Text(
'4',
style: TextStyle(
color: Color.fromRGBO(
77, 145, 190, 1)),
)
: Center(),
alignment: Alignment.center,
margin: EdgeInsets.symmetric(horizontal: 10),
height: _position < 2
? 10
: 20 - (3 - _position) * 10,
width: _position < 2
? 10
: 20 - (3 - _position) * 10,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
boxShadow: _customShadow),
),
],
),
),
),
Container(
alignment: Alignment.center,
height: 40,
width: 80,
decoration: BoxDecoration(
border: Border.all(width: 1, color: Colors.white),
borderRadius: BorderRadius.all(Radius.circular(20)),
color: Colors.white,
boxShadow: _customShadow,
),
child: Material(
color: Colors.transparent,
child: _position < 2.5
? InkWell(
borderRadius:
BorderRadius.all(Radius.circular(20)),
onTap: () => _controller.animateToPage(
_position.toInt() + 1,
duration: Duration(milliseconds: 200),
curve: Curves.linear),
child: SizedBox(
height: 40,
width: 80,
child: Center(
child: Text(context.s.next,
style: TextStyle(
color: Colors.black)))))
: InkWell(
borderRadius:
BorderRadius.all(Radius.circular(20)),
onTap: () {
if (widget.goto == Goto.home) {
Navigator.push(context,
SlideLeftRoute(page: Home()));
Provider.of<SettingState>(context,
listen: false)
.saveShowIntro(1);
} else if (widget.goto == Goto.settings) {
Navigator.pop(context);
}
},
child: SizedBox(
height: 40,
width: 80,
child: Center(
child: Text(context.s.done,
style: TextStyle(
color: Colors.black))))),
),
),
],
),
),
),
],
),
),
),
);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,442 +1,442 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';
import 'package:line_icons/line_icons.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import '../local_storage/sqflite_localpodcast.dart';
import '../state/download_state.dart';
import '../type/episodebrief.dart';
import '../util/custom_widget.dart';
import '../util/extension_helper.dart';
class DownloadsManage extends StatefulWidget {
@override
_DownloadsManageState createState() => _DownloadsManageState();
}
class _DownloadsManageState extends State<DownloadsManage> {
//Downloaded size
int _size;
int _mode;
//Downloaded files
int _fileNum;
bool _clearing;
bool _onlyListened;
List<EpisodeBrief> _selectedList;
Future<List<EpisodeBrief>> _getDownloadedEpisode(int mode) async {
var episodes = <EpisodeBrief>[];
var dbHelper = DBHelper();
episodes = await dbHelper.getDownloadedEpisode(mode);
return episodes;
}
Future<int> _isListened(EpisodeBrief episode) async {
var dbHelper = DBHelper();
return await dbHelper.isListened(episode.enclosureUrl);
}
Future<void> _getStorageSize() async {
_size = 0;
_fileNum = 0;
final dirs = await getExternalStorageDirectories();
for (var dir in dirs) {
dir.list().forEach((d) {
var fileDir = Directory(d.path);
fileDir.list().forEach((file) async {
await File(file.path).stat().then((value) {
_size += value.size;
_fileNum += 1;
if (mounted) setState(() {});
});
});
});
}
}
Future<void> _delSelectedEpisodes() async {
setState(() => _clearing = true);
// await Future.forEach(_selectedList, (EpisodeBrief episode) async
for (var episode in _selectedList) {
var downloader = Provider.of<DownloadState>(context, listen: false);
await downloader.delTask(episode);
if (mounted) setState(() {});
}
await Future.delayed(Duration(seconds: 1));
if (mounted) {
setState(() {
_clearing = false;
});
}
await Future.delayed(Duration(seconds: 1));
if (mounted) setState(() => _selectedList = []);
_getStorageSize();
}
String _downloadDateToString(BuildContext context,
{int downloadDate, int pubDate}) {
final s = context.s;
var date = DateTime.fromMillisecondsSinceEpoch(downloadDate);
var diffrence = DateTime.now().toUtc().difference(date);
if (diffrence.inHours < 24) {
return s.hoursAgo(diffrence.inHours);
} else if (diffrence.inDays < 7) {
return s.daysAgo(diffrence.inDays);
} else {
return DateFormat.yMMMd().format(
DateTime.fromMillisecondsSinceEpoch(pubDate, isUtc: true).toLocal());
}
}
int sumSelected() {
var sum = 0;
if (_selectedList.length == 0) {
return sum;
} else {
for (var episode in _selectedList) {
sum += episode.enclosureLength;
}
return sum;
}
}
@override
void initState() {
super.initState();
_clearing = false;
_selectedList = [];
_mode = 0;
_onlyListened = false;
_getStorageSize();
}
@override
Widget build(BuildContext context) {
final s = context.s;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness,
),
child: Scaffold(
appBar: AppBar(
leading: CustomBackButton(),
elevation: 0,
backgroundColor: context.primaryColor,
),
body: SafeArea(
child: Stack(
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
height: 140.0,
color: context.primaryColor,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 10),
child: RichText(
text: TextSpan(
text: 'Total ',
style: TextStyle(
color: context.accentColor,
fontSize: 20,
),
children: <TextSpan>[
TextSpan(
text: _fileNum.toString(),
style: GoogleFonts.cairo(
textStyle: TextStyle(
color: context.accentColor,
fontSize: 40,
)),
),
TextSpan(
text: _fileNum < 2
? ' episode'
: ' episodes ',
style: TextStyle(
color: context.accentColor,
fontSize: 20,
)),
TextSpan(
text: (_size ~/ 1000000) < 1000
? (_size ~/ 1000000).toString()
: (_size / 1000000000).toStringAsFixed(1),
style: GoogleFonts.cairo(
textStyle: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 50,
)),
),
TextSpan(
text:
(_size ~/ 1000000) < 1000 ? 'Mb' : 'Gb',
style: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 20,
)),
],
),
),
),
Spacer(),
SizedBox(
height: 40,
child: Row(
children: [
Material(
color: Colors.transparent,
child: PopupMenuButton<int>(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10))),
elevation: 1,
tooltip: s.homeSubMenuSortBy,
child: Container(
height: 40,
padding:
EdgeInsets.symmetric(horizontal: 20),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(s.homeSubMenuSortBy),
Padding(
padding: EdgeInsets.symmetric(
horizontal: 5),
),
Icon(
_mode == 0
? LineIcons
.hourglass_start_solid
: _mode == 1
? LineIcons
.hourglass_half_solid
: LineIcons.save,
size: 18,
)
],
)),
itemBuilder: (context) => [
PopupMenuItem(
value: 0,
child: Text(s.newestFirst),
),
PopupMenuItem(
value: 1,
child: Text(s.oldestFirst),
),
PopupMenuItem(
value: 2,
child: Text(s.size),
),
],
onSelected: (value) {
if (value == 0) {
setState(() => _mode = 0);
} else if (value == 1) {
setState(() => _mode = 1);
} else if (value == 2) {
setState(() => _mode = 2);
}
},
),
),
//Spacer(),
Material(
color: Colors.transparent,
child: InkWell(
onTap: () => setState(() {
_onlyListened = !_onlyListened;
}),
child: Row(
children: [
Padding(
padding:
EdgeInsets.symmetric(horizontal: 5),
),
Text(s.listened),
Transform.scale(
scale: 0.8,
child: Checkbox(
value: _onlyListened,
onChanged: (value) {
setState(() {
_onlyListened = value;
});
}),
),
],
),
),
),
],
),
),
],
),
),
Expanded(
child: FutureBuilder<List<EpisodeBrief>>(
future: _getDownloadedEpisode(_mode),
initialData: [],
builder: (context, snapshot) {
var _episodes = snapshot.data;
return ListView.builder(
itemCount: _episodes.length,
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemBuilder: (context, index) {
return FutureBuilder(
future: _isListened(_episodes[index]),
initialData: 0,
builder: (context, snapshot) {
return (_onlyListened && snapshot.data == 0)
? Center()
: Column(
children: <Widget>[
ListTile(
onTap: () {
if (_selectedList.contains(
_episodes[index])) {
setState(() => _selectedList
.removeWhere((episode) =>
episode
.enclosureUrl ==
_episodes[index]
.enclosureUrl));
} else {
setState(() => _selectedList
.add(_episodes[index]));
}
},
leading: CircleAvatar(
backgroundImage:
_episodes[index]
.avatarImage),
title: Text(
_episodes[index].title,
maxLines: 1,
overflow:
TextOverflow.ellipsis,
),
subtitle: Row(
children: [
Text(_downloadDateToString(
context,
downloadDate:
_episodes[index]
.downloadDate,
pubDate:
_episodes[index]
.pubDate)),
SizedBox(width: 20),
if (_episodes[index]
.enclosureLength !=
0)
Text(
'${(_episodes[index].enclosureLength) ~/ 1000000} Mb'),
],
),
trailing: Checkbox(
value: _selectedList.contains(
_episodes[index]),
onChanged: (boo) {
if (boo) {
setState(() =>
_selectedList.add(
_episodes[
index]));
} else {
setState(() => _selectedList
.removeWhere((episode) =>
episode
.enclosureUrl ==
_episodes[index]
.enclosureUrl));
}
},
),
),
Divider(
height: 2,
),
],
);
});
});
},
),
)
],
),
AnimatedPositioned(
duration: Duration(milliseconds: 800),
curve: Curves.elasticInOut,
left: context.width / 2 - 50,
bottom: _selectedList.length == 0 ? -100 : 30,
child: InkWell(
onTap: _delSelectedEpisodes,
child: Stack(
alignment: _clearing
? Alignment.centerLeft
: Alignment.centerRight,
children: <Widget>[
Container(
alignment: Alignment.center,
width: 100,
height: 40,
decoration: BoxDecoration(
borderRadius:
BorderRadius.all(Radius.circular(20.0)),
color: Theme.of(context).accentColor,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Icon(
LineIcons.trash_alt_solid,
color: Colors.white,
),
Text('${sumSelected() ~/ 1000000}Mb',
style: TextStyle(color: Colors.white)),
],
),
),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: AnimatedContainer(
duration: Duration(milliseconds: 500),
alignment: Alignment.center,
width: _clearing ? 100 : 0,
height: _clearing ? 40 : 0,
decoration: BoxDecoration(
borderRadius:
BorderRadius.all(Radius.circular(20.0)),
color: Colors.red.withOpacity(0.6),
),
),
),
],
)),
),
],
),
),
),
);
}
}
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';
import 'package:line_icons/line_icons.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import '../local_storage/sqflite_localpodcast.dart';
import '../state/download_state.dart';
import '../type/episodebrief.dart';
import '../util/custom_widget.dart';
import '../util/extension_helper.dart';
class DownloadsManage extends StatefulWidget {
@override
_DownloadsManageState createState() => _DownloadsManageState();
}
class _DownloadsManageState extends State<DownloadsManage> {
//Downloaded size
int _size;
int _mode;
//Downloaded files
int _fileNum;
bool _clearing;
bool _onlyListened;
List<EpisodeBrief> _selectedList;
Future<List<EpisodeBrief>> _getDownloadedEpisode(int mode) async {
var episodes = <EpisodeBrief>[];
var dbHelper = DBHelper();
episodes = await dbHelper.getDownloadedEpisode(mode);
return episodes;
}
Future<int> _isListened(EpisodeBrief episode) async {
var dbHelper = DBHelper();
return await dbHelper.isListened(episode.enclosureUrl);
}
Future<void> _getStorageSize() async {
_size = 0;
_fileNum = 0;
final dirs = await getExternalStorageDirectories();
for (var dir in dirs) {
dir.list().forEach((d) {
var fileDir = Directory(d.path);
fileDir.list().forEach((file) async {
await File(file.path).stat().then((value) {
_size += value.size;
_fileNum += 1;
if (mounted) setState(() {});
});
});
});
}
}
Future<void> _delSelectedEpisodes() async {
setState(() => _clearing = true);
// await Future.forEach(_selectedList, (EpisodeBrief episode) async
for (var episode in _selectedList) {
var downloader = Provider.of<DownloadState>(context, listen: false);
await downloader.delTask(episode);
if (mounted) setState(() {});
}
await Future.delayed(Duration(seconds: 1));
if (mounted) {
setState(() {
_clearing = false;
});
}
await Future.delayed(Duration(seconds: 1));
if (mounted) setState(() => _selectedList = []);
_getStorageSize();
}
String _downloadDateToString(BuildContext context,
{int downloadDate, int pubDate}) {
final s = context.s;
var date = DateTime.fromMillisecondsSinceEpoch(downloadDate);
var diffrence = DateTime.now().toUtc().difference(date);
if (diffrence.inHours < 24) {
return s.hoursAgo(diffrence.inHours);
} else if (diffrence.inDays < 7) {
return s.daysAgo(diffrence.inDays);
} else {
return DateFormat.yMMMd().format(
DateTime.fromMillisecondsSinceEpoch(pubDate, isUtc: true).toLocal());
}
}
int sumSelected() {
var sum = 0;
if (_selectedList.length == 0) {
return sum;
} else {
for (var episode in _selectedList) {
sum += episode.enclosureLength;
}
return sum;
}
}
@override
void initState() {
super.initState();
_clearing = false;
_selectedList = [];
_mode = 0;
_onlyListened = false;
_getStorageSize();
}
@override
Widget build(BuildContext context) {
final s = context.s;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: Theme.of(context).primaryColor,
systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness,
),
child: Scaffold(
appBar: AppBar(
leading: CustomBackButton(),
elevation: 0,
backgroundColor: context.primaryColor,
),
body: SafeArea(
child: Stack(
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
height: 140.0,
color: context.primaryColor,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 10),
child: RichText(
text: TextSpan(
text: 'Total ',
style: TextStyle(
color: context.accentColor,
fontSize: 20,
),
children: <TextSpan>[
TextSpan(
text: _fileNum.toString(),
style: GoogleFonts.cairo(
textStyle: TextStyle(
color: context.accentColor,
fontSize: 40,
)),
),
TextSpan(
text: _fileNum < 2
? ' episode'
: ' episodes ',
style: TextStyle(
color: context.accentColor,
fontSize: 20,
)),
TextSpan(
text: (_size ~/ 1000000) < 1000
? (_size ~/ 1000000).toString()
: (_size / 1000000000).toStringAsFixed(1),
style: GoogleFonts.cairo(
textStyle: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 50,
)),
),
TextSpan(
text:
(_size ~/ 1000000) < 1000 ? 'Mb' : 'Gb',
style: TextStyle(
color: Theme.of(context).accentColor,
fontSize: 20,
)),
],
),
),
),
Spacer(),
SizedBox(
height: 40,
child: Row(
children: [
Material(
color: Colors.transparent,
child: PopupMenuButton<int>(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10))),
elevation: 1,
tooltip: s.homeSubMenuSortBy,
child: Container(
height: 40,
padding:
EdgeInsets.symmetric(horizontal: 20),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(s.homeSubMenuSortBy),
Padding(
padding: EdgeInsets.symmetric(
horizontal: 5),
),
Icon(
_mode == 0
? LineIcons
.hourglass_start_solid
: _mode == 1
? LineIcons
.hourglass_half_solid
: LineIcons.save,
size: 18,
)
],
)),
itemBuilder: (context) => [
PopupMenuItem(
value: 0,
child: Text(s.newestFirst),
),
PopupMenuItem(
value: 1,
child: Text(s.oldestFirst),
),
PopupMenuItem(
value: 2,
child: Text(s.size),
),
],
onSelected: (value) {
if (value == 0) {
setState(() => _mode = 0);
} else if (value == 1) {
setState(() => _mode = 1);
} else if (value == 2) {
setState(() => _mode = 2);
}
},
),
),
//Spacer(),
Material(
color: Colors.transparent,
child: InkWell(
onTap: () => setState(() {
_onlyListened = !_onlyListened;
}),
child: Row(
children: [
Padding(
padding:
EdgeInsets.symmetric(horizontal: 5),
),
Text(s.listened),
Transform.scale(
scale: 0.8,
child: Checkbox(
value: _onlyListened,
onChanged: (value) {
setState(() {
_onlyListened = value;
});
}),
),
],
),
),
),
],
),
),
],
),
),
Expanded(
child: FutureBuilder<List<EpisodeBrief>>(
future: _getDownloadedEpisode(_mode),
initialData: [],
builder: (context, snapshot) {
var _episodes = snapshot.data;
return ListView.builder(
itemCount: _episodes.length,
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemBuilder: (context, index) {
return FutureBuilder(
future: _isListened(_episodes[index]),
initialData: 0,
builder: (context, snapshot) {
return (_onlyListened && snapshot.data == 0)
? Center()
: Column(
children: <Widget>[
ListTile(
onTap: () {
if (_selectedList.contains(
_episodes[index])) {
setState(() => _selectedList
.removeWhere((episode) =>
episode
.enclosureUrl ==
_episodes[index]
.enclosureUrl));
} else {
setState(() => _selectedList
.add(_episodes[index]));
}
},
leading: CircleAvatar(
backgroundImage:
_episodes[index]
.avatarImage),
title: Text(
_episodes[index].title,
maxLines: 1,
overflow:
TextOverflow.ellipsis,
),
subtitle: Row(
children: [
Text(_downloadDateToString(
context,
downloadDate:
_episodes[index]
.downloadDate,
pubDate:
_episodes[index]
.pubDate)),
SizedBox(width: 20),
if (_episodes[index]
.enclosureLength !=
0)
Text(
'${(_episodes[index].enclosureLength) ~/ 1000000} Mb'),
],
),
trailing: Checkbox(
value: _selectedList.contains(
_episodes[index]),
onChanged: (boo) {
if (boo) {
setState(() =>
_selectedList.add(
_episodes[
index]));
} else {
setState(() => _selectedList
.removeWhere((episode) =>
episode
.enclosureUrl ==
_episodes[index]
.enclosureUrl));
}
},
),
),
Divider(
height: 2,
),
],
);
});
});
},
),
)
],
),
AnimatedPositioned(
duration: Duration(milliseconds: 800),
curve: Curves.elasticInOut,
left: context.width / 2 - 50,
bottom: _selectedList.length == 0 ? -100 : 30,
child: InkWell(
onTap: _delSelectedEpisodes,
child: Stack(
alignment: _clearing
? Alignment.centerLeft
: Alignment.centerRight,
children: <Widget>[
Container(
alignment: Alignment.center,
width: 100,
height: 40,
decoration: BoxDecoration(
borderRadius:
BorderRadius.all(Radius.circular(20.0)),
color: Theme.of(context).accentColor,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Icon(
LineIcons.trash_alt_solid,
color: Colors.white,
),
Text('${sumSelected() ~/ 1000000}Mb',
style: TextStyle(color: Colors.white)),
],
),
),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: AnimatedContainer(
duration: Duration(milliseconds: 500),
alignment: Alignment.center,
width: _clearing ? 100 : 0,
height: _clearing ? 40 : 0,
decoration: BoxDecoration(
borderRadius:
BorderRadius.all(Radius.circular(20.0)),
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/services.dart';
import 'package:intl/intl.dart';
import 'package:intl/intl_standalone.dart';
import '../generated/l10n.dart';
import '../local_storage/key_value_storage.dart';
import '../util/extension_helper.dart';
class LanguagesSetting extends StatefulWidget {
const LanguagesSetting({Key key}) : super(key: key);
@override
_LanguagesSettingState createState() => _LanguagesSettingState();
}
class _LanguagesSettingState extends State<LanguagesSetting> {
_setLocale(Locale locale, {bool systemDefault = false}) async {
var localeStorage = KeyValueStorage(localeKey);
if (systemDefault) {
await localeStorage.saveStringList([]);
await findSystemLocale();
var systemLanCode;
final list = Intl.systemLocale.split('_');
if (list.length == 2) {
systemLanCode = list.first;
} else if (list.length == 3) {
systemLanCode = '${list[0]}_${list[1]}';
} else {
systemLanCode = 'en';
}
await S.load(Locale(systemLanCode));
if (mounted) {
setState(() {});
}
} else {
await localeStorage
.saveStringList([locale.languageCode, locale.countryCode]);
await S.load(locale);
if (mounted) {
setState(() {});
}
}
}
@override
void initState() {
super.initState();
findSystemLocale();
}
@override
Widget build(BuildContext context) {
final s = context.s;
return Column(
children: [
ListTile(
title: Text(
s.systemDefault,
style: TextStyle(
color: Intl.systemLocale.contains(Intl.getCurrentLocale())
? context.accentColor
: null),
),
onTap: () =>
_setLocale(Locale(Intl.systemLocale), systemDefault: true),
contentPadding: const EdgeInsets.only(left: 20, right: 20),
),
Divider(height: 1),
ListTile(
title: Text('English'),
onTap: () => _setLocale(Locale('en')),
contentPadding: const EdgeInsets.only(left: 20, right: 20),
trailing: Radio<Locale>(
value: Locale('en'),
groupValue: Locale(Intl.getCurrentLocale()),
onChanged: _setLocale)),
Divider(height: 1),
ListTile(
title: Text('简体中文'),
onTap: () => _setLocale(Locale('zh_Hans')),
contentPadding: const EdgeInsets.only(left: 20, right: 20),
trailing: Radio<Locale>(
value: Locale('zh_Hans'),
groupValue: Locale(Intl.getCurrentLocale()),
onChanged: _setLocale,
)),
Divider(height: 1),
ListTile(
title: Text('Français'),
onTap: () => _setLocale(Locale('fr')),
contentPadding: const EdgeInsets.only(left: 20, right: 20),
trailing: Radio<Locale>(
value: Locale('fr'),
groupValue: Locale(Intl.getCurrentLocale()),
onChanged: _setLocale),
),
Divider(height: 1),
ListTile(
title: Text('Español'),
onTap: () => _setLocale(Locale('es')),
contentPadding: const EdgeInsets.only(left: 20, right: 20),
trailing: Radio<Locale>(
value: Locale('es'),
groupValue: Locale(Intl.getCurrentLocale()),
onChanged: _setLocale),
),
Divider(height: 1),
ListTile(
title: Text('Português'),
onTap: () => _setLocale(Locale('pt')),
contentPadding: const EdgeInsets.only(left: 20, right: 20),
trailing: Radio<Locale>(
value: Locale('pt'),
groupValue: Locale(Intl.getCurrentLocale()),
onChanged: _setLocale),
),
Divider(height: 1),
ListTile(
title: Text('Italiano'),
onTap: () => _setLocale(Locale('it')),
contentPadding: const EdgeInsets.only(left: 20, right: 20),
trailing: Radio<Locale>(
value: Locale('it'),
groupValue: Locale(Intl.getCurrentLocale()),
onChanged: _setLocale),
),
Divider(height: 1),
ListTile(
onTap: () =>
'mailto:<tsacdop.app@gmail.com>?subject=Tsacdop localization project'
.launchUrl,
contentPadding: const EdgeInsets.only(left: 20, right: 20),
title: Align(
alignment: Alignment.centerLeft,
child: Image(
image: Theme.of(context).brightness == Brightness.light
? AssetImage('assets/localizely_logo.png')
: AssetImage('assets/localizely_logo_light.png'),
height: 20),
),
subtitle: Text(
"If you'd like to contribute to localization project, please contact me."),
),
],
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'package:intl/intl_standalone.dart';
import '../generated/l10n.dart';
import '../local_storage/key_value_storage.dart';
import '../util/extension_helper.dart';
class LanguagesSetting extends StatefulWidget {
const LanguagesSetting({Key key}) : super(key: key);
@override
_LanguagesSettingState createState() => _LanguagesSettingState();
}
class _LanguagesSettingState extends State<LanguagesSetting> {
_setLocale(Locale locale, {bool systemDefault = false}) async {
var localeStorage = KeyValueStorage(localeKey);
if (systemDefault) {
await localeStorage.saveStringList([]);
await findSystemLocale();
var systemLanCode;
final list = Intl.systemLocale.split('_');
if (list.length == 2) {
systemLanCode = list.first;
} else if (list.length == 3) {
systemLanCode = '${list[0]}_${list[1]}';
} else {
systemLanCode = 'en';
}
await S.load(Locale(systemLanCode));
if (mounted) {
setState(() {});
}
} else {
await localeStorage
.saveStringList([locale.languageCode, locale.countryCode]);
await S.load(locale);
if (mounted) {
setState(() {});
}
}
}
@override
void initState() {
super.initState();
findSystemLocale();
}
@override
Widget build(BuildContext context) {
final s = context.s;
return Column(
children: [
ListTile(
title: Text(
s.systemDefault,
style: TextStyle(
color: Intl.systemLocale.contains(Intl.getCurrentLocale())
? context.accentColor
: null),
),
onTap: () =>
_setLocale(Locale(Intl.systemLocale), systemDefault: true),
contentPadding: const EdgeInsets.only(left: 20, right: 20),
),
Divider(height: 1),
ListTile(
title: Text('English'),
onTap: () => _setLocale(Locale('en')),
contentPadding: const EdgeInsets.only(left: 20, right: 20),
trailing: Radio<Locale>(
value: Locale('en'),
groupValue: Locale(Intl.getCurrentLocale()),
onChanged: _setLocale)),
Divider(height: 1),
ListTile(
title: Text('简体中文'),
onTap: () => _setLocale(Locale('zh_Hans')),
contentPadding: const EdgeInsets.only(left: 20, right: 20),
trailing: Radio<Locale>(
value: Locale('zh_Hans'),
groupValue: Locale(Intl.getCurrentLocale()),
onChanged: _setLocale,
)),
Divider(height: 1),
ListTile(
title: Text('Français'),
onTap: () => _setLocale(Locale('fr')),
contentPadding: const EdgeInsets.only(left: 20, right: 20),
trailing: Radio<Locale>(
value: Locale('fr'),
groupValue: Locale(Intl.getCurrentLocale()),
onChanged: _setLocale),
),
Divider(height: 1),
ListTile(
title: Text('Español'),
onTap: () => _setLocale(Locale('es')),
contentPadding: const EdgeInsets.only(left: 20, right: 20),
trailing: Radio<Locale>(
value: Locale('es'),
groupValue: Locale(Intl.getCurrentLocale()),
onChanged: _setLocale),
),
Divider(height: 1),
ListTile(
title: Text('Português'),
onTap: () => _setLocale(Locale('pt')),
contentPadding: const EdgeInsets.only(left: 20, right: 20),
trailing: Radio<Locale>(
value: Locale('pt'),
groupValue: Locale(Intl.getCurrentLocale()),
onChanged: _setLocale),
),
Divider(height: 1),
ListTile(
title: Text('Italiano'),
onTap: () => _setLocale(Locale('it')),
contentPadding: const EdgeInsets.only(left: 20, right: 20),
trailing: Radio<Locale>(
value: Locale('it'),
groupValue: Locale(Intl.getCurrentLocale()),
onChanged: _setLocale),
),
Divider(height: 1),
ListTile(
onTap: () =>
'mailto:<tsacdop.app@gmail.com>?subject=Tsacdop localization project'
.launchUrl,
contentPadding: const EdgeInsets.only(left: 20, right: 20),
title: Align(
alignment: Alignment.centerLeft,
child: Image(
image: Theme.of(context).brightness == Brightness.light
? AssetImage('assets/localizely_logo.png')
: AssetImage('assets/localizely_logo_light.png'),
height: 20),
),
subtitle: Text(
"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/services.dart';
import 'package:provider/provider.dart';
import '../local_storage/key_value_storage.dart';
import '../service/search_api.dart';
import '../state/audio_state.dart';
import '../util/custom_dropdown.dart';
import '../util/custom_widget.dart';
import '../util/episodegrid.dart';
import '../util/extension_helper.dart';
import 'popup_menu.dart';
class LayoutSetting extends StatefulWidget {
const LayoutSetting({Key key}) : super(key: key);
@override
_LayoutSettingState createState() => _LayoutSettingState();
}
class _LayoutSettingState extends State<LayoutSetting> {
final _hideDiscoveyStorage = KeyValueStorage(hidePodcastDiscoveryKey);
Future<Layout> _getLayout(String key) async {
var keyValueStorage = KeyValueStorage(key);
var layout = await keyValueStorage.getInt();
return Layout.values[layout];
}
Future<bool> _getHideDiscovery() async {
return await _hideDiscoveyStorage.getBool(defaultValue: false);
}
Future<void> _saveHideDiscovery(bool boo) async {
await _hideDiscoveyStorage.saveBool(boo);
if (mounted) setState(() {});
}
Future<bool> _hideListened() async {
var hideListenedStorage = KeyValueStorage(hideListenedKey);
var hideListened = await hideListenedStorage.getBool(defaultValue: false);
return hideListened;
}
Future<void> _saveHideListened(bool boo) async {
var hideListenedStorage = KeyValueStorage(hideListenedKey);
await hideListenedStorage.saveBool(boo);
if (mounted) setState(() {});
}
Future<SearchEngine> _getSearchEngine() async {
final storage = KeyValueStorage(searchEngineKey);
final index = await storage.getInt();
return SearchEngine.values[index];
}
Future<void> _saveSearchEngine(SearchEngine engine) async {
final storage = KeyValueStorage(searchEngineKey);
await storage.saveInt(engine.index);
if (mounted) setState(() {});
}
String _getHeightString(PlayerHeight mode) {
final s = context.s;
switch (mode) {
case PlayerHeight.short:
return s.playerHeightShort;
break;
case PlayerHeight.mid:
return s.playerHeightMed;
break;
case PlayerHeight.tall:
return s.playerHeightTall;
break;
default:
return '';
}
}
Widget _gridOptions(BuildContext context,
{String key,
Layout layout,
Layout option,
double scale,
BorderRadiusGeometry borderRadius}) =>
Padding(
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
child: InkWell(
onTap: () async {
var storage = KeyValueStorage(key);
await storage.saveInt(option.index);
setState(() {});
},
borderRadius: borderRadius,
child: AnimatedContainer(
duration: Duration(milliseconds: 400),
height: 30,
width: 50,
decoration: BoxDecoration(
borderRadius: borderRadius,
color: layout == option
? context.accentColor
: context.primaryColorDark,
),
alignment: Alignment.center,
child: SizedBox(
height: 10,
width: 30,
child: CustomPaint(
painter: LayoutPainter(
scale,
layout == option
? Colors.white
: context.textTheme.bodyText1.color),
),
),
),
),
);
Widget _setDefaultGrid(BuildContext context, {String key}) {
return FutureBuilder<Layout>(
future: _getLayout(key),
builder: (context, snapshot) {
return snapshot.hasData
? Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
_gridOptions(
context,
key: key,
layout: snapshot.data,
option: Layout.one,
scale: 4,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(5),
topLeft: Radius.circular(5)),
),
_gridOptions(
context,
key: key,
layout: snapshot.data,
option: Layout.two,
scale: 1,
),
_gridOptions(context,
key: key,
layout: snapshot.data,
option: Layout.three,
scale: 0,
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(5),
topRight: Radius.circular(5))),
],
)
: Center();
});
}
Widget _setDefaultGridView(BuildContext context, {String text, String key}) {
return Padding(
padding: EdgeInsets.only(left: 70.0, right: 20, bottom: 10),
child: context.width > 360
? Row(
children: [
Text(
text,
),
Spacer(),
_setDefaultGrid(context, key: key),
],
)
: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
text,
),
_setDefaultGrid(context, key: key),
],
),
);
}
@override
Widget build(BuildContext context) {
final s = context.s;
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: context.primaryColor,
systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness,
),
child: Scaffold(
appBar: AppBar(
title: Text(s.settingsLayout),
leading: CustomBackButton(),
elevation: 0,
backgroundColor: context.primaryColor,
),
body: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.all(10.0),
),
Container(
height: 30.0,
padding: const EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft,
child: Text(s.settingsPopupMenu,
style: context.textTheme.bodyText1
.copyWith(color: context.accentColor)),
),
ListTile(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PopupMenuSetting())),
contentPadding: EdgeInsets.only(left: 70.0, right: 20),
title: Text(s.settingsPopupMenu),
subtitle: Text(s.settingsPopupMenuDes),
),
Divider(height: 1),
Padding(
padding: EdgeInsets.all(10.0),
),
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft,
child: Text(s.player,
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(color: Theme.of(context).accentColor)),
),
ListTile(
contentPadding: EdgeInsets.fromLTRB(70, 10, 10, 10),
title: Text(s.settingsPlayerHeight),
subtitle: Text(s.settingsPlayerHeightDes),
trailing: Selector<AudioPlayerNotifier, PlayerHeight>(
selector: (_, audio) => audio.playerHeight,
builder: (_, data, __) => MyDropdownButton(
hint: Text(_getHeightString(data)),
underline: Center(),
elevation: 1,
value: data.index,
items: <int>[0, 1, 2].map<DropdownMenuItem<int>>((e) {
return DropdownMenuItem<int>(
value: e,
child: Text(
_getHeightString(PlayerHeight.values[e])));
}).toList(),
onChanged: (index) =>
audio.setPlayerHeight = PlayerHeight.values[index]),
),
),
Divider(height: 1),
Padding(
padding: EdgeInsets.all(10.0),
),
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft,
child: Text(s.search,
style: context.textTheme.bodyText1
.copyWith(color: context.accentColor)),
),
FutureBuilder<bool>(
future: _getHideDiscovery(),
initialData: false,
builder: (context, snapshot) => ListTile(
contentPadding: EdgeInsets.fromLTRB(70, 10, 10, 10),
onTap: () => _saveHideDiscovery(!snapshot.data),
title: Text(s.hidePodcastDiscovery),
subtitle: Text(s.hidePodcastDiscoveryDes),
trailing: Transform.scale(
scale: 0.9,
child: Switch(
value: snapshot.data, onChanged: _saveHideDiscovery),
),
),
),
FutureBuilder(
future: _getSearchEngine(),
initialData: SearchEngine.listenNotes,
builder: (context, snapshot) => ListTile(
contentPadding: EdgeInsets.fromLTRB(70, 10, 10, 10),
title: Text(s.defaultSearchEngine),
subtitle: Text(s.defaultSearchEngineDes),
trailing: MyDropdownButton(
hint: Text(''),
underline: Center(),
elevation: 1,
value: snapshot.data,
items: [
DropdownMenuItem<SearchEngine>(
value: SearchEngine.podcastIndex,
child: Text('Podcastindex')),
DropdownMenuItem<SearchEngine>(
value: SearchEngine.listenNotes,
child: Text('ListenNotes')),
],
onChanged: (value) => _saveSearchEngine(value)),
),
),
Divider(height: 1),
Padding(
padding: EdgeInsets.all(10.0),
),
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft,
child: Text(s.settingsDefaultGrid,
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(color: Theme.of(context).accentColor)),
),
ListView(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: <Widget>[
FutureBuilder<bool>(
future: _hideListened(),
initialData: false,
builder: (context, snapshot) => ListTile(
contentPadding: EdgeInsets.only(left: 70, right: 10),
onTap: () => _saveHideListened(!snapshot.data),
title: Text('Hide listened'),
subtitle: Text('Hide listened episodes by default'),
trailing: Transform.scale(
scale: 0.9,
child: Switch(
value: snapshot.data,
onChanged: _saveHideListened),
),
),
),
_setDefaultGridView(context,
text: s.settingsDefaultGridPodcast,
key: podcastLayoutKey),
_setDefaultGridView(context,
text: s.settingsDefaultGridRecent,
key: recentLayoutKey),
_setDefaultGridView(context,
text: s.settingsDefaultGridFavorite,
key: favLayoutKey),
_setDefaultGridView(context,
text: s.settingsDefaultGridDownload,
key: downloadLayoutKey),
]),
Divider(height: 1),
],
),
)),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import '../local_storage/key_value_storage.dart';
import '../service/search_api.dart';
import '../state/audio_state.dart';
import '../util/custom_dropdown.dart';
import '../util/custom_widget.dart';
import '../util/episodegrid.dart';
import '../util/extension_helper.dart';
import 'popup_menu.dart';
class LayoutSetting extends StatefulWidget {
const LayoutSetting({Key key}) : super(key: key);
@override
_LayoutSettingState createState() => _LayoutSettingState();
}
class _LayoutSettingState extends State<LayoutSetting> {
final _hideDiscoveyStorage = KeyValueStorage(hidePodcastDiscoveryKey);
Future<Layout> _getLayout(String key) async {
var keyValueStorage = KeyValueStorage(key);
var layout = await keyValueStorage.getInt();
return Layout.values[layout];
}
Future<bool> _getHideDiscovery() async {
return await _hideDiscoveyStorage.getBool(defaultValue: false);
}
Future<void> _saveHideDiscovery(bool boo) async {
await _hideDiscoveyStorage.saveBool(boo);
if (mounted) setState(() {});
}
Future<bool> _hideListened() async {
var hideListenedStorage = KeyValueStorage(hideListenedKey);
var hideListened = await hideListenedStorage.getBool(defaultValue: false);
return hideListened;
}
Future<void> _saveHideListened(bool boo) async {
var hideListenedStorage = KeyValueStorage(hideListenedKey);
await hideListenedStorage.saveBool(boo);
if (mounted) setState(() {});
}
Future<SearchEngine> _getSearchEngine() async {
final storage = KeyValueStorage(searchEngineKey);
final index = await storage.getInt();
return SearchEngine.values[index];
}
Future<void> _saveSearchEngine(SearchEngine engine) async {
final storage = KeyValueStorage(searchEngineKey);
await storage.saveInt(engine.index);
if (mounted) setState(() {});
}
String _getHeightString(PlayerHeight mode) {
final s = context.s;
switch (mode) {
case PlayerHeight.short:
return s.playerHeightShort;
break;
case PlayerHeight.mid:
return s.playerHeightMed;
break;
case PlayerHeight.tall:
return s.playerHeightTall;
break;
default:
return '';
}
}
Widget _gridOptions(BuildContext context,
{String key,
Layout layout,
Layout option,
double scale,
BorderRadiusGeometry borderRadius}) =>
Padding(
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
child: InkWell(
onTap: () async {
var storage = KeyValueStorage(key);
await storage.saveInt(option.index);
setState(() {});
},
borderRadius: borderRadius,
child: AnimatedContainer(
duration: Duration(milliseconds: 400),
height: 30,
width: 50,
decoration: BoxDecoration(
borderRadius: borderRadius,
color: layout == option
? context.accentColor
: context.primaryColorDark,
),
alignment: Alignment.center,
child: SizedBox(
height: 10,
width: 30,
child: CustomPaint(
painter: LayoutPainter(
scale,
layout == option
? Colors.white
: context.textTheme.bodyText1.color),
),
),
),
),
);
Widget _setDefaultGrid(BuildContext context, {String key}) {
return FutureBuilder<Layout>(
future: _getLayout(key),
builder: (context, snapshot) {
return snapshot.hasData
? Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
_gridOptions(
context,
key: key,
layout: snapshot.data,
option: Layout.one,
scale: 4,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(5),
topLeft: Radius.circular(5)),
),
_gridOptions(
context,
key: key,
layout: snapshot.data,
option: Layout.two,
scale: 1,
),
_gridOptions(context,
key: key,
layout: snapshot.data,
option: Layout.three,
scale: 0,
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(5),
topRight: Radius.circular(5))),
],
)
: Center();
});
}
Widget _setDefaultGridView(BuildContext context, {String text, String key}) {
return Padding(
padding: EdgeInsets.only(left: 70.0, right: 20, bottom: 10),
child: context.width > 360
? Row(
children: [
Text(
text,
),
Spacer(),
_setDefaultGrid(context, key: key),
],
)
: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
text,
),
_setDefaultGrid(context, key: key),
],
),
);
}
@override
Widget build(BuildContext context) {
final s = context.s;
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: context.primaryColor,
systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness,
),
child: Scaffold(
appBar: AppBar(
title: Text(s.settingsLayout),
leading: CustomBackButton(),
elevation: 0,
backgroundColor: context.primaryColor,
),
body: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.all(10.0),
),
Container(
height: 30.0,
padding: const EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft,
child: Text(s.settingsPopupMenu,
style: context.textTheme.bodyText1
.copyWith(color: context.accentColor)),
),
ListTile(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PopupMenuSetting())),
contentPadding: EdgeInsets.only(left: 70.0, right: 20),
title: Text(s.settingsPopupMenu),
subtitle: Text(s.settingsPopupMenuDes),
),
Divider(height: 1),
Padding(
padding: EdgeInsets.all(10.0),
),
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft,
child: Text(s.player,
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(color: Theme.of(context).accentColor)),
),
ListTile(
contentPadding: EdgeInsets.fromLTRB(70, 10, 10, 10),
title: Text(s.settingsPlayerHeight),
subtitle: Text(s.settingsPlayerHeightDes),
trailing: Selector<AudioPlayerNotifier, PlayerHeight>(
selector: (_, audio) => audio.playerHeight,
builder: (_, data, __) => MyDropdownButton(
hint: Text(_getHeightString(data)),
underline: Center(),
elevation: 1,
value: data.index,
items: <int>[0, 1, 2].map<DropdownMenuItem<int>>((e) {
return DropdownMenuItem<int>(
value: e,
child: Text(
_getHeightString(PlayerHeight.values[e])));
}).toList(),
onChanged: (index) =>
audio.setPlayerHeight = PlayerHeight.values[index]),
),
),
Divider(height: 1),
Padding(
padding: EdgeInsets.all(10.0),
),
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft,
child: Text(s.search,
style: context.textTheme.bodyText1
.copyWith(color: context.accentColor)),
),
FutureBuilder<bool>(
future: _getHideDiscovery(),
initialData: false,
builder: (context, snapshot) => ListTile(
contentPadding: EdgeInsets.fromLTRB(70, 10, 10, 10),
onTap: () => _saveHideDiscovery(!snapshot.data),
title: Text(s.hidePodcastDiscovery),
subtitle: Text(s.hidePodcastDiscoveryDes),
trailing: Transform.scale(
scale: 0.9,
child: Switch(
value: snapshot.data, onChanged: _saveHideDiscovery),
),
),
),
FutureBuilder(
future: _getSearchEngine(),
initialData: SearchEngine.listenNotes,
builder: (context, snapshot) => ListTile(
contentPadding: EdgeInsets.fromLTRB(70, 10, 10, 10),
title: Text(s.defaultSearchEngine),
subtitle: Text(s.defaultSearchEngineDes),
trailing: MyDropdownButton(
hint: Text(''),
underline: Center(),
elevation: 1,
value: snapshot.data,
items: [
DropdownMenuItem<SearchEngine>(
value: SearchEngine.podcastIndex,
child: Text('Podcastindex')),
DropdownMenuItem<SearchEngine>(
value: SearchEngine.listenNotes,
child: Text('ListenNotes')),
],
onChanged: (value) => _saveSearchEngine(value)),
),
),
Divider(height: 1),
Padding(
padding: EdgeInsets.all(10.0),
),
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 70),
alignment: Alignment.centerLeft,
child: Text(s.settingsDefaultGrid,
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(color: Theme.of(context).accentColor)),
),
ListView(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: <Widget>[
FutureBuilder<bool>(
future: _hideListened(),
initialData: false,
builder: (context, snapshot) => ListTile(
contentPadding: EdgeInsets.only(left: 70, right: 10),
onTap: () => _saveHideListened(!snapshot.data),
title: Text('Hide listened'),
subtitle: Text('Hide listened episodes by default'),
trailing: Transform.scale(
scale: 0.9,
child: Switch(
value: snapshot.data,
onChanged: _saveHideListened),
),
),
),
_setDefaultGridView(context,
text: s.settingsDefaultGridPodcast,
key: podcastLayoutKey),
_setDefaultGridView(context,
text: s.settingsDefaultGridRecent,
key: recentLayoutKey),
_setDefaultGridView(context,
text: s.settingsDefaultGridFavorite,
key: favLayoutKey),
_setDefaultGridView(context,
text: s.settingsDefaultGridDownload,
key: downloadLayoutKey),
]),
Divider(height: 1),
],
),
)),
);
}
}

View File

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

View File

@ -1,78 +1,78 @@
const String apacheLicense = "Apache License 2.0";
const String mit = "MIT License";
const String bsd = "BSD 3-Clause";
const String gpl = "GPL 3.0";
const String font = "Open Font License";
class Libries {
String name;
String license;
String link;
Libries(this.name, this.license, this.link);
}
List<Libries> google = [
Libries('Android X', apacheLicense,
'https://source.android.com/setup/start/licenses'),
Libries(
'Flutter', bsd, 'https://github.com/flutter/flutter/blob/master/LICENSE')
];
List<Libries> fonts = [
Libries('Libre Baskerville', font,
"https://fonts.google.com/specimen/Libre+Baskerville"),
Libries('Teko', font, "https://fonts.google.com/specimen/Teko"),
Libries('Martel', font, "https://fonts.google.com/specimen/Martel"),
Libries('Bitter', font, "https://fonts.google.com/specimen/Bitter")
];
List<Libries> plugins = [
Libries('webfeed', mit, 'https://pub.dev/packages/webfeed'),
Libries('json_annotation', bsd, 'https://pub.dev/packages/json_annotation'),
Libries('sqflite', mit, 'https://pub.dev/packages/sqflite'),
Libries('flutter_html', mit, 'https://pub.dev/packages/flutter_html'),
Libries('path_provider', bsd, 'https://pub.dev/packages/path_provider'),
Libries('color_thief_flutter', mit,
'https://pub.dev/packages/color_thief_flutter'),
Libries('provider', mit, 'https://pub.dev/packages/provider'),
Libries(
'google_fonts', apacheLicense, 'https://pub.dev/packages/google_fonts'),
Libries('dio', mit, 'https://pub.dev/packages/dio'),
Libries('file_picker', mit, 'https://pub.dev/packages/file_picker'),
Libries('xml', mit, 'https://pub.dev/packages/xml'),
Libries('marquee', mit, 'https://pub.dev/packages/marquee'),
Libries(
'flutter_downloader', bsd, 'https://pub.dev/packages/flutter_downloader'),
Libries(
'permission_handler', mit, 'https://pub.dev/packages/permission_handler'),
Libries('fluttertoast', mit, 'https://pub.dev/packages/fluttertoast'),
Libries('intl', bsd, 'https://pub.dev/packages/intl'),
Libries('url_launcher', bsd, 'https://pub.dev/packages/url_launcher'),
Libries('image', apacheLicense, 'https://pub.dev/packages/image'),
Libries(
'shared_preferences', bsd, 'https://pub.dev/packages/shared_preferences'),
Libries('uuid', mit, 'https://pub.dev/packages/uuid'),
Libries('tuple', bsd, 'https://pub.dev/packages/tuple'),
Libries('cached_network_image', mit,
'https://pub.dev/packages/cached_network_image'),
Libries('workmanager', mit, 'https://pub.dev/packages/workmanager'),
Libries('app_settings', mit, 'https://pub.dev/packages/app_settings'),
Libries('fl_chart', bsd, 'https://pub.dev/packages/fl_chart'),
Libries('audio_service', mit, 'https://pub.dev/packages/audio_service'),
Libries('just_audio', apacheLicense, 'https://pub.dev/packages/just_audio'),
Libries('line_icons', gpl, 'https://pub.dev/packages/line_icons'),
Libries('flutter_file_dialog', bsd,
'https://pub.dev/packages/flutter_file_dialog'),
Libries('flutter_linkify', mit, 'https://pub.dev/packages/flutter_linkify'),
Libries('extended_nested_scroll_view', mit,
'https://pub.dev/packages/extended_nested_scroll_view'),
Libries('connectivity', bsd, 'https://pub.dev/packages/connectivity'),
Libries('Rxdart', apacheLicense, 'https://pub.dev/packages/rxdart'),
Libries('flutter_isolate', mit, 'https://pub.dev/packages/flutter_isolate'),
Libries('auto_animated', mit, 'https://pub.dev/packages/auto_animated'),
Libries('wc_flutter_share', apacheLicense,
'https://pub.dev/packages/wc_flutter_share'),
Libries('flutter_time_picker_spinner', 'unknow',
'https://pub.dev/packages/flutter_time_picker_spinner'),
Libries('focused_menu', mit, 'https://pub.dev/packages/focused_menu')
];
const String apacheLicense = "Apache License 2.0";
const String mit = "MIT License";
const String bsd = "BSD 3-Clause";
const String gpl = "GPL 3.0";
const String font = "Open Font License";
class Libries {
String name;
String license;
String link;
Libries(this.name, this.license, this.link);
}
List<Libries> google = [
Libries('Android X', apacheLicense,
'https://source.android.com/setup/start/licenses'),
Libries(
'Flutter', bsd, 'https://github.com/flutter/flutter/blob/master/LICENSE')
];
List<Libries> fonts = [
Libries('Libre Baskerville', font,
"https://fonts.google.com/specimen/Libre+Baskerville"),
Libries('Teko', font, "https://fonts.google.com/specimen/Teko"),
Libries('Martel', font, "https://fonts.google.com/specimen/Martel"),
Libries('Bitter', font, "https://fonts.google.com/specimen/Bitter")
];
List<Libries> plugins = [
Libries('webfeed', mit, 'https://pub.dev/packages/webfeed'),
Libries('json_annotation', bsd, 'https://pub.dev/packages/json_annotation'),
Libries('sqflite', mit, 'https://pub.dev/packages/sqflite'),
Libries('flutter_html', mit, 'https://pub.dev/packages/flutter_html'),
Libries('path_provider', bsd, 'https://pub.dev/packages/path_provider'),
Libries('color_thief_flutter', mit,
'https://pub.dev/packages/color_thief_flutter'),
Libries('provider', mit, 'https://pub.dev/packages/provider'),
Libries(
'google_fonts', apacheLicense, 'https://pub.dev/packages/google_fonts'),
Libries('dio', mit, 'https://pub.dev/packages/dio'),
Libries('file_picker', mit, 'https://pub.dev/packages/file_picker'),
Libries('xml', mit, 'https://pub.dev/packages/xml'),
Libries('marquee', mit, 'https://pub.dev/packages/marquee'),
Libries(
'flutter_downloader', bsd, 'https://pub.dev/packages/flutter_downloader'),
Libries(
'permission_handler', mit, 'https://pub.dev/packages/permission_handler'),
Libries('fluttertoast', mit, 'https://pub.dev/packages/fluttertoast'),
Libries('intl', bsd, 'https://pub.dev/packages/intl'),
Libries('url_launcher', bsd, 'https://pub.dev/packages/url_launcher'),
Libries('image', apacheLicense, 'https://pub.dev/packages/image'),
Libries(
'shared_preferences', bsd, 'https://pub.dev/packages/shared_preferences'),
Libries('uuid', mit, 'https://pub.dev/packages/uuid'),
Libries('tuple', bsd, 'https://pub.dev/packages/tuple'),
Libries('cached_network_image', mit,
'https://pub.dev/packages/cached_network_image'),
Libries('workmanager', mit, 'https://pub.dev/packages/workmanager'),
Libries('app_settings', mit, 'https://pub.dev/packages/app_settings'),
Libries('fl_chart', bsd, 'https://pub.dev/packages/fl_chart'),
Libries('audio_service', mit, 'https://pub.dev/packages/audio_service'),
Libries('just_audio', apacheLicense, 'https://pub.dev/packages/just_audio'),
Libries('line_icons', gpl, 'https://pub.dev/packages/line_icons'),
Libries('flutter_file_dialog', bsd,
'https://pub.dev/packages/flutter_file_dialog'),
Libries('flutter_linkify', mit, 'https://pub.dev/packages/flutter_linkify'),
Libries('extended_nested_scroll_view', mit,
'https://pub.dev/packages/extended_nested_scroll_view'),
Libries('connectivity', bsd, 'https://pub.dev/packages/connectivity'),
Libries('Rxdart', apacheLicense, 'https://pub.dev/packages/rxdart'),
Libries('flutter_isolate', mit, 'https://pub.dev/packages/flutter_isolate'),
Libries('auto_animated', mit, 'https://pub.dev/packages/auto_animated'),
Libries('wc_flutter_share', apacheLicense,
'https://pub.dev/packages/wc_flutter_share'),
Libries('flutter_time_picker_spinner', 'unknow',
'https://pub.dev/packages/flutter_time_picker_spinner'),
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:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:line_icons/line_icons.dart';
import '../local_storage/key_value_storage.dart';
import '../util/custom_widget.dart';
import '../util/extension_helper.dart';
class PopupMenuSetting extends StatefulWidget {
const PopupMenuSetting({Key key}) : super(key: key);
@override
_PopupMenuSettingState createState() => _PopupMenuSettingState();
}
class _PopupMenuSettingState extends State<PopupMenuSetting> {
Future<List<int>> _getEpisodeMenu() async {
var popupMenuStorage = KeyValueStorage(episodePopupMenuKey);
var list = await popupMenuStorage.getMenu();
return list;
}
Future<bool> _getTapToOpenPopupMenu() async {
var tapToOpenPopupMenuStorage = KeyValueStorage(tapToOpenPopupMenuKey);
var boo = await tapToOpenPopupMenuStorage.getBool(defaultValue: false);
return boo;
}
_saveEpisodeMene(List<int> list) async {
var popupMenuStorage = KeyValueStorage(episodePopupMenuKey);
await popupMenuStorage.saveMenu(list);
if (mounted) setState(() {});
}
_saveTapToOpenPopupMenu(bool boo) async {
var tapToOpenPopupMenuStorage = KeyValueStorage(tapToOpenPopupMenuKey);
await tapToOpenPopupMenuStorage.saveBool(boo);
if (mounted) setState(() {});
}
Widget _popupMenuItem(List<int> menu, int e,
{Widget icon,
String text,
String description = '',
bool enable = false}) {
return Padding(
key: ObjectKey(text),
padding: EdgeInsets.only(left: 60.0, right: 20),
child: ListTile(
leading: icon,
title: Text(text),
subtitle: Text(description),
onTap: e == 0
? null
: () {
if (e >= 10) {
var index = menu.indexOf(e);
menu.remove(e);
menu.insert(index, e - 10);
_saveEpisodeMene(menu);
} else if (e < 10) {
var index = menu.indexOf(e);
menu.remove(e);
menu.insert(index, e + 10);
_saveEpisodeMene(menu);
}
},
trailing: Checkbox(
value: e < 10,
onChanged: e == 0
? null
: (boo) {
if (boo && e >= 10) {
var index = menu.indexOf(e);
menu.remove(e);
menu.insert(index, e - 10);
_saveEpisodeMene(menu);
} else if (e < 10) {
var index = menu.indexOf(e);
menu.remove(e);
menu.insert(index, e + 10);
_saveEpisodeMene(menu);
}
})),
);
}
@override
Widget build(BuildContext context) {
final s = context.s;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: context.primaryColor,
systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness,
),
child: Scaffold(
appBar: AppBar(
elevation: 0,
leading: CustomBackButton(),
backgroundColor: context.primaryColor,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
color: context.primaryColor,
height: 200,
// color: Colors.red,
child: FlareActor(
'assets/longtap.flr',
alignment: Alignment.center,
animation: 'longtap',
fit: BoxFit.cover,
)),
FutureBuilder<List<int>>(
future: _getEpisodeMenu(),
initialData: [0, 1, 12, 13, 14],
builder: (context, snapshot) {
var menu = snapshot.data;
return Expanded(
child: ListView(
shrinkWrap: true,
children: [
Padding(
padding: EdgeInsets.symmetric(vertical: 10),
),
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 80),
alignment: Alignment.centerLeft,
child: Text(s.settingsPopupMenu,
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(
color: Theme.of(context).accentColor)),
),
FutureBuilder<bool>(
future: _getTapToOpenPopupMenu(),
initialData: false,
builder: (context, snapshot) => ListTile(
contentPadding: EdgeInsets.only(
left: 80, top: 10, bottom: 10, right: 30),
onTap: () =>
_saveTapToOpenPopupMenu(!snapshot.data),
title: Text(s.settingsTapToOpenPopupMenu),
subtitle: Text(s.settingsTapToOpenPopupMenuDes),
trailing: Transform.scale(
scale: 0.9,
child: Switch(
value: snapshot.data,
onChanged: _saveTapToOpenPopupMenu),
),
),
),
...menu.map<Widget>((e) {
var i = e % 10;
switch (i) {
case 0:
return _popupMenuItem(menu, e,
icon: Icon(
LineIcons.play_circle_solid,
color: context.accentColor,
),
text: s.play,
description: s.popupMenuPlayDes);
break;
case 1:
return _popupMenuItem(menu, e,
icon: Icon(
LineIcons.clock_solid,
color: Colors.cyan,
),
text: s.later,
description: s.popupMenuLaterDes);
break;
case 2:
return _popupMenuItem(menu, e,
icon: Icon(LineIcons.heart,
color: Colors.red, size: 21),
text: s.like,
description: s.popupMenuLikeDes);
break;
case 3:
return _popupMenuItem(menu, e,
icon: SizedBox(
width: 23,
height: 23,
child: CustomPaint(
painter: ListenedAllPainter(
Colors.blue,
stroke: 1.5)),
),
text: s.markListened,
description: s.popupMenuMarkDes);
break;
case 4:
return _popupMenuItem(menu, e,
icon: Icon(
LineIcons.download_solid,
color: Colors.green,
),
text: s.download,
description: s.popupMenuDownloadDes);
break;
default:
return Text('Text');
break;
}
}).toList(),
],
),
);
}),
],
)),
);
}
}
import 'package:flare_flutter/flare_actor.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:line_icons/line_icons.dart';
import '../local_storage/key_value_storage.dart';
import '../util/custom_widget.dart';
import '../util/extension_helper.dart';
class PopupMenuSetting extends StatefulWidget {
const PopupMenuSetting({Key key}) : super(key: key);
@override
_PopupMenuSettingState createState() => _PopupMenuSettingState();
}
class _PopupMenuSettingState extends State<PopupMenuSetting> {
Future<List<int>> _getEpisodeMenu() async {
var popupMenuStorage = KeyValueStorage(episodePopupMenuKey);
var list = await popupMenuStorage.getMenu();
return list;
}
Future<bool> _getTapToOpenPopupMenu() async {
var tapToOpenPopupMenuStorage = KeyValueStorage(tapToOpenPopupMenuKey);
var boo = await tapToOpenPopupMenuStorage.getBool(defaultValue: false);
return boo;
}
_saveEpisodeMene(List<int> list) async {
var popupMenuStorage = KeyValueStorage(episodePopupMenuKey);
await popupMenuStorage.saveMenu(list);
if (mounted) setState(() {});
}
_saveTapToOpenPopupMenu(bool boo) async {
var tapToOpenPopupMenuStorage = KeyValueStorage(tapToOpenPopupMenuKey);
await tapToOpenPopupMenuStorage.saveBool(boo);
if (mounted) setState(() {});
}
Widget _popupMenuItem(List<int> menu, int e,
{Widget icon,
String text,
String description = '',
bool enable = false}) {
return Padding(
key: ObjectKey(text),
padding: EdgeInsets.only(left: 60.0, right: 20),
child: ListTile(
leading: icon,
title: Text(text),
subtitle: Text(description),
onTap: e == 0
? null
: () {
if (e >= 10) {
var index = menu.indexOf(e);
menu.remove(e);
menu.insert(index, e - 10);
_saveEpisodeMene(menu);
} else if (e < 10) {
var index = menu.indexOf(e);
menu.remove(e);
menu.insert(index, e + 10);
_saveEpisodeMene(menu);
}
},
trailing: Checkbox(
value: e < 10,
onChanged: e == 0
? null
: (boo) {
if (boo && e >= 10) {
var index = menu.indexOf(e);
menu.remove(e);
menu.insert(index, e - 10);
_saveEpisodeMene(menu);
} else if (e < 10) {
var index = menu.indexOf(e);
menu.remove(e);
menu.insert(index, e + 10);
_saveEpisodeMene(menu);
}
})),
);
}
@override
Widget build(BuildContext context) {
final s = context.s;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
systemNavigationBarColor: context.primaryColor,
systemNavigationBarIconBrightness:
Theme.of(context).accentColorBrightness,
),
child: Scaffold(
appBar: AppBar(
elevation: 0,
leading: CustomBackButton(),
backgroundColor: context.primaryColor,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
color: context.primaryColor,
height: 200,
// color: Colors.red,
child: FlareActor(
'assets/longtap.flr',
alignment: Alignment.center,
animation: 'longtap',
fit: BoxFit.cover,
)),
FutureBuilder<List<int>>(
future: _getEpisodeMenu(),
initialData: [0, 1, 12, 13, 14],
builder: (context, snapshot) {
var menu = snapshot.data;
return Expanded(
child: ListView(
shrinkWrap: true,
children: [
Padding(
padding: EdgeInsets.symmetric(vertical: 10),
),
Container(
height: 30.0,
padding: EdgeInsets.symmetric(horizontal: 80),
alignment: Alignment.centerLeft,
child: Text(s.settingsPopupMenu,
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(
color: Theme.of(context).accentColor)),
),
FutureBuilder<bool>(
future: _getTapToOpenPopupMenu(),
initialData: false,
builder: (context, snapshot) => ListTile(
contentPadding: EdgeInsets.only(
left: 80, top: 10, bottom: 10, right: 30),
onTap: () =>
_saveTapToOpenPopupMenu(!snapshot.data),
title: Text(s.settingsTapToOpenPopupMenu),
subtitle: Text(s.settingsTapToOpenPopupMenuDes),
trailing: Transform.scale(
scale: 0.9,
child: Switch(
value: snapshot.data,
onChanged: _saveTapToOpenPopupMenu),
),
),
),
...menu.map<Widget>((e) {
var i = e % 10;
switch (i) {
case 0:
return _popupMenuItem(menu, e,
icon: Icon(
LineIcons.play_circle_solid,
color: context.accentColor,
),
text: s.play,
description: s.popupMenuPlayDes);
break;
case 1:
return _popupMenuItem(menu, e,
icon: Icon(
LineIcons.clock_solid,
color: Colors.cyan,
),
text: s.later,
description: s.popupMenuLaterDes);
break;
case 2:
return _popupMenuItem(menu, e,
icon: Icon(LineIcons.heart,
color: Colors.red, size: 21),
text: s.like,
description: s.popupMenuLikeDes);
break;
case 3:
return _popupMenuItem(menu, e,
icon: SizedBox(
width: 23,
height: 23,
child: CustomPaint(
painter: ListenedAllPainter(
Colors.blue,
stroke: 1.5)),
),
text: s.markListened,
description: s.popupMenuMarkDes);
break;
case 4:
return _popupMenuItem(menu, e,
icon: Icon(
LineIcons.download_solid,
color: Colors.green,
),
text: s.download,
description: s.popupMenuDownloadDes);
break;
default:
return Text('Text');
break;
}
}).toList(),
],
),
);
}),
],
)),
);
}
}

View File

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

View File

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

View File

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

View File

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

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