├── android ├── settings_aar.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradle.properties ├── .gitignore ├── app │ ├── src │ │ ├── main │ │ │ └── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── launcher_icon.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── launcher_icon.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── launcher_icon.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── launcher_icon.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── launcher_icon.png │ │ │ │ ├── drawable-hdpi │ │ │ │ ├── ic_action_pause.png │ │ │ │ ├── ic_action_stop.png │ │ │ │ └── ic_action_play_arrow.png │ │ │ │ ├── drawable-mdpi │ │ │ │ ├── ic_action_pause.png │ │ │ │ ├── ic_action_stop.png │ │ │ │ └── ic_action_play_arrow.png │ │ │ │ ├── drawable-xhdpi │ │ │ │ ├── ic_action_stop.png │ │ │ │ ├── ic_action_pause.png │ │ │ │ └── ic_action_play_arrow.png │ │ │ │ ├── drawable-xxhdpi │ │ │ │ ├── ic_action_pause.png │ │ │ │ ├── ic_action_stop.png │ │ │ │ └── ic_action_play_arrow.png │ │ │ │ ├── drawable-xxxhdpi │ │ │ │ ├── ic_action_stop.png │ │ │ │ ├── ic_action_pause.png │ │ │ │ └── ic_action_play_arrow.png │ │ │ │ ├── values │ │ │ │ └── styles.xml │ │ │ │ └── drawable │ │ │ │ └── launch_background.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── proguard-rules.pro │ ├── google-services.json │ └── build.gradle ├── key.properties ├── fastlane │ └── metadata │ │ └── android │ │ └── en-US │ │ └── images │ │ └── phoneScreenshots │ │ ├── Nexus 5X-drawer.png │ │ ├── Nexus 5X-home.png │ │ ├── Nexus 5X-episode.png │ │ ├── Nexus 5X-episodes.png │ │ ├── Nexus 5X-explore.png │ │ ├── Nexus 5X-podcast.png │ │ ├── Nexus 5X-settings.png │ │ └── Nexus 5X-explore-with-results.png ├── settings.gradle ├── build.gradle └── gradlew.bat ├── ios ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner │ ├── Runner-Bridging-Header.h │ ├── AppDelegate.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ ├── main.m │ ├── AppDelegate.swift │ ├── AppDelegate.m │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Runner.xcworkspace │ └── contents.xcworkspacedata ├── Runner.xcodeproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata └── .gitignore ├── lib ├── constants.dart ├── services │ ├── api │ │ ├── netcastsoss_data │ │ │ ├── .openapi-generator │ │ │ │ └── VERSION │ │ │ ├── analysis_options.yaml │ │ │ ├── .travis.yml │ │ │ ├── pubspec.yaml │ │ │ ├── test │ │ │ │ ├── loopback_count_test.dart │ │ │ │ ├── ping_controller_api_test.dart │ │ │ │ ├── inline_response200_test.dart │ │ │ │ ├── ping_response_test.dart │ │ │ │ ├── podcasts_filter1_test.dart │ │ │ │ ├── podcasts_filter_test.dart │ │ │ │ ├── podcasts_controller_api_test.dart │ │ │ │ ├── new_podcasts_test.dart │ │ │ │ ├── podcasts_fields_test.dart │ │ │ │ ├── podcasts_test.dart │ │ │ │ ├── podcasts_partial_test.dart │ │ │ │ └── podcasts_with_relations_test.dart │ │ │ ├── doc │ │ │ │ ├── LoopbackCount.md │ │ │ │ ├── InlineResponse200.md │ │ │ │ ├── PingResponse.md │ │ │ │ ├── PodcastsFilter1.md │ │ │ │ ├── PodcastsFilter.md │ │ │ │ ├── PingControllerApi.md │ │ │ │ ├── PodcastsFields.md │ │ │ │ ├── NewPodcasts.md │ │ │ │ ├── Podcasts.md │ │ │ │ ├── PodcastsWithRelations.md │ │ │ │ └── PodcastsPartial.md │ │ │ ├── lib │ │ │ │ ├── api │ │ │ │ │ ├── ping_controller_api.jretro.dart │ │ │ │ │ └── ping_controller_api.dart │ │ │ │ ├── model │ │ │ │ │ ├── loopback_count.dart │ │ │ │ │ ├── loopback_count.jser.dart │ │ │ │ │ ├── inline_response200.dart │ │ │ │ │ ├── ping_response.dart │ │ │ │ │ ├── podcasts_filter1.dart │ │ │ │ │ ├── podcasts_filter.dart │ │ │ │ │ ├── ping_response.jser.dart │ │ │ │ │ ├── inline_response200.jser.dart │ │ │ │ │ ├── podcasts_filter1.jser.dart │ │ │ │ │ ├── podcasts_filter.jser.dart │ │ │ │ │ ├── new_podcasts.dart │ │ │ │ │ ├── podcasts.dart │ │ │ │ │ ├── podcasts_fields.dart │ │ │ │ │ └── podcasts_partial.dart │ │ │ │ └── auth │ │ │ │ │ ├── oauth.dart │ │ │ │ │ ├── auth.dart │ │ │ │ │ ├── basic_auth.dart │ │ │ │ │ └── api_key_auth.dart │ │ │ ├── .gitignore │ │ │ ├── .openapi-generator-ignore │ │ │ └── git_push.sh │ │ ├── netcastsoss_data_api_config.dart │ │ └── itunes_search.dart │ ├── connectors │ │ ├── local_database.dart │ │ └── elastic.dart │ ├── chromecast.dart │ └── feeds │ │ └── podcast.dart ├── redux │ ├── store.dart │ └── state.dart ├── main.dart ├── widgets │ ├── podcast │ │ ├── episodes.dart │ │ ├── home.dart │ │ └── options.dart │ ├── common │ │ ├── tags.dart │ │ ├── outline_icon_button.dart │ │ ├── circular_progress_with_optional_action.dart │ │ ├── people.dart │ │ ├── episode_tile.dart │ │ ├── with_fade_in_image.dart │ │ ├── vertical_list_view.dart │ │ ├── placeholder_screen.dart │ │ ├── app_bar.dart │ │ ├── bottom_app_bar_player.dart │ │ └── toggling_widget_pair.dart │ ├── favorites │ │ └── index.dart │ ├── episode │ │ ├── info.dart │ │ └── home.dart │ ├── queue │ │ └── index.dart │ ├── downloads │ │ └── index.dart │ ├── home │ │ └── list.dart │ └── notifications.dart ├── helpers │ ├── reassemble.dart │ ├── dash.dart │ ├── podcast.dart │ └── dynamic_theme.dart ├── main_prod.dart ├── models │ ├── podcast_subscription.dart │ ├── user_episode.dart │ ├── episode_action.dart │ └── app_settings.dart ├── run_app.dart └── themes.dart ├── assets ├── icons │ └── app.png └── data │ └── podcasts.sqlite3 ├── fonts ├── OpenSans-Bold.ttf ├── OpenSans-Italic.ttf ├── OpenSans-Light.ttf ├── OpenSans-ExtraBold.ttf ├── OpenSans-Regular.ttf ├── OpenSans-SemiBold.ttf ├── OpenSans-BoldItalic.ttf ├── OpenSans-LightItalic.ttf ├── OpenSans-SemiBoldItalic.ttf └── OpenSans-ExtraBoldItalic.ttf ├── .github └── .FUNDING.yml ├── images ├── 01--default-fadein-image.jpg ├── 02--default-fadein-image.jpg ├── 03--default-fadein-image.jpg ├── 04--default-fadein-image.jpg ├── 05--default-fadein-image.jpg ├── 06--default-fadein-image.jpg ├── 07--default-fadein-image.jpg ├── 08--default-fadein-image.jpg └── drawer-header--balloons.jpg ├── .metadata ├── screenshots.yaml ├── .gitattributes ├── test_driver └── app.dart ├── packages └── podcasts-indexer │ ├── scripts │ ├── autoupdate.js │ └── data-migrator.js │ ├── package.json │ ├── .gitignore │ └── common │ └── models │ └── podcast.json ├── .gitignore ├── hear2learn.iml ├── package.json ├── notes └── jaguar-models.md ├── test ├── netcastsoss_data_api_test.dart └── podcast_feeds_test.dart ├── hear2learn_android.iml ├── keystore └── upload_certificate.pem ├── .gitlab-ci.yml └── Dockerfile /android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /lib/constants.dart: -------------------------------------------------------------------------------- 1 | const String LIBRARY_LABEL = 'Library'; 2 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/.openapi-generator/VERSION: -------------------------------------------------------------------------------- 1 | 4.3.1 -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /assets/icons/app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/assets/icons/app.png -------------------------------------------------------------------------------- /fonts/OpenSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/fonts/OpenSans-Bold.ttf -------------------------------------------------------------------------------- /.github/.FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [eemp, mirajp] 4 | -------------------------------------------------------------------------------- /fonts/OpenSans-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/fonts/OpenSans-Italic.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/fonts/OpenSans-Light.ttf -------------------------------------------------------------------------------- /assets/data/podcasts.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/assets/data/podcasts.sqlite3 -------------------------------------------------------------------------------- /fonts/OpenSans-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/fonts/OpenSans-ExtraBold.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/fonts/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/fonts/OpenSans-SemiBold.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/fonts/OpenSans-BoldItalic.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/fonts/OpenSans-LightItalic.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/fonts/OpenSans-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/fonts/OpenSans-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /images/01--default-fadein-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/images/01--default-fadein-image.jpg -------------------------------------------------------------------------------- /images/02--default-fadein-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/images/02--default-fadein-image.jpg -------------------------------------------------------------------------------- /images/03--default-fadein-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/images/03--default-fadein-image.jpg -------------------------------------------------------------------------------- /images/04--default-fadein-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/images/04--default-fadein-image.jpg -------------------------------------------------------------------------------- /images/05--default-fadein-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/images/05--default-fadein-image.jpg -------------------------------------------------------------------------------- /images/06--default-fadein-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/images/06--default-fadein-image.jpg -------------------------------------------------------------------------------- /images/07--default-fadein-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/images/07--default-fadein-image.jpg -------------------------------------------------------------------------------- /images/08--default-fadein-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/images/08--default-fadein-image.jpg -------------------------------------------------------------------------------- /images/drawer-header--balloons.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/images/drawer-header--balloons.jpg -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/mipmap-hdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/mipmap-mdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_action_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/drawable-hdpi/ic_action_pause.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_action_stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/drawable-hdpi/ic_action_stop.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_action_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/drawable-mdpi/ic_action_pause.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_action_stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/drawable-mdpi/ic_action_stop.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_action_stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/drawable-xhdpi/ic_action_stop.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_action_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/drawable-xhdpi/ic_action_pause.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_action_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_action_pause.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_action_stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_action_stop.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_action_stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_action_stop.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_action_play_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/drawable-hdpi/ic_action_play_arrow.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_action_play_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/drawable-mdpi/ic_action_play_arrow.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_action_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_action_pause.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_action_play_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/drawable-xhdpi/ic_action_play_arrow.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_action_play_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_action_play_arrow.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_action_play_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_action_play_arrow.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /android/key.properties: -------------------------------------------------------------------------------- 1 | U2FsdGVkX182/v+8I+3qYgCldhoADTkGT10LwEjQRKtlfUZn71amkiqKfGJMOU4C 2 | SMcTsIHj5mFY4p405RCCz3nYVdstK5ZaH22vlHtX+wugXpRKD1dNMJTy5pFMclHp 3 | RE5toPkcLOYxJd1n9W4qO+Ik24UzB9it3Qx/dVoSZpw= 4 | -------------------------------------------------------------------------------- /android/fastlane/metadata/android/en-US/images/phoneScreenshots/Nexus 5X-drawer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/fastlane/metadata/android/en-US/images/phoneScreenshots/Nexus 5X-drawer.png -------------------------------------------------------------------------------- /android/fastlane/metadata/android/en-US/images/phoneScreenshots/Nexus 5X-home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/fastlane/metadata/android/en-US/images/phoneScreenshots/Nexus 5X-home.png -------------------------------------------------------------------------------- /android/fastlane/metadata/android/en-US/images/phoneScreenshots/Nexus 5X-episode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/fastlane/metadata/android/en-US/images/phoneScreenshots/Nexus 5X-episode.png -------------------------------------------------------------------------------- /android/fastlane/metadata/android/en-US/images/phoneScreenshots/Nexus 5X-episodes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/fastlane/metadata/android/en-US/images/phoneScreenshots/Nexus 5X-episodes.png -------------------------------------------------------------------------------- /android/fastlane/metadata/android/en-US/images/phoneScreenshots/Nexus 5X-explore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/fastlane/metadata/android/en-US/images/phoneScreenshots/Nexus 5X-explore.png -------------------------------------------------------------------------------- /android/fastlane/metadata/android/en-US/images/phoneScreenshots/Nexus 5X-podcast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/fastlane/metadata/android/en-US/images/phoneScreenshots/Nexus 5X-podcast.png -------------------------------------------------------------------------------- /android/fastlane/metadata/android/en-US/images/phoneScreenshots/Nexus 5X-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/fastlane/metadata/android/en-US/images/phoneScreenshots/Nexus 5X-settings.png -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/fastlane/metadata/android/en-US/images/phoneScreenshots/Nexus 5X-explore-with-results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eemp/NetcastsOSS/HEAD/android/fastlane/metadata/android/en-US/images/phoneScreenshots/Nexus 5X-explore-with-results.png -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/.travis.yml: -------------------------------------------------------------------------------- 1 | # https://docs.travis-ci.com/user/languages/dart/ 2 | # 3 | language: dart 4 | dart: 5 | # Install a specific stable release 6 | - "1.24.3" 7 | install: 8 | - pub get 9 | 10 | script: 11 | - pub run test 12 | -------------------------------------------------------------------------------- /ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 27321ebbad34b0a3fafe99fac037102196d655ff 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /screenshots.yaml: -------------------------------------------------------------------------------- 1 | # Screen capture tests 2 | tests: 3 | - test_driver/app.dart 4 | 5 | # Interim location of screenshots from tests 6 | staging: tmp/screenshots 7 | 8 | # A list of locales supported by the app 9 | locales: 10 | - en-US 11 | 12 | # A list of devices to emulate 13 | devices: 14 | android: 15 | - Nexus 5X 16 | 17 | # Frame screenshots 18 | frame: false 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | #pattern filter=crypt diff=crypt 2 | lib/main_prod.dart filter=crypt diff=crypt 3 | keystore/keystore.jks filter=crypt diff=crypt 4 | keystore/upload_certificate.pem filter=crypt diff=crypt 5 | android/key.properties filter=crypt diff=crypt 6 | android/app/google-services.json filter=crypt diff=crypt 7 | packages/podcasts-indexer/server/datasources.json filter=crypt diff=crypt 8 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: netcastsoss_data_api 2 | version: 1.0.0 3 | description: OpenAPI API client 4 | environment: 5 | sdk: ">=2.1.0 <3.0.0" 6 | dependencies: 7 | jaguar_retrofit: ^2.8.8 8 | jaguar_serializer: ^2.2.12 9 | dev_dependencies: 10 | jaguar_retrofit_gen: ^2.8.10 11 | jaguar_serializer_cli: ^2.2.8 12 | build_runner: ^1.6.5 13 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/test/loopback_count_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:netcastsoss_data_api/api.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | // tests for LoopbackCount 5 | void main() { 6 | var instance = new LoopbackCount(); 7 | 8 | group('test LoopbackCount', () { 9 | // num count (default value: null) 10 | test('to test the property `count`', () async { 11 | // TODO 12 | }); 13 | 14 | 15 | }); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/test/ping_controller_api_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:netcastsoss_data_api/api.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | 5 | /// tests for PingControllerApi 6 | void main() { 7 | var instance = new PingControllerApi(); 8 | 9 | group('tests for PingControllerApi', () { 10 | //Future pingControllerPing() async 11 | test('test pingControllerPing', () async { 12 | // TODO 13 | }); 14 | 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /lib/redux/store.dart: -------------------------------------------------------------------------------- 1 | import 'package:hear2learn/redux/reducer.dart'; 2 | import 'package:hear2learn/redux/state.dart'; 3 | import 'package:redux/redux.dart'; 4 | import 'package:redux_thunk/redux_thunk.dart'; 5 | 6 | final Store store = Store( 7 | AppReducer, 8 | initialState: AppState(), 9 | // ignore: always_specify_types 10 | middleware: [ 11 | thunkMiddleware, 12 | ], 13 | ); 14 | 15 | Store appStore() { 16 | return store; 17 | } 18 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data_api_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:openapi_generator_annotations/openapi_generator_annotations.dart'; 2 | 3 | @Openapi( 4 | additionalProperties: AdditionalProperties(pubName: 'netcastsoss_data_api'), 5 | alwaysRun: true, 6 | inputSpecFile: 'https://netcastsoss-data.herokuapp.com/openapi.json', 7 | generatorName: 'dart-jaguar', 8 | outputDirectory: 'lib/services/api/netcastsoss_data') 9 | class NetcastsOSSDataConfig extends OpenapiGeneratorConfig {} 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'dart:typed_data'; 4 | 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter/services.dart'; 7 | 8 | import 'package:hear2learn/app.dart'; 9 | import 'package:hear2learn/run_app.dart'; 10 | import 'package:path_provider/path_provider.dart'; 11 | import 'package:path/path.dart'; 12 | 13 | Future main() async { 14 | WidgetsFlutterBinding.ensureInitialized(); 15 | 16 | final App app = App(); 17 | await app.init(); 18 | 19 | await start(); 20 | } 21 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/doc/LoopbackCount.md: -------------------------------------------------------------------------------- 1 | # netcastsoss_data_api.model.LoopbackCount 2 | 3 | ## Load the model package 4 | ```dart 5 | import 'package:netcastsoss_data_api/api.dart'; 6 | ``` 7 | 8 | ## Properties 9 | Name | Type | Description | Notes 10 | ------------ | ------------- | ------------- | ------------- 11 | **count** | **num** | | [optional] [default to null] 12 | 13 | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) 14 | 15 | 16 | -------------------------------------------------------------------------------- /test_driver/app.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_driver/driver_extension.dart'; 5 | import 'package:hear2learn/main_prod.dart' as app; // ignore: uri_does_not_exist 6 | 7 | Future main() async { 8 | // This line enables the extension 9 | enableFlutterDriverExtension(); 10 | 11 | WidgetsApp.debugAllowBannerOverride = false; 12 | 13 | // Call the `main()` function of your app or call `runApp` with any widget you 14 | // are interested in testing. 15 | await app.main(); 16 | } 17 | 18 | -------------------------------------------------------------------------------- /lib/widgets/podcast/episodes.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:hear2learn/models/episode.dart'; 4 | import 'package:hear2learn/widgets/common/episode_list.dart'; 5 | 6 | class PodcastEpisodesList extends StatelessWidget { 7 | final List episodes; 8 | 9 | const PodcastEpisodesList({ 10 | Key key, 11 | this.episodes, 12 | }) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return EpisodesList( 17 | episodes: episodes, 18 | episodeQueue: EpisodeQueue.PODCAST, 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | #Flutter Wrapper 2 | -keep class io.flutter.app.** { *; } 3 | -keep class io.flutter.plugin.** { *; } 4 | -keep class io.flutter.util.** { *; } 5 | -keep class io.flutter.view.** { *; } 6 | -keep class io.flutter.** { *; } 7 | -keep class io.flutter.plugins.** { *; } 8 | -dontnote android.net.http.* 9 | -dontnote org.apache.commons.codec.** 10 | -dontnote org.apache.http.** 11 | #In app Purchase 12 | -keep class com.amazon.** {*;} 13 | -keep class com.dooboolab.** { *; } 14 | -keep class com.android.vending.billing.** 15 | -dontwarn com.amazon.** 16 | -keepattributes *Annotation* 17 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/test/inline_response200_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:netcastsoss_data_api/api.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | // tests for InlineResponse200 5 | void main() { 6 | var instance = new InlineResponse200(); 7 | 8 | group('test InlineResponse200', () { 9 | // String genre (default value: null) 10 | test('to test the property `genre`', () async { 11 | // TODO 12 | }); 13 | 14 | // List items (default value: const []) 15 | test('to test the property `items`', () async { 16 | // TODO 17 | }); 18 | 19 | 20 | }); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/lib/api/ping_controller_api.jretro.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'ping_controller_api.dart'; 4 | 5 | // ************************************************************************** 6 | // JaguarHttpGenerator 7 | // ************************************************************************** 8 | 9 | abstract class _$PingControllerApiClient implements ApiClient { 10 | final String basePath = ""; 11 | Future pingControllerPing() async { 12 | var req = base.get.path(basePath).path("/ping"); 13 | return req.go(throwOnErr: true).map(decodeOne); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/lib/model/loopback_count.dart: -------------------------------------------------------------------------------- 1 | import 'package:jaguar_serializer/jaguar_serializer.dart'; 2 | 3 | 4 | part 'loopback_count.jser.dart'; 5 | 6 | class LoopbackCount { 7 | 8 | @Alias('count', isNullable: false, ) 9 | final num count; 10 | 11 | 12 | LoopbackCount( 13 | 14 | 15 | { 16 | this.count = null 17 | 18 | } 19 | ); 20 | 21 | @override 22 | String toString() { 23 | return 'LoopbackCount[count=$count, ]'; 24 | } 25 | } 26 | 27 | @GenSerializer(nullableFields: true) 28 | class LoopbackCountSerializer extends Serializer with _$LoopbackCountSerializer { 29 | 30 | } 31 | 32 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/doc/InlineResponse200.md: -------------------------------------------------------------------------------- 1 | # netcastsoss_data_api.model.InlineResponse200 2 | 3 | ## Load the model package 4 | ```dart 5 | import 'package:netcastsoss_data_api/api.dart'; 6 | ``` 7 | 8 | ## Properties 9 | Name | Type | Description | Notes 10 | ------------ | ------------- | ------------- | ------------- 11 | **genre** | **String** | | [optional] [default to null] 12 | **items** | [**List<PodcastsWithRelations>**](PodcastsWithRelations.md) | | [optional] [default to const []] 13 | 14 | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) 15 | 16 | 17 | -------------------------------------------------------------------------------- /lib/widgets/common/tags.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:hear2learn/models/podcast.dart'; 4 | 5 | class Tags extends StatelessWidget { 6 | final List genres; 7 | 8 | const Tags({ 9 | Key key, 10 | this.genres, 11 | }) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | if(genres == null || genres.isEmpty) { 16 | return Container(height: 0.0, width: 0.0); 17 | } 18 | 19 | genres.sort((Genre a, Genre b) => a.name.length.compareTo(b.name.length)); 20 | 21 | return Wrap( 22 | children: genres.map((Genre genre) => Chip( 23 | label: Text(genre.name), 24 | )).toList(), 25 | runSpacing: -8.0, 26 | spacing: 8.0, 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/doc/PingResponse.md: -------------------------------------------------------------------------------- 1 | # netcastsoss_data_api.model.PingResponse 2 | 3 | ## Load the model package 4 | ```dart 5 | import 'package:netcastsoss_data_api/api.dart'; 6 | ``` 7 | 8 | ## Properties 9 | Name | Type | Description | Notes 10 | ------------ | ------------- | ------------- | ------------- 11 | **greeting** | **String** | | [optional] [default to null] 12 | **date** | **String** | | [optional] [default to null] 13 | **url** | **String** | | [optional] [default to null] 14 | **headers** | [**Map<String, Object>**](Object.md) | | [optional] [default to const {}] 15 | 16 | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) 17 | 18 | 19 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | classpath 'com.google.gms:google-services:3.2.1' 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | rootProject.buildDir = '../build' 23 | subprojects { 24 | project.buildDir = "${rootProject.buildDir}/${project.name}" 25 | } 26 | subprojects { 27 | project.evaluationDependsOn(':app') 28 | } 29 | 30 | task clean(type: Delete) { 31 | delete rootProject.buildDir 32 | } 33 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://www.dartlang.org/tools/private-files.html 2 | 3 | # Files and directories created by pub 4 | .buildlog 5 | .packages 6 | .project 7 | .pub/ 8 | build/ 9 | **/packages/ 10 | 11 | # Files created by dart2js 12 | # (Most Dart developers will use pub build to compile Dart, use/modify these 13 | # rules if you intend to use dart2js directly 14 | # Convention is to use extension '.dart.js' for Dart compiled to Javascript to 15 | # differentiate from explicit Javascript files) 16 | *.dart.js 17 | *.part.js 18 | *.js.deps 19 | *.js.map 20 | *.info.json 21 | 22 | # Directory created by dartdoc 23 | doc/api/ 24 | 25 | # Don't commit pubspec lock file 26 | # (Library packages only! Remove pattern if developing an application package) 27 | pubspec.lock 28 | -------------------------------------------------------------------------------- /lib/helpers/reassemble.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | 5 | class ReassembleListener extends StatefulWidget { 6 | const ReassembleListener({Key key, this.onReassemble, this.child}) 7 | : super(key: key); 8 | 9 | final VoidCallback onReassemble; 10 | final Widget child; 11 | 12 | @override 13 | _ReassembleListenerState createState() => _ReassembleListenerState(); 14 | } 15 | 16 | class _ReassembleListenerState extends State { 17 | @override 18 | void reassemble() { 19 | super.reassemble(); 20 | if (widget.onReassemble != null) { 21 | widget.onReassemble(); 22 | } 23 | } 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return widget.child; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/widgets/common/outline_icon_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class OutlineIconButton extends StatelessWidget { 4 | final IconData icon; 5 | final Function onPressed; 6 | final double size; 7 | 8 | const OutlineIconButton({ 9 | Key key, 10 | this.icon, 11 | this.onPressed, 12 | this.size=20.0 13 | }) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return RawMaterialButton( 18 | onPressed: onPressed, 19 | child: Icon( 20 | icon, 21 | size: size, 22 | ), 23 | constraints: BoxConstraints.tight(Size.fromRadius(size)), 24 | shape: CircleBorder(side: BorderSide(color: Theme.of(context).colorScheme.onSurface.withOpacity(0.12))), 25 | elevation: 2.0, 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/podcasts-indexer/scripts/autoupdate.js: -------------------------------------------------------------------------------- 1 | const Promise = require('bluebird'); 2 | const { DataSource } = require('loopback-datasource-juggler'); 3 | 4 | const datasources = require('../server/datasources.json'); 5 | const modelDefinition = require('../common/models/podcast.json'); 6 | const { name:modelName, properties:modelProperties, settings: modelSettings } = modelDefinition; 7 | 8 | const dsName = process.env.DS || 'mysql'; 9 | const DS = new DataSource(dsName, datasources[dsName]); 10 | const model = DS.define(modelName, modelProperties, modelSettings); 11 | 12 | DS.autoupdate(modelName).then(() => { 13 | console.log(`Autoupdate ${dsName} based on model.`); 14 | process.exit(0); 15 | }).catch(err => { 16 | console.error('Unable to migrate podcasts data, encountered: ', err.message); 17 | process.exit(-1); 18 | }); 19 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/doc/PodcastsFilter1.md: -------------------------------------------------------------------------------- 1 | # netcastsoss_data_api.model.PodcastsFilter1 2 | 3 | ## Load the model package 4 | ```dart 5 | import 'package:netcastsoss_data_api/api.dart'; 6 | ``` 7 | 8 | ## Properties 9 | Name | Type | Description | Notes 10 | ------------ | ------------- | ------------- | ------------- 11 | **offset** | **int** | | [optional] [default to null] 12 | **limit** | **int** | | [optional] [default to null] 13 | **skip** | **int** | | [optional] [default to null] 14 | **order** | **List<String>** | | [optional] [default to const []] 15 | **fields** | [**PodcastsFields**](PodcastsFields.md) | | [optional] [default to null] 16 | 17 | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) 18 | 19 | 20 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/lib/auth/oauth.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:netcastsoss_data_api/auth/auth.dart'; 3 | import 'package:jaguar_retrofit/jaguar_retrofit.dart'; 4 | 5 | class OAuthInterceptor extends AuthInterceptor { 6 | Map tokens = {}; 7 | 8 | @override 9 | FutureOr before(RouteBase route) { 10 | final authInfo = getAuthInfo(route, "oauth"); 11 | for (var info in authInfo) { 12 | final token = tokens[info["name"]]; 13 | if(token != null) { 14 | route.header("Authorization", "Bearer ${token}"); 15 | break; 16 | } 17 | } 18 | return super.before(route); 19 | } 20 | 21 | @override 22 | FutureOr after(StringResponse response) { 23 | return Future.value(response); 24 | } 25 | } -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/lib/auth/auth.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:jaguar_retrofit/jaguar_retrofit.dart'; 4 | 5 | abstract class AuthInterceptor extends Interceptor { 6 | /* 7 | * Get auth information on given route for the given type 8 | * Can return null if type is not present on auth data or if route doesn't need authentication 9 | */ 10 | List> getAuthInfo(RouteBase route, String type) { 11 | if (route.metadataMap.containsKey("auth")) { 12 | final auth = route.metadataMap["auth"]; 13 | List> results = []; 14 | for (var info in auth) { 15 | if(info["type"] == type) { 16 | results.add(info); 17 | } 18 | } 19 | return results; 20 | } 21 | return []; 22 | } 23 | } -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/test/ping_response_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:netcastsoss_data_api/api.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | // tests for PingResponse 5 | void main() { 6 | var instance = new PingResponse(); 7 | 8 | group('test PingResponse', () { 9 | // String greeting (default value: null) 10 | test('to test the property `greeting`', () async { 11 | // TODO 12 | }); 13 | 14 | // String date (default value: null) 15 | test('to test the property `date`', () async { 16 | // TODO 17 | }); 18 | 19 | // String url (default value: null) 20 | test('to test the property `url`', () async { 21 | // TODO 22 | }); 23 | 24 | // Map headers (default value: const {}) 25 | test('to test the property `headers`', () async { 26 | // TODO 27 | }); 28 | 29 | 30 | }); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /lib/services/connectors/local_database.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:jaguar_query_sqflite/jaguar_query_sqflite.dart'; 4 | 5 | class LocalDatabaseAdapter { 6 | final String path; 7 | SqfliteAdapter adapter; 8 | 9 | // _cache is library-private, thanks to 10 | // the _ in front of its name. 11 | static final Map _cache = 12 | {}; 13 | 14 | factory LocalDatabaseAdapter(String path) { 15 | if (_cache.containsKey(path)) { 16 | return _cache[path]; 17 | } else { 18 | final LocalDatabaseAdapter adapter = LocalDatabaseAdapter._internal(path); 19 | _cache[path] = adapter; 20 | return adapter; 21 | } 22 | } 23 | 24 | LocalDatabaseAdapter._internal(this.path); 25 | 26 | Future init() async { 27 | adapter = SqfliteAdapter(path); 28 | await adapter.connect(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/lib/model/loopback_count.jser.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'loopback_count.dart'; 4 | 5 | // ************************************************************************** 6 | // JaguarSerializerGenerator 7 | // ************************************************************************** 8 | 9 | abstract class _$LoopbackCountSerializer implements Serializer { 10 | @override 11 | Map toMap(LoopbackCount model) { 12 | if (model == null) return null; 13 | Map ret = {}; 14 | setMapValueIfNotNull(ret, 'count', model.count); 15 | return ret; 16 | } 17 | 18 | @override 19 | LoopbackCount fromMap(Map map) { 20 | if (map == null) return null; 21 | final obj = 22 | LoopbackCount(count: map['count'] as num ?? getJserDefault('count')); 23 | return obj; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/lib/model/inline_response200.dart: -------------------------------------------------------------------------------- 1 | import 'package:jaguar_serializer/jaguar_serializer.dart'; 2 | 3 | 4 | import 'package:netcastsoss_data_api/model/podcasts_with_relations.dart'; 5 | 6 | part 'inline_response200.jser.dart'; 7 | 8 | class InlineResponse200 { 9 | 10 | @Alias('genre', isNullable: false, ) 11 | final String genre; 12 | 13 | @Alias('items', isNullable: false, ) 14 | final List items; 15 | 16 | 17 | InlineResponse200( 18 | 19 | 20 | { 21 | this.genre = null, 22 | this.items = const [] 23 | 24 | } 25 | ); 26 | 27 | @override 28 | String toString() { 29 | return 'InlineResponse200[genre=$genre, items=$items, ]'; 30 | } 31 | } 32 | 33 | @GenSerializer(nullableFields: true) 34 | class InlineResponse200Serializer extends Serializer with _$InlineResponse200Serializer { 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/doc/PodcastsFilter.md: -------------------------------------------------------------------------------- 1 | # netcastsoss_data_api.model.PodcastsFilter 2 | 3 | ## Load the model package 4 | ```dart 5 | import 'package:netcastsoss_data_api/api.dart'; 6 | ``` 7 | 8 | ## Properties 9 | Name | Type | Description | Notes 10 | ------------ | ------------- | ------------- | ------------- 11 | **offset** | **int** | | [optional] [default to null] 12 | **limit** | **int** | | [optional] [default to null] 13 | **skip** | **int** | | [optional] [default to null] 14 | **order** | **List<String>** | | [optional] [default to const []] 15 | **where** | [**Map<String, Object>**](Object.md) | | [optional] [default to const {}] 16 | **fields** | [**PodcastsFields**](PodcastsFields.md) | | [optional] [default to null] 17 | 18 | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) 19 | 20 | 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | 39 | # ** CUSTOM ** 40 | linux/ 41 | tmp/ 42 | .vscode/ 43 | coverage/ 44 | linux/ 45 | # *.jorm.dart 46 | 47 | ## security 48 | # lib/main_prod.dart 49 | # android/key.properties 50 | -------------------------------------------------------------------------------- /hear2learn.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/lib/api/ping_controller_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:jaguar_retrofit/annotations/annotations.dart'; 2 | import 'package:jaguar_retrofit/jaguar_retrofit.dart'; 3 | import 'package:jaguar_serializer/jaguar_serializer.dart'; 4 | import 'package:jaguar_mimetype/jaguar_mimetype.dart'; 5 | import 'dart:async'; 6 | 7 | import 'package:netcastsoss_data_api/model/ping_response.dart'; 8 | 9 | part 'ping_controller_api.jretro.dart'; 10 | 11 | @GenApiClient() 12 | class PingControllerApi extends ApiClient with _$PingControllerApiClient { 13 | final Route base; 14 | final Map converters; 15 | final Duration timeout; 16 | 17 | PingControllerApi({this.base, this.converters, this.timeout = const Duration(minutes: 2)}); 18 | 19 | /// 20 | /// 21 | /// 22 | @GetReq(path: "/ping") 23 | Future pingControllerPing( 24 | ) { 25 | return super.pingControllerPing( 26 | 27 | ).timeout(timeout); 28 | } 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/test/podcasts_filter1_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:netcastsoss_data_api/api.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | // tests for PodcastsFilter1 5 | void main() { 6 | var instance = new PodcastsFilter1(); 7 | 8 | group('test PodcastsFilter1', () { 9 | // int offset (default value: null) 10 | test('to test the property `offset`', () async { 11 | // TODO 12 | }); 13 | 14 | // int limit (default value: null) 15 | test('to test the property `limit`', () async { 16 | // TODO 17 | }); 18 | 19 | // int skip (default value: null) 20 | test('to test the property `skip`', () async { 21 | // TODO 22 | }); 23 | 24 | // List order (default value: const []) 25 | test('to test the property `order`', () async { 26 | // TODO 27 | }); 28 | 29 | // PodcastsFields fields (default value: null) 30 | test('to test the property `fields`', () async { 31 | // TODO 32 | }); 33 | 34 | 35 | }); 36 | 37 | } 38 | -------------------------------------------------------------------------------- /lib/services/chromecast.dart: -------------------------------------------------------------------------------- 1 | // Taken from https://github.com/terrabythia/flutter_chromecast_example 2 | import 'package:flutter_mdns_plugin/flutter_mdns_plugin.dart'; 3 | import 'package:observable/observable.dart'; 4 | 5 | class ServiceDiscovery extends ChangeNotifier { 6 | 7 | FlutterMdnsPlugin _flutterMdnsPlugin; 8 | List foundServices = []; 9 | 10 | ServiceDiscovery() { 11 | _flutterMdnsPlugin = FlutterMdnsPlugin( 12 | discoveryCallbacks: DiscoveryCallbacks( 13 | onDiscoveryStarted: () => {}, 14 | onDiscoveryStopped: () => {}, 15 | onDiscovered: (ServiceInfo serviceInfo) => {}, 16 | onResolved: (ServiceInfo serviceInfo) { 17 | foundServices.add(serviceInfo); 18 | notifyChange(); 19 | } 20 | ) 21 | ); 22 | 23 | } 24 | 25 | startDiscovery() { 26 | _flutterMdnsPlugin.startDiscovery('_googlecast._tcp'); 27 | } 28 | 29 | stopDiscovery() { 30 | _flutterMdnsPlugin.stopDiscovery(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/lib/model/ping_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:jaguar_serializer/jaguar_serializer.dart'; 2 | 3 | 4 | part 'ping_response.jser.dart'; 5 | 6 | class PingResponse { 7 | 8 | @Alias('greeting', isNullable: false, ) 9 | final String greeting; 10 | 11 | @Alias('date', isNullable: false, ) 12 | final String date; 13 | 14 | @Alias('url', isNullable: false, ) 15 | final String url; 16 | 17 | @Alias('headers', isNullable: false, ) 18 | final Map headers; 19 | 20 | 21 | PingResponse( 22 | 23 | 24 | { 25 | this.greeting = null, 26 | this.date = null, 27 | this.url = null, 28 | this.headers = const {} 29 | 30 | } 31 | ); 32 | 33 | @override 34 | String toString() { 35 | return 'PingResponse[greeting=$greeting, date=$date, url=$url, headers=$headers, ]'; 36 | } 37 | } 38 | 39 | @GenSerializer(nullableFields: true) 40 | class PingResponseSerializer extends Serializer with _$PingResponseSerializer { 41 | 42 | } 43 | 44 | -------------------------------------------------------------------------------- /lib/widgets/common/circular_progress_with_optional_action.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:hear2learn/helpers/dash.dart' as dash; 4 | import 'package:percent_indicator/percent_indicator.dart'; 5 | 6 | class CircularProgressWithOptionalAction extends StatelessWidget { 7 | final Icon icon; 8 | final Function onPressed; 9 | final double progress; 10 | 11 | const CircularProgressWithOptionalAction({ 12 | Key key, 13 | this.icon, 14 | this.onPressed, 15 | this.progress, 16 | }) : super(key: key); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return GestureDetector( 21 | child: CircularPercentIndicator( 22 | backgroundColor: progress == null 23 | ? Colors.transparent 24 | : const Color(0xFFB8C7CB), 25 | center: icon, 26 | lineWidth: 3.0, 27 | percent: dash.clamp(0.0, progress ?? 0.0, 1.0), 28 | radius: 40.0, 29 | progressColor: Theme.of(context).accentColor, 30 | ), 31 | onTap: onPressed, 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/widgets/common/people.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class PeopleList extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return ListView( 7 | children: [ 8 | Container( 9 | child: const ListTile( 10 | title: Text('Chuck Nice'), 11 | subtitle: Text('Host'), 12 | leading: CircleAvatar( 13 | backgroundImage: NetworkImage('https://upload.wikimedia.org/wikipedia/commons/3/35/Chuck_Nice_at_Caroline%27s.jpg'), 14 | ), 15 | ), 16 | ), 17 | Container( 18 | child: const ListTile( 19 | title: Text('Neil deGrasse Tyson'), 20 | leading: CircleAvatar( 21 | backgroundImage: NetworkImage('https://upload.wikimedia.org/wikipedia/commons/thumb/8/82/Neil_deGrasse_Tyson_in_June_2017_%28cropped%29.jpg/800px-Neil_deGrasse_Tyson_in_June_2017_%28cropped%29.jpg'), 22 | ), 23 | ), 24 | ), 25 | ], 26 | shrinkWrap: true, 27 | ); 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "NetcastsOSS", 3 | "version": "1.0.0", 4 | "description": "Simple podcasts (netcasts) management powered by open source software", 5 | "directories": { 6 | "lib": "lib", 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "build:docker": "docker build -t flutter-dev .", 11 | "clean": "docker run --rm -it --privileged --volume=$PWD:/opt/workspace flutter-dev flutter clean", 12 | "release": "npm run clean; docker run --rm -it --privileged --volume=$PWD:/opt/workspace flutter-dev flutter build appbundle --target lib/main_prod.dart", 13 | "start:docker": "docker run --rm -it --privileged --volume /dev/bus/usb:/dev/bus/usb --volume=$PWD:/opt/workspace -p \"3700:3700\" flutter-dev bash", 14 | "test": "echo \"Error: no test specified\" && exit 1" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/eemp/NetcastsOSS.git" 19 | }, 20 | "author": "Mihir Patel", 21 | "bugs": { 22 | "url": "https://github.com/eemp/NetcastsOSS/issues" 23 | }, 24 | "homepage": "https://github.com/eemp/NetcastsOSS" 25 | } 26 | -------------------------------------------------------------------------------- /lib/main_prod.dart: -------------------------------------------------------------------------------- 1 | U2FsdGVkX1/h4lC1g9rd6A27gOds97RtahuRSw4R0Q7QHACXeW67U2pxmefeSE0P 2 | jHzXuGlp9Sf+wY+qj6FTATn5vScUroQFweX6tKS4mgU1o8YZJANK44uMPjIxhkug 3 | KejkDYth8QBQb2bYhZfe9XF9FGXpXqb2N8MxZvWQQLSzGyOmtMdezgYjPSPjyOmK 4 | 93MeN1aaTZJczCDHxuoiXT4YdyNR/xbYuzYZU+SR02RAsTbbbQgIGLde6Z1ZX50K 5 | fBzni7I7imyrwYfGqA5hMy70x67bSYqiqA8Kq9TJnfAgY8B6BgxG81xdwYtilzDO 6 | zWFd8SUiZ23Rdh5A7FGVPPfaVr6tlz5wjnpC8xS3EXd2C7bN4UHwCdwUA2xtqLqY 7 | MrTZKpaGZ0gSzs72Y00Zo+6/+BbrqFOVvh+ig/HgyaeqUenRdOuJyqX1MWfr+7Es 8 | 1yh3XyVbezR4RoOSPnSrR6Cb/e1AGuOOFj2IjC8uoGDhSge068C7PIrHH1ihyvti 9 | /AUBylEXRBtWqV3HFeRh7muqHlpaaxkjBSsyZ/1a6Uunfy1Fpk+mNEKHWujuXFEE 10 | LzqFDJnGicW/C4iwHOvp452HGqgIMo4Z/6NHAb3NUb8+2zEoPZzmkTmjMxura6BH 11 | y3SLjnNyFoOT03PYuHAZdMbRc0RHnyD3ZpAHy670/csWeA6hXvkrsiGWwZ/B7g8H 12 | vV8LcMTDCOuq+Sdb/AEUqgzqRekApF5tymksM79Ujv+46NwbG0pHIBTO1XbDFm3E 13 | AxnNHMuRUBl93B6U8tZwZkmqvpOxXVXjh3JdOBH5UgX+bDNxQKqU23Xr1PkTBGGr 14 | gH6u5wwIvWqae38q6E2bstEbRsiQZyHnKd0XMhhsomZ81LMYycmzSoE2PSX25q5U 15 | S/F1DgQic7vl3uQzJOSzVdCWOA9uqLDZQQUcM3nKNunpK3QT+alUlMEM0u4tzYUN 16 | eFi3qfTy9bn2XHC9d3MTF4YoYucai8ZRjIVzPKO+boq3uVoAff4o64rM1ngnPJTk 17 | uqUKHMJ/BAJHpYOQ8SEDuw== 18 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/lib/auth/basic_auth.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:netcastsoss_data_api/auth/auth.dart'; 3 | import 'package:jaguar_retrofit/jaguar_retrofit.dart'; 4 | 5 | class BasicAuthInfo { 6 | final String username; 7 | final String password; 8 | 9 | const BasicAuthInfo(this.username, this.password); 10 | 11 | } 12 | 13 | class BasicAuthInterceptor extends AuthInterceptor { 14 | Map authInfo = {}; 15 | 16 | @override 17 | FutureOr before(RouteBase route) { 18 | final metadataAuthInfo = getAuthInfo(route, "basic"); 19 | for (var info in metadataAuthInfo) { 20 | final authName = info["name"]; 21 | final basicAuthInfo = authInfo[authName]; 22 | if(basicAuthInfo != null) { 23 | route.basicAuth(basicAuthInfo.username, basicAuthInfo.password); 24 | break; 25 | } 26 | } 27 | return super.before(route); 28 | } 29 | 30 | @override 31 | FutureOr after(StringResponse response) { 32 | return Future.value(response); 33 | } 34 | } -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/.openapi-generator-ignore: -------------------------------------------------------------------------------- 1 | # OpenAPI Generator Ignore 2 | # Generated by openapi-generator https://github.com/openapitools/openapi-generator 3 | 4 | # Use this file to prevent files from being overwritten by the generator. 5 | # The patterns follow closely to .gitignore or .dockerignore. 6 | 7 | # As an example, the C# client generator defines ApiClient.cs. 8 | # You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: 9 | #ApiClient.cs 10 | 11 | # You can match any string of characters against a directory, file or extension with a single asterisk (*): 12 | #foo/*/qux 13 | # The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux 14 | 15 | # You can recursively match patterns against a directory, file or extension with a double asterisk (**): 16 | #foo/**/qux 17 | # This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux 18 | 19 | # You can also negate patterns with an exclamation (!). 20 | # For example, you can ignore all files in a docs folder with the file extension .md: 21 | #docs/*.md 22 | # Then explicitly reverse the ignore rule for a single file: 23 | #!docs/README.md 24 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/lib/auth/api_key_auth.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:netcastsoss_data_api/auth/auth.dart'; 3 | import 'package:jaguar_retrofit/jaguar_retrofit.dart'; 4 | 5 | class ApiKeyAuthInterceptor extends AuthInterceptor { 6 | Map apiKeys = {}; 7 | 8 | @override 9 | FutureOr before(RouteBase route) { 10 | final authInfo = getAuthInfo(route, "apiKey"); 11 | for (var info in authInfo) { 12 | final authName = info["name"]; 13 | final authKeyName = info["keyName"]; 14 | final authWhere = info["where"]; 15 | final apiKey = apiKeys[authName]; 16 | if(apiKey != null) { 17 | if(authWhere == 'query'){ 18 | route.query(authKeyName, apiKey); 19 | } 20 | else { 21 | route.header(authKeyName, apiKey); 22 | } 23 | break; 24 | } 25 | } 26 | return super.before(route); 27 | } 28 | 29 | @override 30 | FutureOr after(StringResponse response) { 31 | return Future.value(response); 32 | } 33 | } -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/test/podcasts_filter_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:netcastsoss_data_api/api.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | // tests for PodcastsFilter 5 | void main() { 6 | var instance = new PodcastsFilter(); 7 | 8 | group('test PodcastsFilter', () { 9 | // int offset (default value: null) 10 | test('to test the property `offset`', () async { 11 | // TODO 12 | }); 13 | 14 | // int limit (default value: null) 15 | test('to test the property `limit`', () async { 16 | // TODO 17 | }); 18 | 19 | // int skip (default value: null) 20 | test('to test the property `skip`', () async { 21 | // TODO 22 | }); 23 | 24 | // List order (default value: const []) 25 | test('to test the property `order`', () async { 26 | // TODO 27 | }); 28 | 29 | // Map where (default value: const {}) 30 | test('to test the property `where`', () async { 31 | // TODO 32 | }); 33 | 34 | // PodcastsFields fields (default value: null) 35 | test('to test the property `fields`', () async { 36 | // TODO 37 | }); 38 | 39 | 40 | }); 41 | 42 | } 43 | -------------------------------------------------------------------------------- /packages/podcasts-indexer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "podcasts-indexer", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "autoupdate:mongodb": "DS=mongodb node scripts/autoupdate.js", 8 | "build:data:mongodb": "DS=mongodb node scripts/itunes-data-fetcher.js", 9 | "build:feeds-data:mongodb": "DS=mongodb node scripts/feed-data-fetcher.js", 10 | "migrate:mysql-sqlite": "FROM_DS=mysql TO_DS=sqlite node scripts/data-migrator.js", 11 | "migrate:sqlite-mongodb": "FROM_DS=sqlite TO_DS=mongodb node scripts/data-migrator.js", 12 | "migrate:sqlite-mysql": "FROM_DS=sqlite TO_DS=mysql node scripts/data-migrator.js", 13 | "test": "echo \"Error: no test specified\" && exit 1" 14 | }, 15 | "keywords": [], 16 | "author": "Mihir Patel", 17 | "dependencies": { 18 | "axios": "^0.21.2", 19 | "bluebird": "^3.5.5", 20 | "bottleneck": "^2.19.5", 21 | "highland": "^2.13.5", 22 | "loopback-connector-es": "^1.4.1", 23 | "loopback-connector-mongodb": "^6.0.0", 24 | "loopback-connector-mysql": "^5.4.1", 25 | "loopback-connector-sqlite3": "^3.0.0", 26 | "loopback-datasource-juggler": "^4.9.0", 27 | "rss-parser": "^3.7.6", 28 | "sqlite3": "^5.0.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/lib/model/podcasts_filter1.dart: -------------------------------------------------------------------------------- 1 | import 'package:jaguar_serializer/jaguar_serializer.dart'; 2 | 3 | 4 | import 'package:netcastsoss_data_api/model/podcasts_fields.dart'; 5 | 6 | part 'podcasts_filter1.jser.dart'; 7 | 8 | class PodcastsFilter1 { 9 | 10 | @Alias('offset', isNullable: false, ) 11 | final int offset; 12 | 13 | @Alias('limit', isNullable: false, ) 14 | final int limit; 15 | 16 | @Alias('skip', isNullable: false, ) 17 | final int skip; 18 | 19 | @Alias('order', isNullable: false, ) 20 | final List order; 21 | 22 | @Alias('fields', isNullable: false, ) 23 | final PodcastsFields fields; 24 | 25 | 26 | PodcastsFilter1( 27 | 28 | 29 | { 30 | this.offset = null, 31 | this.limit = null, 32 | this.skip = null, 33 | this.order = const [], 34 | this.fields = null 35 | 36 | } 37 | ); 38 | 39 | @override 40 | String toString() { 41 | return 'PodcastsFilter1[offset=$offset, limit=$limit, skip=$skip, order=$order, fields=$fields, ]'; 42 | } 43 | } 44 | 45 | @GenSerializer(nullableFields: true) 46 | class PodcastsFilter1Serializer extends Serializer with _$PodcastsFilter1Serializer { 47 | 48 | } 49 | 50 | -------------------------------------------------------------------------------- /notes/jaguar-models.md: -------------------------------------------------------------------------------- 1 | # Jaguar Models 2 | 3 | [jaguar_orm](https://pub.dartlang.org/packages/jaguar_orm) powers some of 4 | the models powering data persisted to sqlite. 5 | The notes below describe how to work with these models. 6 | 7 | ## Creating/Updating models 8 | 9 | Models reside in the `lib/models` directory. Please refer to 10 | the [jaguar_orm]((https://pub.dartlang.org/packages/jaguar_orm)) docs 11 | and existing app models for more information on working with these models. 12 | In addition to the manually created model files, the ORM package creates 13 | generated code supporting these models in files with `.jorm.dart` name suffixes. 14 | Upon creating new models, or updating existing models, please run the following 15 | to update the generated code: 16 | 17 | ``` 18 | flutter packages pub run build_runner build 19 | ``` 20 | 21 | ## Registering new models 22 | 23 | In order for any new model to be accessible via the `app` singleton, 24 | the new model must be register in the `initModels` method in `lib/app.dart`. 25 | 26 | ## Updating sqlite schemas 27 | 28 | In order to update existing sqlite schemas, uncomment the `await model.drop()` 29 | line and when the app starts up, a new table will replace the dropped table. 30 | 31 | ## Migrations 32 | 33 | Currently, there is no support for gracefully updating existing sqlite schemas 34 | in installed apps. 35 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/doc/PingControllerApi.md: -------------------------------------------------------------------------------- 1 | # netcastsoss_data_api.api.PingControllerApi 2 | 3 | ## Load the API package 4 | ```dart 5 | import 'package:netcastsoss_data_api/api.dart'; 6 | ``` 7 | 8 | All URIs are relative to *https://netcastsoss-data.herokuapp.com* 9 | 10 | Method | HTTP request | Description 11 | ------------- | ------------- | ------------- 12 | [**pingControllerPing**](PingControllerApi.md#pingControllerPing) | **Get** /ping | 13 | 14 | 15 | # **pingControllerPing** 16 | > PingResponse pingControllerPing() 17 | 18 | 19 | 20 | ### Example 21 | ```dart 22 | import 'package:netcastsoss_data_api/api.dart'; 23 | 24 | var api_instance = new PingControllerApi(); 25 | 26 | try { 27 | var result = api_instance.pingControllerPing(); 28 | print(result); 29 | } catch (e) { 30 | print("Exception when calling PingControllerApi->pingControllerPing: $e\n"); 31 | } 32 | ``` 33 | 34 | ### Parameters 35 | This endpoint does not need any parameter. 36 | 37 | ### Return type 38 | 39 | [**PingResponse**](PingResponse.md) 40 | 41 | ### Authorization 42 | 43 | No authorization required 44 | 45 | ### HTTP request headers 46 | 47 | - **Content-Type**: Not defined 48 | - **Accept**: application/json 49 | 50 | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) 51 | 52 | -------------------------------------------------------------------------------- /lib/widgets/common/episode_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class EpisodeTile extends StatelessWidget { 4 | final bool emphasis; 5 | final Image image; 6 | final bool isSelected; 7 | final String subtitle; 8 | final String title; 9 | final Function onLongPress; 10 | final Function onTap; 11 | final Widget options; 12 | 13 | const EpisodeTile({ 14 | Key key, 15 | this.emphasis = false, 16 | this.image, 17 | this.isSelected = false, 18 | this.options, 19 | this.subtitle, 20 | this.title, 21 | this.onLongPress, 22 | this.onTap, 23 | }) : super(key: key); 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Container( 28 | color: isSelected 29 | ? Theme.of(context).colorScheme.onSurface.withOpacity(0.12) 30 | : Theme.of(context).canvasColor, 31 | child: ListTile( 32 | contentPadding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), 33 | onLongPress: onLongPress, 34 | onTap: onTap, 35 | subtitle: Text(subtitle), 36 | title: Text(title, 37 | maxLines: 2, 38 | overflow: TextOverflow.ellipsis, 39 | style: emphasis 40 | ? const TextStyle(fontWeight: FontWeight.bold) 41 | : const TextStyle(fontWeight: FontWeight.normal), 42 | ), 43 | trailing: options, 44 | ), 45 | ); 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /test/netcastsoss_data_api_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:netcastsoss_data_api/api.dart'; 2 | import 'package:netcastsoss_data_api/model/podcasts_filter.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | 6 | void main() { 7 | final jaguarApiGen = NetcastsossDataApi(); 8 | 9 | test('Ping NetcastsOSS Data API', () async { 10 | var api_instance = jaguarApiGen.getPingControllerApi(); 11 | var result = await api_instance.pingControllerPing(); 12 | expect(result.greeting, equals('Hello from LoopBack')); 13 | expect(result.url, equals('/ping')); 14 | }); 15 | 16 | test('Fetch top podcasts by genre', () async { 17 | var api_instance = jaguarApiGen.getPodcastsControllerApi(); 18 | var result = await api_instance.podcastsControllerFindPopularPodcasts(null); 19 | expect(result.length, equals(10)); 20 | expect(result[0].genre, equals('Comedy')); 21 | expect(result[0].items[0].name, equals('The Joe Rogan Experience')); 22 | expect(result[1].genre, equals('Personal Journals')); 23 | expect(result[1].items[0].name, equals('This American Life')); 24 | }); 25 | 26 | test('Search podcasts by keywords', () async { 27 | var api_instance = jaguarApiGen.getPodcastsControllerApi(); 28 | var result = await api_instance.podcastsControllerSearchPodcastsByText('Infinite Monkey Cage', PodcastsFilter( 29 | limit: 1, 30 | )); 31 | expect(result.length, equals(1)); 32 | expect(result[0].name, equals('The Infinite Monkey Cage')); 33 | }); 34 | } 35 | 36 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/lib/model/podcasts_filter.dart: -------------------------------------------------------------------------------- 1 | import 'package:jaguar_serializer/jaguar_serializer.dart'; 2 | 3 | 4 | import 'package:netcastsoss_data_api/model/podcasts_fields.dart'; 5 | 6 | part 'podcasts_filter.jser.dart'; 7 | 8 | class PodcastsFilter { 9 | 10 | @Alias('offset', isNullable: false, ) 11 | final int offset; 12 | 13 | @Alias('limit', isNullable: false, ) 14 | final int limit; 15 | 16 | @Alias('skip', isNullable: false, ) 17 | final int skip; 18 | 19 | @Alias('order', isNullable: false, ) 20 | final List order; 21 | 22 | @Alias('where', isNullable: false, ) 23 | final Map where; 24 | 25 | @Alias('fields', isNullable: false, ) 26 | final PodcastsFields fields; 27 | 28 | 29 | PodcastsFilter( 30 | 31 | 32 | { 33 | this.offset = null, 34 | this.limit = 10, 35 | this.skip = 0, 36 | this.order = const [], 37 | this.where = const {}, 38 | this.fields = null 39 | 40 | } 41 | ); 42 | 43 | @override 44 | String toString() { 45 | // return PodcastsFilter[offset=$offset, limit=$limit, skip=$skip, order=$order, where=$where, fields=$fields, ]'; 46 | // TODO: hack, need to serialize to JSON correctly 47 | return '{"limit": $limit, "skip": $skip}'; 48 | } 49 | } 50 | 51 | @GenSerializer(nullableFields: true) 52 | class PodcastsFilterSerializer extends Serializer with _$PodcastsFilterSerializer { 53 | 54 | } 55 | 56 | -------------------------------------------------------------------------------- /lib/redux/state.dart: -------------------------------------------------------------------------------- 1 | import 'package:connectivity/connectivity.dart'; 2 | import 'package:dart_chromecast/casting/cast.dart'; 3 | import 'package:hear2learn/models/app_settings.dart'; 4 | import 'package:hear2learn/models/episode.dart'; 5 | import 'package:hear2learn/models/podcast.dart'; 6 | 7 | class AppState { 8 | final CastSender castSender; 9 | final ConnectivityResult connectivity; 10 | final String playingEpisode; 11 | AppSettings settings; 12 | final List subscriptions; 13 | Map userEpisodes; 14 | 15 | AppState({ 16 | this.castSender, 17 | this.connectivity, 18 | this.playingEpisode, 19 | this.settings, 20 | this.subscriptions, 21 | this.userEpisodes, 22 | }) { 23 | settings ??= AppSettings(); 24 | // ignore: prefer_collection_literals 25 | userEpisodes ??= Map(); 26 | } 27 | 28 | AppState copyWith({ 29 | CastSender castSender, 30 | ConnectivityResult connectivity, 31 | String playingEpisode, 32 | AppSettings settings, 33 | List subscriptions, 34 | Map userEpisodes, 35 | }) { 36 | return AppState( 37 | castSender: castSender ?? this.castSender, 38 | connectivity: connectivity ?? this.connectivity, 39 | playingEpisode: playingEpisode ?? this.playingEpisode, 40 | settings: settings ?? this.settings, 41 | subscriptions: subscriptions ?? this.subscriptions, 42 | userEpisodes: userEpisodes ?? this.userEpisodes, 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/podcasts-indexer/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | .env.test 60 | 61 | # parcel-bundler cache (https://parceljs.org/) 62 | .cache 63 | 64 | # next.js build output 65 | .next 66 | 67 | # nuxt.js build output 68 | .nuxt 69 | 70 | # vuepress build output 71 | .vuepress/dist 72 | 73 | # Serverless directories 74 | .serverless/ 75 | 76 | # FuseBox cache 77 | .fusebox/ 78 | 79 | # DynamoDB Local files 80 | .dynamodb/ 81 | -------------------------------------------------------------------------------- /lib/widgets/common/with_fade_in_image.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'package:cached_network_image/cached_network_image.dart'; 5 | import 'package:hear2learn/helpers/dash.dart' as dash; 6 | 7 | class WithFadeInImage extends StatelessWidget { 8 | final List availablePlaceholders = [ 9 | 'images/01--default-fadein-image.jpg', 10 | 'images/02--default-fadein-image.jpg', 11 | 'images/03--default-fadein-image.jpg', 12 | 'images/04--default-fadein-image.jpg', 13 | 'images/05--default-fadein-image.jpg', 14 | 'images/06--default-fadein-image.jpg', 15 | 'images/07--default-fadein-image.jpg', 16 | 'images/08--default-fadein-image.jpg', 17 | ]; 18 | final Random rng = Random(); 19 | 20 | final String heroTag; 21 | final String location; 22 | 23 | WithFadeInImage({ 24 | Key key, 25 | this.location, 26 | this.heroTag, 27 | }) : super(key: key); 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | final int randIdx = rng.nextInt(availablePlaceholders.length); 32 | final String randomPlaceholder = availablePlaceholders[randIdx]; 33 | 34 | final Widget image = Image( 35 | fit: BoxFit.cover, 36 | image: dash.isNotEmpty(location) 37 | ? CachedNetworkImageProvider(location) 38 | : AssetImage(randomPlaceholder), 39 | ); 40 | 41 | return heroTag != null 42 | ? Hero( 43 | tag: heroTag, 44 | child: image, 45 | ) 46 | : image 47 | ; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /hear2learn_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /lib/models/podcast_subscription.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:hear2learn/models/podcast.dart'; 3 | import 'package:jaguar_orm/jaguar_orm.dart'; 4 | import 'package:uuid/uuid.dart'; 5 | 6 | part 'podcast_subscription.jorm.dart'; 7 | 8 | class PodcastSubscription { 9 | @IgnoreColumn() 10 | final Uuid uuid = Uuid(); 11 | 12 | @PrimaryKey(length: 36) 13 | String id; 14 | bool isSubscribed; 15 | DateTime created; 16 | @Column(length: 0, isNullable: false) 17 | String details; 18 | @Column(length: 0, isNullable: false) 19 | String podcastId; 20 | @Column(length: 0, isNullable: false) 21 | String podcastUrl; 22 | 23 | PodcastSubscription({ 24 | this.created, 25 | this.details, 26 | this.id, 27 | this.isSubscribed, 28 | this.podcastId, 29 | this.podcastUrl, 30 | }) { 31 | id ??= uuid.v4(); 32 | } 33 | 34 | Podcast getPodcastFromDetails() { 35 | final Map decodedDetails = json.decode(details); 36 | return Podcast.fromJson(decodedDetails); 37 | } 38 | 39 | @override 40 | String toString() { 41 | return 'Id = $id: Created on ${created.toString()}, isSubscribed = ${isSubscribed.toString()}, podcastId: $podcastId, podcastUrl: $podcastUrl, details: $details'; 42 | } 43 | } 44 | 45 | @GenBean() 46 | class PodcastSubscriptionBean extends Bean with _PodcastSubscriptionBean { 47 | @override 48 | String get tableName => 'podcast_subscription'; 49 | 50 | // ignore: always_specify_types 51 | PodcastSubscriptionBean(Adapter adapter) : super(adapter); 52 | } 53 | -------------------------------------------------------------------------------- /lib/widgets/favorites/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:flutter_redux/flutter_redux.dart'; 4 | import 'package:hear2learn/app.dart'; 5 | import 'package:hear2learn/models/episode.dart'; 6 | import 'package:hear2learn/redux/selectors.dart'; 7 | import 'package:hear2learn/redux/state.dart'; 8 | import 'package:hear2learn/widgets/common/app_bar.dart'; 9 | import 'package:hear2learn/widgets/common/bottom_app_bar_player.dart'; 10 | import 'package:hear2learn/widgets/common/episode_list.dart'; 11 | import 'package:hear2learn/widgets/common/placeholder_screen.dart'; 12 | 13 | class FavoritePage extends StatelessWidget { 14 | static const String routeName = 'FavoritePage'; 15 | 16 | final App app = App(); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Scaffold( 21 | appBar: EnhancedAppBar( 22 | title: const Text('Favorites') 23 | ), 24 | body: StoreConnector>( 25 | converter: favoritesSelector, 26 | builder: (BuildContext context, List favorites) { 27 | if(favorites.isEmpty) { 28 | return const PlaceholderScreen( 29 | icon: Icons.favorite_border, 30 | subtitle: 'Favorite episodes and manage them here.', 31 | title: 'No favorites available', 32 | ); 33 | } 34 | 35 | return EpisodesList( 36 | episodes: favorites, 37 | ); 38 | }, 39 | ), 40 | bottomNavigationBar: const BottomAppBarPlayer(), 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/widgets/episode/info.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:flutter_html/flutter_html.dart'; 4 | import 'package:hear2learn/models/episode.dart'; 5 | 6 | class EpisodeInfo extends StatelessWidget { 7 | final Episode episode; 8 | 9 | const EpisodeInfo({ 10 | Key key, 11 | this.episode, 12 | }) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return ListView( 17 | children: [ 18 | //Container( 19 | //child: Text('People', style: Theme.of(context).textTheme.subhead), 20 | //margin: const EdgeInsets.only(bottom: 16.0), 21 | //), 22 | //Container( 23 | //child: PeopleList(), 24 | //margin: const EdgeInsets.only(bottom: 16.0), 25 | //), 26 | Container( 27 | child: Text('Description', style: Theme.of(context).textTheme.subhead), 28 | margin: const EdgeInsets.only(bottom: 16.0), 29 | ), 30 | Container( 31 | child: Html( 32 | data: episode.description, 33 | defaultTextStyle: Theme.of(context).textTheme.body1, 34 | ), 35 | padding: const EdgeInsets.only(bottom: 16.0), 36 | ), 37 | //Container( 38 | //child: Text('Topics', style: Theme.of(context).textTheme.subhead), 39 | //margin: const EdgeInsets.only(bottom: 16.0), 40 | //), 41 | //Container( 42 | //child: Tags(), 43 | //margin: const EdgeInsets.only(bottom: 16.0), 44 | //), 45 | ], 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/widgets/queue/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:flutter_redux/flutter_redux.dart'; 4 | import 'package:hear2learn/app.dart'; 5 | import 'package:hear2learn/models/episode.dart'; 6 | import 'package:hear2learn/redux/selectors.dart'; 7 | import 'package:hear2learn/redux/state.dart'; 8 | import 'package:hear2learn/widgets/common/app_bar.dart'; 9 | import 'package:hear2learn/widgets/common/bottom_app_bar_player.dart'; 10 | import 'package:hear2learn/widgets/common/episode_list.dart'; 11 | import 'package:hear2learn/widgets/common/placeholder_screen.dart'; 12 | 13 | class QueuedListPage extends StatelessWidget { 14 | static const String routeName = 'QueuedListPage'; 15 | 16 | final App app = App(); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Scaffold( 21 | appBar: EnhancedAppBar( 22 | title: const Text('Queue') 23 | ), 24 | body: StoreConnector>( 25 | converter: queueSelector, 26 | builder: (BuildContext context, List queue) { 27 | if(queue.isEmpty) { 28 | return const PlaceholderScreen( 29 | icon: Icons.get_app, 30 | subtitle: 'Queue episodes via podcast episode list or downloads page, and manage them here.', 31 | title: 'No queue selected yet', 32 | ); 33 | } 34 | 35 | return EpisodesList( 36 | episodes: queue, 37 | ); 38 | }, 39 | ), 40 | bottomNavigationBar: const BottomAppBarPlayer(), 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/lib/model/ping_response.jser.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'ping_response.dart'; 4 | 5 | // ************************************************************************** 6 | // JaguarSerializerGenerator 7 | // ************************************************************************** 8 | 9 | abstract class _$PingResponseSerializer implements Serializer { 10 | @override 11 | Map toMap(PingResponse model) { 12 | if (model == null) return null; 13 | Map ret = {}; 14 | setMapValueIfNotNull(ret, 'greeting', model.greeting); 15 | setMapValueIfNotNull(ret, 'date', model.date); 16 | setMapValueIfNotNull(ret, 'url', model.url); 17 | setMapValueIfNotNull( 18 | ret, 19 | 'headers', 20 | codeNonNullMap(model.headers, (val) => passProcessor.serialize(val), 21 | {})); 22 | return ret; 23 | } 24 | 25 | @override 26 | PingResponse fromMap(Map map) { 27 | if (map == null) return null; 28 | final obj = PingResponse( 29 | greeting: map['greeting'] as String ?? getJserDefault('greeting'), 30 | date: map['date'] as String ?? getJserDefault('date'), 31 | url: map['url'] as String ?? getJserDefault('url'), 32 | headers: codeNonNullMap( 33 | map['headers'] as Map, 34 | (val) => passProcessor.deserialize(val) as Object, 35 | {}) ?? 36 | getJserDefault('headers')); 37 | return obj; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/doc/PodcastsFields.md: -------------------------------------------------------------------------------- 1 | # netcastsoss_data_api.model.PodcastsFields 2 | 3 | ## Load the model package 4 | ```dart 5 | import 'package:netcastsoss_data_api/api.dart'; 6 | ``` 7 | 8 | ## Properties 9 | Name | Type | Description | Notes 10 | ------------ | ------------- | ------------- | ------------- 11 | **id** | **bool** | | [optional] [default to null] 12 | **artist** | **bool** | | [optional] [default to null] 13 | **artwork100** | **bool** | | [optional] [default to null] 14 | **artwork30** | **bool** | | [optional] [default to null] 15 | **artwork600** | **bool** | | [optional] [default to null] 16 | **artwork60** | **bool** | | [optional] [default to null] 17 | **artworkOrig** | **bool** | | [optional] [default to null] 18 | **description** | **bool** | | [optional] [default to null] 19 | **episodes** | **bool** | | [optional] [default to null] 20 | **feed** | **bool** | | [optional] [default to null] 21 | **genres** | **bool** | | [optional] [default to null] 22 | **lastModifiedTimestamp** | **bool** | | [optional] [default to null] 23 | **lastUpdated** | **bool** | | [optional] [default to null] 24 | **name** | **bool** | | [optional] [default to null] 25 | **popularity** | **bool** | | [optional] [default to null] 26 | **primaryGenre** | **bool** | | [optional] [default to null] 27 | **releaseDate** | **bool** | | [optional] [default to null] 28 | **type** | **bool** | | [optional] [default to null] 29 | 30 | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) 31 | 32 | 33 | -------------------------------------------------------------------------------- /lib/widgets/downloads/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:flutter_redux/flutter_redux.dart'; 4 | import 'package:hear2learn/app.dart'; 5 | import 'package:hear2learn/models/episode.dart'; 6 | import 'package:hear2learn/redux/selectors.dart'; 7 | import 'package:hear2learn/redux/state.dart'; 8 | import 'package:hear2learn/widgets/common/app_bar.dart'; 9 | import 'package:hear2learn/widgets/common/bottom_app_bar_player.dart'; 10 | import 'package:hear2learn/widgets/common/episode_list.dart'; 11 | import 'package:hear2learn/widgets/common/placeholder_screen.dart'; 12 | 13 | class DownloadPage extends StatelessWidget { 14 | static const String routeName = 'DownloadPage'; 15 | 16 | final App app = App(); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Scaffold( 21 | appBar: EnhancedAppBar( 22 | title: const Text('Downloads') 23 | ), 24 | body: StoreConnector>( 25 | converter: downloadsSelector, 26 | builder: (BuildContext context, List downloads) { 27 | if(downloads.isEmpty) { 28 | return const PlaceholderScreen( 29 | icon: Icons.get_app, 30 | subtitle: 'Download episodes and manage them here.', 31 | title: 'No downloads available', 32 | ); 33 | } 34 | 35 | return EpisodesList( 36 | episodes: downloads, 37 | episodeQueue: EpisodeQueue.DOWNLOADS, 38 | ); 39 | }, 40 | ), 41 | bottomNavigationBar: const BottomAppBarPlayer(), 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/doc/NewPodcasts.md: -------------------------------------------------------------------------------- 1 | # netcastsoss_data_api.model.NewPodcasts 2 | 3 | ## Load the model package 4 | ```dart 5 | import 'package:netcastsoss_data_api/api.dart'; 6 | ``` 7 | 8 | ## Properties 9 | Name | Type | Description | Notes 10 | ------------ | ------------- | ------------- | ------------- 11 | **artist** | **String** | | [optional] [default to null] 12 | **artwork100** | **String** | | [optional] [default to null] 13 | **artwork30** | **String** | | [optional] [default to null] 14 | **artwork600** | **String** | | [optional] [default to null] 15 | **artwork60** | **String** | | [optional] [default to null] 16 | **artworkOrig** | **String** | | [optional] [default to null] 17 | **description** | **String** | | [optional] [default to null] 18 | **episodes** | **String** | | [optional] [default to null] 19 | **feed** | **String** | | [default to null] 20 | **genres** | **String** | | [optional] [default to null] 21 | **lastModifiedTimestamp** | [**DateTime**](DateTime.md) | | [optional] [default to null] 22 | **lastUpdated** | [**DateTime**](DateTime.md) | | [optional] [default to null] 23 | **name** | **String** | | [optional] [default to null] 24 | **popularity** | **num** | | [optional] [default to null] 25 | **primaryGenre** | **String** | | [optional] [default to null] 26 | **releaseDate** | [**DateTime**](DateTime.md) | | [optional] [default to null] 27 | **type** | **String** | | [optional] [default to null] 28 | 29 | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) 30 | 31 | 32 | -------------------------------------------------------------------------------- /lib/widgets/common/vertical_list_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class VerticalListTile extends StatelessWidget { 4 | final Widget image; 5 | final Function onTap; 6 | final String subtitle; 7 | final String title; 8 | 9 | const VerticalListTile({ 10 | Key key, 11 | this.image, 12 | this.onTap, 13 | this.subtitle, 14 | this.title, 15 | }) : super(key: key); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return GestureDetector( 20 | onTap: onTap, 21 | child: ListTile( 22 | contentPadding: const EdgeInsets.symmetric(horizontal: 4.0, vertical: 4.0), 23 | leading: image != null 24 | ? Container( 25 | child: image, 26 | height: 80.0, 27 | width: 80.0, 28 | ) 29 | : null, 30 | subtitle: subtitle != null 31 | ? Text(subtitle, maxLines: 2, overflow: TextOverflow.ellipsis) 32 | : null, 33 | title: Text(title, maxLines: 2, overflow: TextOverflow.ellipsis), 34 | ), 35 | ); 36 | } 37 | } 38 | 39 | class VerticalListView extends StatelessWidget { 40 | final List children; 41 | 42 | const VerticalListView({ 43 | Key key, 44 | this.children, 45 | }) : super(key: key); 46 | 47 | @override 48 | Widget build(BuildContext context) { 49 | return ListView.separated( 50 | itemCount: children.length, 51 | itemBuilder: (BuildContext context, int idx) { 52 | return children[idx]; 53 | }, 54 | separatorBuilder: (BuildContext context, int idx) => const Divider(), 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/services/feeds/podcast.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | //import 'package:flutter/foundation.dart'; 4 | 5 | import 'package:flutter_cache_manager/flutter_cache_manager.dart'; 6 | import 'package:hear2learn/helpers/dash.dart' as dash; 7 | import 'package:hear2learn/models/episode.dart'; 8 | import 'package:hear2learn/models/podcast.dart'; 9 | import 'package:webfeed/webfeed.dart'; 10 | 11 | Future getPodcastFromFeed({ 12 | Podcast podcast, 13 | String url, 14 | BaseCacheManager cacheManager 15 | }) async { 16 | //final response = await http.get(url); 17 | //RssFeed feed = await dash.compute(parseFeed, response.body); 18 | url = podcast?.feed ?? url; 19 | 20 | //CacheManager.showDebugLogs = true; 21 | final File file = await (cacheManager ?? DefaultCacheManager()).getSingleFile(url); 22 | final String feedContent = await file.readAsString(); 23 | final RssFeed feed = await dash.compute(parseFeed, feedContent); 24 | 25 | final List episodes = feed.items.map((RssItem item) => Episode( 26 | description: item.description, 27 | podcastTitle: feed.title, 28 | podcastUrl: url, 29 | pubDate: item.pubDate, 30 | size: item.enclosure?.length, 31 | title: item.title, 32 | url: item.enclosure?.url, 33 | )).toList(); 34 | 35 | if(podcast != null) { 36 | podcast.episodes = episodes; 37 | } 38 | 39 | return podcast ?? Podcast( 40 | artworkOrig: feed.image?.url, 41 | description: feed.description, 42 | episodes: episodes, 43 | feed: url, 44 | name: feed.title, 45 | ); 46 | } 47 | 48 | RssFeed parseFeed(String responseBody) { 49 | return RssFeed.parse(responseBody); 50 | } 51 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/doc/Podcasts.md: -------------------------------------------------------------------------------- 1 | # netcastsoss_data_api.model.Podcasts 2 | 3 | ## Load the model package 4 | ```dart 5 | import 'package:netcastsoss_data_api/api.dart'; 6 | ``` 7 | 8 | ## Properties 9 | Name | Type | Description | Notes 10 | ------------ | ------------- | ------------- | ------------- 11 | **id** | **String** | | [default to null] 12 | **artist** | **String** | | [optional] [default to null] 13 | **artwork100** | **String** | | [optional] [default to null] 14 | **artwork30** | **String** | | [optional] [default to null] 15 | **artwork600** | **String** | | [optional] [default to null] 16 | **artwork60** | **String** | | [optional] [default to null] 17 | **artworkOrig** | **String** | | [optional] [default to null] 18 | **description** | **String** | | [optional] [default to null] 19 | **episodes** | **String** | | [optional] [default to null] 20 | **feed** | **String** | | [default to null] 21 | **genres** | **String** | | [optional] [default to null] 22 | **lastModifiedTimestamp** | [**DateTime**](DateTime.md) | | [optional] [default to null] 23 | **lastUpdated** | [**DateTime**](DateTime.md) | | [optional] [default to null] 24 | **name** | **String** | | [optional] [default to null] 25 | **popularity** | **num** | | [optional] [default to null] 26 | **primaryGenre** | **String** | | [optional] [default to null] 27 | **releaseDate** | [**DateTime**](DateTime.md) | | [optional] [default to null] 28 | **type** | **String** | | [optional] [default to null] 29 | 30 | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) 31 | 32 | 33 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/doc/PodcastsWithRelations.md: -------------------------------------------------------------------------------- 1 | # netcastsoss_data_api.model.PodcastsWithRelations 2 | 3 | ## Load the model package 4 | ```dart 5 | import 'package:netcastsoss_data_api/api.dart'; 6 | ``` 7 | 8 | ## Properties 9 | Name | Type | Description | Notes 10 | ------------ | ------------- | ------------- | ------------- 11 | **id** | **String** | | [default to null] 12 | **artist** | **String** | | [optional] [default to null] 13 | **artwork100** | **String** | | [optional] [default to null] 14 | **artwork30** | **String** | | [optional] [default to null] 15 | **artwork600** | **String** | | [optional] [default to null] 16 | **artwork60** | **String** | | [optional] [default to null] 17 | **artworkOrig** | **String** | | [optional] [default to null] 18 | **description** | **String** | | [optional] [default to null] 19 | **episodes** | **String** | | [optional] [default to null] 20 | **feed** | **String** | | [default to null] 21 | **genres** | **String** | | [optional] [default to null] 22 | **lastModifiedTimestamp** | [**DateTime**](DateTime.md) | | [optional] [default to null] 23 | **lastUpdated** | [**DateTime**](DateTime.md) | | [optional] [default to null] 24 | **name** | **String** | | [optional] [default to null] 25 | **popularity** | **num** | | [optional] [default to null] 26 | **primaryGenre** | **String** | | [optional] [default to null] 27 | **releaseDate** | [**DateTime**](DateTime.md) | | [optional] [default to null] 28 | **type** | **String** | | [optional] [default to null] 29 | 30 | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) 31 | 32 | 33 | -------------------------------------------------------------------------------- /lib/widgets/common/placeholder_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class PlaceholderScreen extends StatelessWidget { 4 | final IconData icon; 5 | final String subtitle; 6 | final String title; 7 | 8 | const PlaceholderScreen({ 9 | Key key, 10 | this.icon, 11 | this.subtitle, 12 | this.title, 13 | }) : super(key: key); 14 | 15 | Color getIconColorWithContext(BuildContext context) { 16 | final Brightness brightness = Theme.of(context).brightness; 17 | return brightness == Brightness.dark 18 | ? Colors.grey[900] 19 | : Colors.grey[200] 20 | ; 21 | } 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return Container( 26 | child: LayoutBuilder( 27 | builder: (BuildContext context, BoxConstraints constraints) => Stack( 28 | children: [ 29 | Center( 30 | child: Icon(icon, color: getIconColorWithContext(context), size: constraints.biggest.width) 31 | ), 32 | Center( 33 | child: Column( 34 | children: [ 35 | Text(title, style: Theme.of(context).textTheme.title), 36 | Container( 37 | child: Text(subtitle, 38 | style: Theme.of(context).textTheme.subtitle, 39 | textAlign: TextAlign.center, 40 | ), 41 | padding: const EdgeInsets.all(16.0), 42 | ), 43 | ], 44 | mainAxisAlignment: MainAxisAlignment.center, 45 | ), 46 | ), 47 | ], 48 | ), 49 | ), 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/doc/PodcastsPartial.md: -------------------------------------------------------------------------------- 1 | # netcastsoss_data_api.model.PodcastsPartial 2 | 3 | ## Load the model package 4 | ```dart 5 | import 'package:netcastsoss_data_api/api.dart'; 6 | ``` 7 | 8 | ## Properties 9 | Name | Type | Description | Notes 10 | ------------ | ------------- | ------------- | ------------- 11 | **id** | **String** | | [optional] [default to null] 12 | **artist** | **String** | | [optional] [default to null] 13 | **artwork100** | **String** | | [optional] [default to null] 14 | **artwork30** | **String** | | [optional] [default to null] 15 | **artwork600** | **String** | | [optional] [default to null] 16 | **artwork60** | **String** | | [optional] [default to null] 17 | **artworkOrig** | **String** | | [optional] [default to null] 18 | **description** | **String** | | [optional] [default to null] 19 | **episodes** | **String** | | [optional] [default to null] 20 | **feed** | **String** | | [optional] [default to null] 21 | **genres** | **String** | | [optional] [default to null] 22 | **lastModifiedTimestamp** | [**DateTime**](DateTime.md) | | [optional] [default to null] 23 | **lastUpdated** | [**DateTime**](DateTime.md) | | [optional] [default to null] 24 | **name** | **String** | | [optional] [default to null] 25 | **popularity** | **num** | | [optional] [default to null] 26 | **primaryGenre** | **String** | | [optional] [default to null] 27 | **releaseDate** | [**DateTime**](DateTime.md) | | [optional] [default to null] 28 | **type** | **String** | | [optional] [default to null] 29 | 30 | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) 31 | 32 | 33 | -------------------------------------------------------------------------------- /test/podcast_feeds_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:core'; 2 | import 'dart:io'; 3 | import 'package:flutter_cache_manager/flutter_cache_manager.dart'; 4 | import 'package:flutter_cache_manager/src/cache_store.dart'; 5 | import 'package:flutter_cache_manager/src/web/web_helper.dart'; 6 | import 'package:hear2learn/models/podcast.dart'; 7 | import 'package:hear2learn/services/feeds/podcast.dart'; 8 | import 'package:mockito/mockito.dart'; 9 | import 'package:path/path.dart' as path; 10 | import 'package:test/test.dart'; 11 | 12 | void main() { 13 | test('Sample podcast feed request', () async { 14 | const url = 'https://podcasts.files.bbci.co.uk/b00snr0w.rss'; 15 | TestCacheManager testCacheManager = TestCacheManager(); 16 | final Podcast podcast = await getPodcastFromFeed( 17 | url: url, 18 | cacheManager: testCacheManager 19 | ); 20 | expect(podcast.name, equals('The Infinite Monkey Cage')); 21 | expect(podcast.episodes.isNotEmpty, equals(true)); 22 | }); 23 | } 24 | 25 | class TestCacheManager extends BaseCacheManager { 26 | TestCacheManager() : super('test', 27 | cacheStore: MockCacheStore(), 28 | webHelper: MockWebHelper(), 29 | ); 30 | 31 | @override 32 | Future getSingleFile(String url, {Map headers}) { 33 | String samplePath = path.join(path.dirname(Platform.script.path), 'sample_feed.rss'); 34 | return Future.value(File(samplePath)); 35 | } 36 | 37 | @override 38 | Future getFilePath() { 39 | //Not needed because we supply our own store 40 | throw UnimplementedError(); 41 | } 42 | } 43 | 44 | class MockCacheStore extends Mock implements CacheStore {} 45 | class MockWebHelper extends Mock implements WebHelper {} 46 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | netcastsOSS 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /lib/models/user_episode.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:hear2learn/models/episode.dart'; 3 | import 'package:jaguar_orm/jaguar_orm.dart'; 4 | import 'package:uuid/uuid.dart'; 5 | 6 | part 'user_episode.jorm.dart'; 7 | 8 | class UserEpisode { 9 | @IgnoreColumn() 10 | static final Uuid uuid = Uuid(); 11 | 12 | DateTime created; 13 | @Column(length: 0, isNullable: false) 14 | String details; 15 | @Column(length: 0, isNullable: false) 16 | String url; 17 | @PrimaryKey(length: 36) 18 | String id; 19 | 20 | UserEpisode({ 21 | this.created, 22 | this.details, 23 | this.url, 24 | this.id, 25 | }) { 26 | created ??= DateTime.now(); 27 | id ??= uuid.v4(); 28 | } 29 | 30 | @override 31 | String toString() { 32 | return 'Id = $id: Created on ${created.toString()}, url = $url, details: $details'; 33 | } 34 | 35 | Episode getEpisodeFromDetails() { 36 | final Map decodedDetails = jsonDecode(details); 37 | return Episode( 38 | description: decodedDetails['description'], 39 | media: decodedDetails['media'], 40 | podcastTitle: decodedDetails['podcastTitle'], 41 | podcastUrl: decodedDetails['podcastUrl'], 42 | pubDate: decodedDetails['pubDate'], 43 | size: decodedDetails['size'], 44 | title: decodedDetails['title'], 45 | url: decodedDetails['url'], 46 | ); 47 | } 48 | 49 | static String createNewId() { 50 | return uuid.v4(); 51 | } 52 | } 53 | 54 | @GenBean() 55 | class UserEpisodeBean extends Bean with _UserEpisodeBean { 56 | @override 57 | String get tableName => 'user_episode'; 58 | 59 | // ignore: always_specify_types 60 | UserEpisodeBean(Adapter adapter) : super(adapter); 61 | } 62 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/lib/model/inline_response200.jser.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'inline_response200.dart'; 4 | 5 | // ************************************************************************** 6 | // JaguarSerializerGenerator 7 | // ************************************************************************** 8 | 9 | abstract class _$InlineResponse200Serializer 10 | implements Serializer { 11 | Serializer __podcastsWithRelationsSerializer; 12 | Serializer get _podcastsWithRelationsSerializer => 13 | __podcastsWithRelationsSerializer ??= PodcastsWithRelationsSerializer(); 14 | @override 15 | Map toMap(InlineResponse200 model) { 16 | if (model == null) return null; 17 | Map ret = {}; 18 | setMapValueIfNotNull(ret, 'genre', model.genre); 19 | setMapValueIfNotNull( 20 | ret, 21 | 'items', 22 | codeNonNullIterable( 23 | model.items, 24 | (val) => _podcastsWithRelationsSerializer 25 | .toMap(val as PodcastsWithRelations), 26 | [])); 27 | return ret; 28 | } 29 | 30 | @override 31 | InlineResponse200 fromMap(Map map) { 32 | if (map == null) return null; 33 | final obj = InlineResponse200( 34 | genre: map['genre'] as String ?? getJserDefault('genre'), 35 | items: codeNonNullIterable( 36 | map['items'] as Iterable, 37 | (val) => _podcastsWithRelationsSerializer.fromMap(val as Map), 38 | []) ?? 39 | getJserDefault('items')); 40 | return obj; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/models/episode_action.dart: -------------------------------------------------------------------------------- 1 | import 'package:jaguar_orm/jaguar_orm.dart'; 2 | import 'package:uuid/uuid.dart'; 3 | 4 | part 'episode_action.jorm.dart'; 5 | 6 | enum EpisodeActionType { 7 | DOWNLOAD, 8 | FAVORITE, 9 | FINISH, 10 | PLAY, 11 | } 12 | 13 | class EpisodeAction { 14 | @IgnoreColumn() 15 | static final Uuid uuid = Uuid(); 16 | 17 | DateTime created; 18 | @Column(length: 0, isNullable: false) 19 | String details; 20 | @Column(length: 0, isNullable: false) 21 | String type; 22 | @Column(length: 0, isNullable: false) 23 | String url; 24 | @PrimaryKey(length: 36) 25 | String id; 26 | 27 | EpisodeAction({ 28 | EpisodeActionType actionType, 29 | this.created, 30 | this.details, 31 | this.type, 32 | this.url, 33 | this.id, 34 | }) { 35 | created ??= DateTime.now(); 36 | id ??= uuid.v4(); 37 | type = actionType != null ? actionType.toString() : type; 38 | } 39 | 40 | EpisodeAction copyWith({ 41 | DateTime created, 42 | String details, 43 | String id, 44 | String type, 45 | String url, 46 | }) { 47 | return EpisodeAction( 48 | created: created ?? this.created, 49 | details: details ?? this.details, 50 | id: id ?? this.id, 51 | type: type ?? this.type, 52 | url: url ?? this.url, 53 | ); 54 | } 55 | 56 | @override 57 | String toString() { 58 | return 'Id = $id: Created on ${created.toString()}, url = $url, details: $details'; 59 | } 60 | 61 | static String createNewId() { 62 | return uuid.v4(); 63 | } 64 | } 65 | 66 | @GenBean() 67 | class EpisodeActionBean extends Bean with _EpisodeActionBean { 68 | @override 69 | String get tableName => 'episode_action'; 70 | 71 | // ignore: always_specify_types 72 | EpisodeActionBean(Adapter adapter) : super(adapter); 73 | } 74 | -------------------------------------------------------------------------------- /keystore/upload_certificate.pem: -------------------------------------------------------------------------------- 1 | U2FsdGVkX19s5FLmwgAaocmgMZeuRqRUd6rTfkzWFxJt/DjO5XhJSLTY7p8XWy6M 2 | yjVlKJd0DnRrA79Ka7wNXlO17gim1+MB1jgtckWXwhSWZOLKdjccnl2wOb4Jlysc 3 | wtnpEvli5UQarZEMfIgUYG/8Ct60bCWW83tL+eBNdb13Zw57zkjYpcluYDRarlog 4 | 9iN8Hg+V92He20xC8tHDfcvSVTZK32hGHgR2bAetHlXodRCd6oG9fu3TbHxcVwiY 5 | /7/FuwoWH6vunLT92c7I3AeNUmck0DeVyxE9l4eIUy4MYvBsiGhGKC7nsPRGC4iz 6 | fhf8Hv0oWGNOXyjiBmDYvBQMrLtFzPDLOXp0CiJvClBTy2WMYxlVBfIEyY1cEmvj 7 | d3fjGJr0N88gL5ChBKK9ONjRGG9447LP27Iw3nLuUej7Xq0FDIdWIyrq1Ul10Qsh 8 | m7gl+V7uA5Z318diVYidWRa1t5fDAWfu94r+M4X4xB9idIremGkzZeFkKzC+JYPJ 9 | stY/BC0ARMf7RF+H6sL3US5RrLe6bdEqwBf2Khdd7Hdfvz+j1MMBs4y+CSGg9hJF 10 | PB/B+1YNrsLjPiaiXGJ5sh1PPzqBry8bxH5Tk+Dv2+dewJ11Q3e2TW3CbL32d6et 11 | vqwJN5TpdCv99pSOhJN56S+BxlzHdyozhUkEHbJoV8OyPUNK0ZJxggG+I4L8+cP+ 12 | l5waHRUOXUL3E32nlfH6cSn/CZRKir0mQx8xJrjv0GZjvrLjeyXDdmSVX3XlhF2F 13 | /PU4xl+OeooC4D7Cmuz1kcuWdQreT5Hrlioss8gYwdcmcRXkqb6PGu3j+VXYCGoN 14 | NwMQZDiQ171HbmFD9Ta2rMtDAdwx9+n+abkPFA9bPlshERhOJes0rswIsKollroi 15 | 4FYRI0JuUAFZNk0CL1TN8LOaM5MTLw6FaPGWhSU+SuR2sbeLMuwyEnrW1cAsxbJb 16 | 8gMRIMA9JSr6D4AE+rWF6OmC2xTA0WZs38OvworbdWDm/kp3f17WYVou2fNg7ydQ 17 | 84lBSvFHye36iqZBNxPWOq59VVHfzswbTplTZMX7mFFsp4yL1795PS3VnznLcVNS 18 | NQSNAlWs6cb0cCmSS+ItUK9zkNtI5h8LHTf3i6w46XHCBzp/fJcemh/v1GVmssu6 19 | vMlYLlJIkPb+bYYeN921uz9r3aE4LXIUfVeonlUhBmJauLAS1Moo9kDMwolFeoNV 20 | vHG5Qtz5EEm5qdM98P1ygcnDmo1iPV6sb2Yn01cS4obGYQJ9LCFdkEuO7oeY21aa 21 | Aorr8qYaKk/7yYmadaCU3Ob0VEo484gXkzyK8cR5qx8Kinf6yMOelrmAIfxDjWDS 22 | C6F9K3xFg3GJFOkfo/ExZBIQUKvLm2piFe2ZwzWSb/S4+scqwCcS836KXpmCdQ6R 23 | GAIPJnTDYrP/pyFUIC13Ti625EhAghbzFluewwmAXAcN3VEfh5sALEjdHMK02SsM 24 | jajV9++l2dwaBb2sR2EE6YRHjU6kL0x7CSceel3SCyk/+q7RDl9qt9Atw6hZXgDy 25 | 3HBw1ym5/uzUVAtnSV3zvArn9hBy4ECx8aslsb3zv6uh0Zw/clCPi1X9knO09KtY 26 | SL+Tg1CgRTavI4GtopjMOnUvmkQw21x2+gtShn18ImZLfG3U+qgUk5HGW/v4Jnl/ 27 | KCBUUkMtDe6BITqlbgnw1/sWSLTlRklhIw5dR8KKEiu7KdrWK7lTPaR1372g00yx 28 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/lib/model/podcasts_filter1.jser.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'podcasts_filter1.dart'; 4 | 5 | // ************************************************************************** 6 | // JaguarSerializerGenerator 7 | // ************************************************************************** 8 | 9 | abstract class _$PodcastsFilter1Serializer 10 | implements Serializer { 11 | Serializer __podcastsFieldsSerializer; 12 | Serializer get _podcastsFieldsSerializer => 13 | __podcastsFieldsSerializer ??= PodcastsFieldsSerializer(); 14 | @override 15 | Map toMap(PodcastsFilter1 model) { 16 | if (model == null) return null; 17 | Map ret = {}; 18 | setMapValueIfNotNull(ret, 'offset', model.offset); 19 | setMapValueIfNotNull(ret, 'limit', model.limit); 20 | setMapValueIfNotNull(ret, 'skip', model.skip); 21 | setMapValueIfNotNull(ret, 'order', 22 | codeNonNullIterable(model.order, (val) => val as String, [])); 23 | setMapValueIfNotNull( 24 | ret, 'fields', _podcastsFieldsSerializer.toMap(model.fields)); 25 | return ret; 26 | } 27 | 28 | @override 29 | PodcastsFilter1 fromMap(Map map) { 30 | if (map == null) return null; 31 | final obj = PodcastsFilter1( 32 | offset: map['offset'] as int ?? getJserDefault('offset'), 33 | limit: map['limit'] as int ?? getJserDefault('limit'), 34 | skip: map['skip'] as int ?? getJserDefault('skip'), 35 | order: codeNonNullIterable( 36 | map['order'] as Iterable, (val) => val as String, []) ?? 37 | getJserDefault('order'), 38 | fields: _podcastsFieldsSerializer.fromMap(map['fields'] as Map) ?? 39 | getJserDefault('fields')); 40 | return obj; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/widgets/home/list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:hear2learn/models/podcast.dart'; 4 | import 'package:hear2learn/widgets/common/horizontal_list_view.dart'; 5 | import 'package:hear2learn/widgets/common/with_fade_in_image.dart'; 6 | import 'package:hear2learn/widgets/podcast/index.dart'; 7 | 8 | class HomepageList extends StatelessWidget { 9 | final bool directToEpisodes; 10 | final List list; 11 | final Function onMoreClick; 12 | final String title; 13 | final Icon titleIcon; 14 | 15 | const HomepageList({ 16 | Key key, 17 | this.directToEpisodes = false, 18 | this.list, 19 | this.onMoreClick, 20 | this.title, 21 | this.titleIcon, 22 | }) : super(key: key); 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | final List tiles = list?.map((Podcast podcast) { 27 | final Widget image = WithFadeInImage( 28 | heroTag: '$title/${podcast.artwork600}', 29 | location: podcast.artwork600, 30 | ); 31 | 32 | return HorizontalListTile( 33 | image: image, 34 | onTap: () { 35 | Navigator.push( 36 | context, 37 | MaterialPageRoute( 38 | builder: (BuildContext context) => PodcastPage( 39 | directToEpisodes: directToEpisodes, 40 | image: image, 41 | podcast: podcast, 42 | ), 43 | settings: const RouteSettings(name: PodcastPage.routeName), 44 | ), 45 | ); 46 | }, 47 | title: podcast.name, 48 | ); 49 | })?.toList() ?? []; 50 | 51 | return tiles.isNotEmpty 52 | ? HorizontalListViewCard( 53 | children: tiles, 54 | onMoreClick: onMoreClick, 55 | title: title, 56 | titleIcon: titleIcon, 57 | ) 58 | : Container(height: 0.0, width: 0.0); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/widgets/podcast/home.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:flutter_html/flutter_html.dart'; 4 | import 'package:hear2learn/models/podcast.dart'; 5 | import 'package:hear2learn/widgets/common/tags.dart'; 6 | import 'package:hear2learn/widgets/podcast/options.dart'; 7 | 8 | class PodcastHome extends StatelessWidget { 9 | final Widget image; 10 | final Function onShare; 11 | final Function onSubscribe; 12 | final Function onUnsubscribe; 13 | final Podcast podcast; 14 | 15 | String get description => podcast.description; 16 | List get genres => podcast.genres; 17 | 18 | const PodcastHome({ 19 | Key key, 20 | this.image, 21 | this.podcast, 22 | this.onShare, 23 | this.onSubscribe, 24 | this.onUnsubscribe, 25 | }) : super(key: key); 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return ListView( 30 | children: [ 31 | ClipRRect( 32 | borderRadius: BorderRadius.circular(8.0), 33 | child: Container( 34 | child: image, 35 | height: MediaQuery.of(context).size.width - 64, 36 | width: MediaQuery.of(context).size.width - 32, 37 | ), 38 | ), 39 | Container( 40 | child: PodcastOptions( 41 | podcast: podcast, 42 | onShare: onShare, 43 | onSubscribe: onSubscribe, 44 | onUnsubscribe: onUnsubscribe, 45 | ), 46 | margin: const EdgeInsets.symmetric(vertical: 8.0), 47 | ), 48 | Container( 49 | child: description != null ? Html( 50 | data: description, 51 | defaultTextStyle: Theme.of(context).textTheme.body1, 52 | ) : null, 53 | margin: const EdgeInsets.symmetric(vertical: 8.0), 54 | ), 55 | Container( 56 | child: Tags( 57 | genres: genres, 58 | ), 59 | margin: const EdgeInsets.symmetric(vertical: 8.0), 60 | ), 61 | ], 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/git_push.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ 3 | # 4 | # Usage example: /bin/sh ./git_push.sh wing328 openapi-pestore-perl "minor update" "gitlab.com" 5 | 6 | git_user_id=$1 7 | git_repo_id=$2 8 | release_note=$3 9 | git_host=$4 10 | 11 | if [ "$git_host" = "" ]; then 12 | git_host="github.com" 13 | echo "[INFO] No command line input provided. Set \$git_host to $git_host" 14 | fi 15 | 16 | if [ "$git_user_id" = "" ]; then 17 | git_user_id="GIT_USER_ID" 18 | echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" 19 | fi 20 | 21 | if [ "$git_repo_id" = "" ]; then 22 | git_repo_id="GIT_REPO_ID" 23 | echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" 24 | fi 25 | 26 | if [ "$release_note" = "" ]; then 27 | release_note="Minor update" 28 | echo "[INFO] No command line input provided. Set \$release_note to $release_note" 29 | fi 30 | 31 | # Initialize the local directory as a Git repository 32 | git init 33 | 34 | # Adds the files in the local repository and stages them for commit. 35 | git add . 36 | 37 | # Commits the tracked changes and prepares them to be pushed to a remote repository. 38 | git commit -m "$release_note" 39 | 40 | # Sets the new remote 41 | git_remote=`git remote` 42 | if [ "$git_remote" = "" ]; then # git remote not defined 43 | 44 | if [ "$GIT_TOKEN" = "" ]; then 45 | echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." 46 | git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git 47 | else 48 | git remote add origin https://${git_user_id}:${GIT_TOKEN}@${git_host}/${git_user_id}/${git_repo_id}.git 49 | fi 50 | 51 | fi 52 | 53 | git pull origin master 54 | 55 | # Pushes (Forces) the changes in the local repository up to the remote repository 56 | echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git" 57 | git push origin master 2>&1 | grep -v 'To https' 58 | 59 | -------------------------------------------------------------------------------- /lib/widgets/common/app_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:dart_chromecast/casting/cast.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_redux/flutter_redux.dart'; 4 | import 'package:hear2learn/app.dart'; 5 | import 'package:hear2learn/redux/actions.dart'; 6 | import 'package:hear2learn/redux/selectors.dart'; 7 | import 'package:hear2learn/redux/state.dart'; 8 | import 'package:hear2learn/widgets/common/chromecast_device_picker.dart'; 9 | 10 | class EnhancedAppBar extends StatelessWidget with PreferredSizeWidget { 11 | final App app = App(); 12 | final PreferredSizeWidget bottom; 13 | final Widget title; 14 | 15 | EnhancedAppBar({ 16 | Key key, 17 | this.bottom, 18 | this.title, 19 | }): super(key: key); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return AppBar( 24 | title: title, 25 | bottom: bottom, 26 | actions: [ 27 | StoreConnector ( 28 | converter: castSelector, 29 | builder: (BuildContext context, CastSender cast) { 30 | bool connected = cast != null; 31 | return IconButton( 32 | icon: Icon(connected ? Icons.cast_connected : Icons.cast), 33 | onPressed: () async { 34 | if(connected) { 35 | await app.store.dispatch(disconnectCast()); 36 | return; 37 | } 38 | 39 | Navigator.of(context).push( 40 | MaterialPageRoute( 41 | builder: (BuildContext context) => DevicePicker( 42 | onDevicePicked: (CastDevice cast) async { 43 | await app.store.dispatch(connectToCast(cast)); 44 | }, 45 | ), 46 | fullscreenDialog: true, 47 | ) 48 | ); 49 | }, 50 | ); 51 | }, 52 | ), 53 | ], 54 | ); 55 | } 56 | 57 | @override 58 | Size get preferredSize => Size.fromHeight(kToolbarHeight + (bottom?.preferredSize?.height ?? 0)); 59 | } 60 | -------------------------------------------------------------------------------- /lib/widgets/podcast/options.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:flutter_redux/flutter_redux.dart'; 4 | import 'package:hear2learn/models/podcast.dart'; 5 | import 'package:hear2learn/redux/selectors.dart'; 6 | import 'package:hear2learn/redux/state.dart'; 7 | import 'package:hear2learn/widgets/common/outline_icon_button.dart'; 8 | 9 | class PodcastOptions extends StatelessWidget { 10 | final Function onShare; 11 | final Function onSubscribe; 12 | final Function onUnsubscribe; 13 | final Podcast podcast; 14 | 15 | const PodcastOptions({ 16 | Key key, 17 | this.onShare, 18 | this.onSubscribe, 19 | this.onUnsubscribe, 20 | this.podcast, 21 | }) : super(key: key); 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return Row( 26 | children: [ 27 | Container( 28 | child: buildSubscriptionButton(), 29 | margin: const EdgeInsets.only(right: 16.0), 30 | ), 31 | buildShareOptions(), 32 | ], 33 | ); 34 | } 35 | 36 | Widget buildSubscriptionButton() { 37 | return StoreConnector( 38 | converter: getSubscriptionSelector(podcast), 39 | builder: (BuildContext context, Podcast subscription) { 40 | final bool isSubscribed = subscription != null; 41 | 42 | return !isSubscribed 43 | ? buildSubscribeOption() 44 | : buildUnsubscribeOption() 45 | ; 46 | }, 47 | ); 48 | } 49 | 50 | Widget buildSubscribeOption() { 51 | return OutlineButton.icon( 52 | icon: const Icon(Icons.add), 53 | label: const Text('Subscribe'), 54 | onPressed: onSubscribe, 55 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(32.0)), 56 | ); 57 | } 58 | 59 | Widget buildUnsubscribeOption() { 60 | return RaisedButton.icon( 61 | icon: const Icon(Icons.done), 62 | label: const Text('Subscribed'), 63 | onPressed: onUnsubscribe, 64 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(32.0)), 65 | ); 66 | } 67 | 68 | Widget buildShareOptions() { 69 | return OutlineIconButton( 70 | icon: Icons.share, 71 | onPressed: onShare, 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # A functional, but probably terrible, example of GitLab CI for Flutter 2 | image: cirrusci/flutter 3 | 4 | stages: 5 | - test 6 | # - build 7 | 8 | lint: 9 | except: 10 | - schedules 11 | stage: test 12 | before_script: 13 | - flutter channel dev 14 | - flutter upgrade 15 | script: 16 | - flutter analyze 17 | when: manual 18 | 19 | tests: 20 | except: 21 | - schedules 22 | stage: test 23 | before_script: 24 | - flutter channel dev 25 | - flutter upgrade 26 | script: 27 | - flutter test 28 | when: manual 29 | 30 | itunes-data: 31 | only: 32 | refs: 33 | - schedules 34 | variables: 35 | - $WHICH_SCHEDULE == "itunes-data" 36 | image: 37 | name: node:current 38 | before_script: 39 | - curl -XGET https://gist.githubusercontent.com/eemp/b3e810a3cd691834ed65c994cbd0da6a/raw/475e1278f93e5006fc12a3b7a6940ce08c1cd14d/transcrypt > transcrypt 40 | - yes | bash ./transcrypt -c aes-256-cbc -p $TRANSCRYPT_PASSWORD || true 41 | - npm install --prefix packages/podcasts-indexer 42 | script: 43 | - BATCH_SIZE=$BATCH_SIZE OFFSET=$(( 10#$(TZ=America/New_York date +'%H')/$PERIOD * $BATCH_SIZE )) npm run --prefix packages/podcasts-indexer build:data:mongodb 44 | 45 | feeds-data: 46 | only: 47 | refs: 48 | - schedules 49 | variables: 50 | - $WHICH_SCHEDULE == "feeds-data" 51 | image: 52 | name: node:current 53 | before_script: 54 | - curl -XGET https://gist.githubusercontent.com/eemp/b3e810a3cd691834ed65c994cbd0da6a/raw/475e1278f93e5006fc12a3b7a6940ce08c1cd14d/transcrypt > transcrypt 55 | - yes | bash ./transcrypt -c aes-256-cbc -p $TRANSCRYPT_PASSWORD || true 56 | - npm install --prefix packages/podcasts-indexer 57 | script: 58 | - BATCH_SIZE=$BATCH_SIZE OFFSET=$(( 10#$(TZ=America/New_York date +'%H')/$PERIOD * $BATCH_SIZE )) npm run --prefix packages/podcasts-indexer build:feeds-data:mongodb 59 | 60 | ping-api: 61 | only: 62 | refs: 63 | - schedules 64 | variables: 65 | - $WHICH_SCHEDULE == "ping-api" 66 | image: 67 | name: byrnedo/alpine-curl 68 | entrypoint: ["/bin/sh", "-c"] 69 | script: 70 | curl -XGET "$API_BASE_URL/ping" 71 | 72 | #build: 73 | # stage: build 74 | # script: 75 | # - flutter build apk 76 | -------------------------------------------------------------------------------- /packages/podcasts-indexer/common/models/podcast.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "podcast", 3 | "settings": { 4 | "mysql": { 5 | "table": "podcasts" 6 | }, 7 | "sqlite3": { 8 | "table": "podcasts" 9 | } 10 | }, 11 | "properties": { 12 | "artist": { 13 | "type": "object", 14 | "properties": { 15 | "id": { 16 | "type": "string" 17 | }, 18 | "name": { 19 | "type": "string" 20 | } 21 | } 22 | }, 23 | "artwork100": { 24 | "type": "string" 25 | }, 26 | "artwork30": { 27 | "type": "string" 28 | }, 29 | "artwork600": { 30 | "type": "string" 31 | }, 32 | "artwork60": { 33 | "type": "string" 34 | }, 35 | "artworkOrig": { 36 | "type": "string", 37 | "dataType": "TEXT" 38 | }, 39 | "description": { 40 | "type": "string", 41 | "dataType": "TEXT" 42 | }, 43 | "episodes": { 44 | "type": "object", 45 | "properties": { 46 | "count": { 47 | "type": "number" 48 | } 49 | } 50 | }, 51 | "feed": { 52 | "type": "string", 53 | "required": true 54 | }, 55 | "genres": { 56 | "type": "object", 57 | "properties": { 58 | "id": { 59 | "type": "string" 60 | }, 61 | "name": { 62 | "type": "string" 63 | } 64 | } 65 | }, 66 | "id": { 67 | "type": "string", 68 | "forceId": true, 69 | "id": true, 70 | "required": true 71 | }, 72 | "last_modified_timestamp": { 73 | "type": "date" 74 | }, 75 | "last_updated": { 76 | "type": "date" 77 | }, 78 | "name": { 79 | "type": "string" 80 | }, 81 | "popularity": { 82 | "type": "number" 83 | }, 84 | "primary_genre": { 85 | "type": "object", 86 | "properties": { 87 | "id": { 88 | "type": "string" 89 | }, 90 | "name": { 91 | "type": "string" 92 | } 93 | } 94 | }, 95 | "release_date": { 96 | "type": "date" 97 | }, 98 | "type": { 99 | "type": "string" 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/test/podcasts_controller_api_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:netcastsoss_data_api/api.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | 5 | /// tests for PodcastsControllerApi 6 | void main() { 7 | var instance = new PodcastsControllerApi(); 8 | 9 | group('tests for PodcastsControllerApi', () { 10 | //Future podcastsControllerCount({ Map where }) async 11 | test('test podcastsControllerCount', () async { 12 | // TODO 13 | }); 14 | 15 | //Future podcastsControllerCreate({ NewPodcasts newPodcasts }) async 16 | test('test podcastsControllerCreate', () async { 17 | // TODO 18 | }); 19 | 20 | //Future podcastsControllerDeleteById(String id) async 21 | test('test podcastsControllerDeleteById', () async { 22 | // TODO 23 | }); 24 | 25 | //Future> podcastsControllerFind({ PodcastsFilter filter }) async 26 | test('test podcastsControllerFind', () async { 27 | // TODO 28 | }); 29 | 30 | //Future podcastsControllerFindById(String id, { PodcastsFilter1 filter }) async 31 | test('test podcastsControllerFindById', () async { 32 | // TODO 33 | }); 34 | 35 | //Future> podcastsControllerFindPopularPodcasts({ PodcastsFilter filter }) async 36 | test('test podcastsControllerFindPopularPodcasts', () async { 37 | // TODO 38 | }); 39 | 40 | //Future podcastsControllerReplaceById(String id, { Podcasts podcasts }) async 41 | test('test podcastsControllerReplaceById', () async { 42 | // TODO 43 | }); 44 | 45 | //Future> podcastsControllerSearchPodcastsByText({ String q, num limit }) async 46 | test('test podcastsControllerSearchPodcastsByText', () async { 47 | // TODO 48 | }); 49 | 50 | //Future podcastsControllerUpdateAll({ Map where, PodcastsPartial podcastsPartial }) async 51 | test('test podcastsControllerUpdateAll', () async { 52 | // TODO 53 | }); 54 | 55 | //Future podcastsControllerUpdateById(String id, { PodcastsPartial podcastsPartial }) async 56 | test('test podcastsControllerUpdateById', () async { 57 | // TODO 58 | }); 59 | 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/lib/model/podcasts_filter.jser.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'podcasts_filter.dart'; 4 | 5 | // ************************************************************************** 6 | // JaguarSerializerGenerator 7 | // ************************************************************************** 8 | 9 | abstract class _$PodcastsFilterSerializer 10 | implements Serializer { 11 | Serializer __podcastsFieldsSerializer; 12 | Serializer get _podcastsFieldsSerializer => 13 | __podcastsFieldsSerializer ??= PodcastsFieldsSerializer(); 14 | @override 15 | Map toMap(PodcastsFilter model) { 16 | if (model == null) return null; 17 | Map ret = {}; 18 | setMapValueIfNotNull(ret, 'offset', model.offset); 19 | setMapValueIfNotNull(ret, 'limit', model.limit); 20 | setMapValueIfNotNull(ret, 'skip', model.skip); 21 | setMapValueIfNotNull(ret, 'order', 22 | codeNonNullIterable(model.order, (val) => val as String, [])); 23 | setMapValueIfNotNull( 24 | ret, 25 | 'where', 26 | codeNonNullMap(model.where, (val) => passProcessor.serialize(val), 27 | {})); 28 | setMapValueIfNotNull( 29 | ret, 'fields', _podcastsFieldsSerializer.toMap(model.fields)); 30 | return ret; 31 | } 32 | 33 | @override 34 | PodcastsFilter fromMap(Map map) { 35 | if (map == null) return null; 36 | final obj = PodcastsFilter( 37 | offset: map['offset'] as int ?? getJserDefault('offset'), 38 | limit: map['limit'] as int ?? getJserDefault('limit'), 39 | skip: map['skip'] as int ?? getJserDefault('skip'), 40 | order: codeNonNullIterable( 41 | map['order'] as Iterable, (val) => val as String, []) ?? 42 | getJserDefault('order'), 43 | where: codeNonNullMap( 44 | map['where'] as Map, 45 | (val) => passProcessor.deserialize(val) as Object, 46 | {}) ?? 47 | getJserDefault('where'), 48 | fields: _podcastsFieldsSerializer.fromMap(map['fields'] as Map) ?? 49 | getJserDefault('fields')); 50 | return obj; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/widgets/common/bottom_app_bar_player.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:flutter_redux/flutter_redux.dart'; 4 | import 'package:hear2learn/app.dart'; 5 | import 'package:hear2learn/models/episode.dart'; 6 | import 'package:hear2learn/redux/actions.dart'; 7 | import 'package:hear2learn/redux/selectors.dart'; 8 | import 'package:hear2learn/redux/state.dart'; 9 | import 'package:hear2learn/widgets/common/circular_progress_with_optional_action.dart'; 10 | import 'package:hear2learn/widgets/episode/index.dart'; 11 | 12 | class BottomAppBarPlayer extends StatelessWidget { 13 | final String mode; 14 | 15 | const BottomAppBarPlayer({ 16 | Key key, 17 | this.mode = 'default', 18 | }) : super(key: key); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | final App app = App(); 23 | 24 | return StoreConnector( 25 | converter: playingEpisodeSelector, 26 | builder: (BuildContext context, Episode episode) { 27 | return episode != null 28 | ? BottomAppBar( 29 | child: ListTile( 30 | leading: CircularProgressWithOptionalAction( 31 | icon: episode.isPlaying() 32 | ? const Icon(Icons.pause) 33 | : const Icon(Icons.play_arrow), 34 | onPressed: () { 35 | if(episode.isPlaying()) { 36 | app.store.dispatch(pauseEpisode(episode)); 37 | } 38 | else { 39 | app.store.dispatch(playEpisode(episode)); 40 | } 41 | }, 42 | progress: (episode.position?.inSeconds?.toDouble() ?? 0) 43 | / (episode.length?.inSeconds?.toDouble() ?? 1), 44 | ), 45 | onTap: () { 46 | Navigator.push( 47 | context, 48 | MaterialPageRoute( 49 | builder: (BuildContext context) => EpisodePage(episode: episode), 50 | settings: const RouteSettings(name: EpisodePage.routeName), 51 | ), 52 | ); 53 | }, 54 | subtitle: Text(episode.podcastTitle), 55 | title: Text(episode.title, overflow: TextOverflow.ellipsis), 56 | ), 57 | ) 58 | : Container(height: 0.0, width: 0.0) 59 | ; 60 | }, 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/services/connectors/elastic.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | //import 'package:flutter/foundation.dart'; 4 | 5 | import 'package:hear2learn/helpers/dash.dart' as dash; 6 | import 'package:http/http.dart' as http; 7 | 8 | class ElasticsearchClient { 9 | static const String SEARCH_ROUTE = '_search'; 10 | 11 | final String host; 12 | final String index; 13 | 14 | const ElasticsearchClient({ 15 | this.host, 16 | this.index, 17 | }); 18 | 19 | Future search({ 20 | Map body, 21 | String index, 22 | String query, 23 | String type, 24 | }) async { 25 | index ??= this.index; 26 | final String path = getPath(index, type, SEARCH_ROUTE); 27 | final Uri uri = query != null 28 | ? Uri.https(host, path, { 'q': query }) 29 | : Uri.https(host, path); 30 | 31 | final dynamic response = await http.post(uri, 32 | body: json.encode(body), 33 | headers: { 'Content-Type': 'application/json' } 34 | ).timeout(const Duration(seconds: 5)); 35 | final String responseBody = response.body; 36 | 37 | return dash.compute(parseResponse, responseBody); 38 | } 39 | 40 | static String getPath(String index, String type, String route) { 41 | final List types = type is List 42 | ? type 43 | : [ type ]; 44 | return [ index, types.join(','), route ].join('/'); 45 | } 46 | } 47 | 48 | class ElasticsearchResponse { 49 | int total = 0; 50 | List hits = []; 51 | 52 | ElasticsearchResponse.fromJson(Map json) { 53 | if(json['hits'] != null) { 54 | final List rawHits = json['hits']['hits']; 55 | hits = rawHits.map((dynamic result) => Hit.fromJson(result)).toList(); 56 | total = json['hits']['total']; 57 | } 58 | } 59 | } 60 | 61 | class Hit { 62 | // looks like dart doesn't create getters 63 | // for underscore prefixed variables 64 | String id; 65 | double score; 66 | Map source; 67 | String type; 68 | 69 | Hit.fromJson(Map json) { 70 | id = json['_id']; 71 | score = json['_score']; 72 | source = json['_source']; 73 | type = json['_type']; 74 | } 75 | } 76 | 77 | ElasticsearchResponse parseResponse(String responseBody) { 78 | final Map parsedContent = json.decode(responseBody); 79 | return ElasticsearchResponse.fromJson(parsedContent); 80 | } 81 | -------------------------------------------------------------------------------- /lib/run_app.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | 5 | import 'package:flutter_redux/flutter_redux.dart'; 6 | import 'package:hear2learn/app.dart'; 7 | import 'package:hear2learn/helpers/reassemble.dart'; 8 | import 'package:hear2learn/helpers/dynamic_theme.dart'; 9 | import 'package:hear2learn/redux/actions.dart'; 10 | import 'package:hear2learn/redux/state.dart'; 11 | import 'package:hear2learn/themes.dart'; 12 | import 'package:hear2learn/widgets/home/index.dart'; 13 | import 'package:redux/redux.dart'; 14 | 15 | Future start({ List navigatorObservers }) async { 16 | final App app = App(); 17 | 18 | app.store.dispatch(updateConnectivity); 19 | app.store.dispatch(loadSettings); 20 | app.store.dispatch(updateSubscriptions); 21 | app.store.dispatch(updateDownloads); 22 | 23 | SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); 24 | 25 | navigatorObservers ??= []; 26 | runApp( 27 | AppWidget( 28 | navigatorObservers: navigatorObservers, 29 | store: app.store, 30 | title: app.packageInfo.appName, 31 | ) 32 | ); 33 | } 34 | 35 | class AppWidget extends StatelessWidget { 36 | final List navigatorObservers; 37 | final Store store; 38 | final String title; 39 | 40 | const AppWidget({ 41 | Key key, 42 | this.navigatorObservers, 43 | this.store, 44 | this.title 45 | }) : super(key: key); 46 | 47 | @override 48 | Widget build(BuildContext context) { 49 | final App app = App(); 50 | return DynamicTheme( 51 | defaultThemeName: ThemeProvider.DEFAULT_THEME, 52 | themeBuilder: ThemeProvider(), 53 | widgetBuilder: (BuildContext context, ThemeData theme) { 54 | return StoreProvider( 55 | store: store, 56 | child: ReassembleListener( 57 | onReassemble: () { 58 | // if you need something reloaded on hot reload, 59 | // this is the place to do it 60 | // print('Reload detected... if necessary, initAudioService'); 61 | // app.initAudioService(); 62 | }, 63 | child: MaterialApp( 64 | home: Home(), 65 | navigatorObservers: navigatorObservers, 66 | theme: theme, 67 | title: title, 68 | ), 69 | ), 70 | ); 71 | }, 72 | ); 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /packages/podcasts-indexer/scripts/data-migrator.js: -------------------------------------------------------------------------------- 1 | const Promise = require('bluebird'); 2 | const { DataSource } = require('loopback-datasource-juggler'); 3 | 4 | const datasources = require('../server/datasources.json'); 5 | const modelDefinition = require('../common/models/podcast.json'); 6 | const { name:modelName, properties:modelProperties, settings: modelSettings } = modelDefinition; 7 | 8 | const fromDSName = process.env.FROM_DS; 9 | const toDSName = process.env.TO_DS; 10 | const strategyName = process.env.STRATEGY; 11 | if(!fromDSName || !toDSName) { 12 | console.error('Usage: FROM_DS=sqlite TO_DS=mysql node data-migrator.js'); 13 | process.exit(-1); 14 | } 15 | 16 | const fromDS = new DataSource(fromDSName, datasources[fromDSName]); 17 | const origModel = fromDS.define(modelName, modelProperties, modelSettings); 18 | 19 | const toDS = new DataSource(toDSName, datasources[toDSName]); 20 | const targetModel = toDS.define(modelName, modelProperties, modelSettings); 21 | 22 | const STRATEGIES = { 23 | default: replaceOrCreateRecords, 24 | create: createRecords, 25 | upsert: replaceOrCreateRecords, 26 | }; 27 | const strategy = STRATEGIES[strategyName] || STRATEGIES.default; 28 | 29 | console.log(`Fetching podcasts records from ${fromDSName} datasource...`); 30 | origModel.find({limit: 9999}).then(results => { 31 | const records = results.map(result => result.toJSON()); 32 | console.log(`Found ${records.length} records to migrate...`); 33 | return toDS.autoupdate(modelName).then(result => 34 | strategy(records) 35 | ); 36 | }).then(() => { 37 | console.log('Finished pushing podcasts data.'); 38 | }).catch(err => { 39 | console.error('Unable to migrate podcasts data, encountered: ', err.message); 40 | process.exit(-1); 41 | }); 42 | 43 | function createRecords(records) { 44 | return targetModel.create(records).then(res => { 45 | console.log(`Finished batch creating ${res.length} records.`); 46 | process.exit(0); 47 | }).catch(err => { 48 | console.error('Unable to batch create all records due to following error:', err); 49 | process.exit(-1); 50 | }); 51 | } 52 | 53 | function replaceOrCreateRecords(records) { 54 | return Promise.mapSeries(records, record => { 55 | return targetModel.replaceOrCreate(record).then(() => { 56 | console.log(`Upserted ${record.title || record.name || record.id}`); 57 | }).catch(err => { 58 | console.error('Unable to upsert following record:', record); 59 | console.error('Encountered error:', err); 60 | }); 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /android/app/google-services.json: -------------------------------------------------------------------------------- 1 | U2FsdGVkX19K9uX16BuKZqcq0EX1Lb8cQavf9BYdzrb+A80wud64OV/muYSfBKIo 2 | b/iy3KCgofYpaL6ym2lKuyVOjJY38Hln/uhp/0BaD8u8d0pH+xBUx76M07Gma9rI 3 | m2/kJXA7BT1MiI6TiRkwE44n57lw7z7rkNjgzKlBaxuabCMEjuweSlHpP6dboTWP 4 | UPBmzeoRHSGhuKNxXxKVp2/OvdTjbQLHIV7jSuJwU1HOdVo5i6Dlrkx7PIoPC9E3 5 | jiaPRgE6JV96qpCgnS5iSBmYLg9hWG2iW7JTh/EVepAwgVC9EGhF2HD7DPikoPAq 6 | o1p5iLTVUKn72RhtanSTZJKiATFsAQtk0t+YbncxoBPGaTTjtuOYM0dBiPV9IYaF 7 | C2RVuLILGWFkjK1yQiwK4mzOqkGr3ecwdO89v/S5kdI2WreeORiHH6pDESjX25Ae 8 | tZauygoie45A3J+h6expRPW/ttxaDUkyqQyEjm33DV/6Fx9Fdd36klMFs7kLuidk 9 | zv25+6a9B+N6Avp59tYmuSUP0ztw+ClDxTNn+ffHU/gRfOPXMYfudV1oljcZ7aAX 10 | fEBebZEuAvh6vk2EKD+LqmF2vJfV7KXOnMaTapAc9aKAZ4sjS3AHaT7dHV/rsAYm 11 | luzOHnrJfeCyKQu0iwLvB6ks+Qd/o50OXhpqi4KTv35+Jbofp7UUAKyeZnu5n1E7 12 | AXpv8B+yc85o0SCYpPVm4Z5zJ8MyTKDK5JwMqEBk+3ya+vGktZd2RODf79olWhva 13 | dcckOhQo6d8DJd0KYerTqoi5tGE4tTgzFdIISo99K2ydGXc2FSPy2EgYc7TeoG+4 14 | DhOFRc8O0cWk8zVc2hAg0VAeKF9W7y//AT+cj8+Co1Ntv/f4pr9iXEs2SoNiwnJo 15 | KTPsyRd7hkWwBpDnfpfx8q5roX/JJPooxjtp81AZzkA+LvBQz95LD5J5Om30HeIT 16 | 2LqlVuL0NcgYPesBcjzpR6idBVbIJOn6l2bkgBWPAKRyA3WOUe4OqgNiqaEGPQeK 17 | cXY6b+2Eeew0sUTNZ13e92l9zCsKFWD1TeKZj5wbgjIvjyWgi5syF3SyvakDwRwk 18 | fRqeuRVyOmLq/jen1PHuxTI2LrjVKXsjPL0kDSvCrlDl94pI6OiEztQz32Lu+KYd 19 | ouaAONHUZVwjCG+mTpEswirTEhSdbX/b0FvOcEyCCqj571spXES6gvKErhXUFnE5 20 | jC1sAjlNu/tPP7ktcroWz1KUK0OIj4eIG6z5ySsgHHbISuiNmXamgRAadr/v0xEB 21 | WMhly/GRytNps97biHzZuEZtOWcl6y6ZIQUy7+t8fOw2eB9lcCNPQHz3SXEtV/qK 22 | RQLNy3BMohHkwdYuRZ/kWjBsk4cznIyvz4/bMZ3OszyPGI4opPIBgOrhZLUOm/gv 23 | HjclWG0s68FLN/sANvEpmFVbYy4eF2h8DPI4qH7jNaFwU3ufEZgK4gHSwkN3OyVH 24 | GYVGLUdUEvA0sBlPrUHXBnNROUlNy+vX8EhCKBigOagU5t/X8khA/7gToVxlPL7W 25 | e7gGVz+ETP+xKMewa/SRvt7ZzwhHEFMBeFO0F6LbPRP4sFxz7LWCFSjL7bJNDmZF 26 | rSJBagy08O0sV1Hdxy1FNMwVbr5QiKKGPmkY1TaI91zIAy6jojCrBTvLm8ptq8cw 27 | 1D4JBF+uYrm2Cp+uk8qBvJBNY+OZbMf57lYTaXMjrkkEh9gQC0FsBjXSS5LIgLtE 28 | 8UbLUUeposWveJ7vLy0N93XR3gF90D8CGvyKXYHO1Uw1LaM/bQb6XobvXtjwDt6I 29 | uY7jAAxnAFJmkYjolbZvLvwJf3lI4x/YsG1XdUyGbou0DxaTbfDgh7HIHsW7CV5i 30 | clEOXk7SGYc2v5utHn45zBqxcaaeiNV+hY1Ixze7WtGhX4jLYdW82rd+zWtYRzpj 31 | PaBab9MLuDqHgS5zKR3FxzFk44gHB7bjyJLy7mD+hMTsQz/K6kg8F7Jxqdk4OKFS 32 | xLgdGBosNhIFO95lk8ycue6ITFq5PmulfBU53D8Pxm+55Gr1QSttx2XqBcbbs1xa 33 | X3LPH0MCPl+omu2ttleYuybm1743k4b/qcWTY9aZ/6kfizAPOkiojaMmU29eg/wA 34 | FwNOdqvBgTB7T3VUVk9TN4rWIcZ1g6ZLE200jrsdbUSyLeojPYnnxvbtlAbP35ql 35 | COlHa4KBnZxztiNY1dY9OwhowcmNROU0HrhhSxmzC1Jo27FEHzv3uGJlcWe1SOPi 36 | aZs4l6/Qn3dB1ouCzXqup0RNlhE9ZBIori5tuRHNZe30tSheVzJBMYpEx1Y79Zyc 37 | RrXiMDz3JLSSTgC3sfIORk5uDa1YUk37LNOQYrs4znfQPMJ4jbGuZPx0IaYh7W5a 38 | rgtF/oHawPeNVWJiEKW3HA== 39 | -------------------------------------------------------------------------------- /lib/helpers/dash.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:math' as math; 3 | 4 | import 'package:easy_debounce/easy_debounce.dart'; 5 | import 'package:flutter/foundation.dart' as flutter show compute, ComputeCallback; 6 | 7 | bool get isInDebugMode { 8 | bool inDebugMode = false; 9 | assert(inDebugMode = true); 10 | return inDebugMode; 11 | } 12 | 13 | // for more information on this hack, see https://github.com/flutter/flutter/issues/24703#issuecomment-473335593 14 | Future compute(flutter.ComputeCallback callback, Q message) async { 15 | if (isInDebugMode) { 16 | return callback(message); 17 | } 18 | 19 | return await flutter.compute(callback, message); 20 | } 21 | 22 | dynamic apply(Function fn, [List args, Map namedArgs]) { 23 | return Function.apply( 24 | fn, 25 | args ?? [], 26 | symbolizeKeys(namedArgs ?? {}) 27 | ); 28 | } 29 | 30 | Map symbolizeKeys(Map map) { 31 | final Map result = {}; 32 | map.forEach((String k, dynamic v) { result[Symbol(k)] = v; }); 33 | return result; 34 | } 35 | 36 | T find(List list, Function test) { 37 | if(!(list is List)) { 38 | return null; 39 | } 40 | 41 | final int index = list.indexWhere(test); 42 | return index >= 0 43 | ? list[index] 44 | : null 45 | ; 46 | } 47 | 48 | 49 | double clamp(double a, double val, double b) { 50 | return math.max(a, math.min(val, b)); 51 | } 52 | 53 | bool isEmpty(String value) { 54 | return value == null || value.isEmpty; 55 | } 56 | 57 | bool isNotEmpty(String value) { 58 | return value != null && value.isNotEmpty; 59 | } 60 | 61 | // TODO: no return values 62 | Function debounce(Function fn, Duration delay, { String debounceId }) { 63 | debounceId = debounceId ?? StackTrace.current.toString().split("\n")[1]; 64 | 65 | return ([List args]) { 66 | EasyDebounce.debounce(debounceId, delay, () => apply(fn, args)); 67 | }; 68 | } 69 | 70 | Function throttle(Function fn, Duration delay) { 71 | Timer timer; 72 | DateTime lastExec = DateTime.now(); 73 | 74 | void exec(List args) { 75 | lastExec = DateTime.now(); 76 | apply(fn, args); 77 | } 78 | 79 | return ([List args]) { 80 | final Duration elapsed = DateTime.now().difference(lastExec); 81 | if(elapsed.compareTo(delay) >= 0) { 82 | exec(args); 83 | } 84 | 85 | if(timer != null) { 86 | timer.cancel(); 87 | } 88 | timer = Timer(delay, () => exec(args)); 89 | }; 90 | } 91 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | throw new GradleException("flutter.versionCode not found in local.properties"); 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | throw new GradleException("flutter.versionName not found in local.properties"); 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | def keystoreProperties = new Properties() 29 | def keystorePropertiesFile = rootProject.file('key.properties') 30 | if (keystorePropertiesFile.exists()) { 31 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 32 | } 33 | 34 | android { 35 | compileSdkVersion 28 36 | 37 | sourceSets { 38 | main.java.srcDirs += 'src/main/kotlin' 39 | } 40 | 41 | lintOptions { 42 | disable 'InvalidPackage' 43 | } 44 | 45 | defaultConfig { 46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 47 | applicationId "io.eemp.netcastsOSS.dev" 48 | minSdkVersion 16 49 | targetSdkVersion 28 50 | versionCode flutterVersionCode.toInteger() 51 | versionName flutterVersionName 52 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 53 | } 54 | 55 | buildTypes { 56 | release { 57 | signingConfig signingConfigs.debug 58 | shrinkResources false 59 | } 60 | } 61 | } 62 | 63 | flutter { 64 | source '../..' 65 | } 66 | 67 | dependencies { 68 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 69 | testImplementation 'junit:junit:4.12' 70 | androidTestImplementation 'androidx.test:runner:1.1.1' 71 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 72 | } 73 | 74 | apply plugin: 'com.google.gms.google-services' 75 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | RUN apt-get update 4 | 5 | ENV DEBIAN_FRONTEND=noninteractive 6 | 7 | # Install some dependencies 8 | RUN dpkg --add-architecture i386 && apt-get update \ 9 | && apt-get install -y --force-yes curl expect git wget unzip xz-utils \ 10 | libc6-i386 lib32stdc++6 lib32gcc1 lib32ncurses5 lib32z1 11 | 12 | # Install java 13 | RUN apt-get install -y openjdk-8-jdk-headless 14 | 15 | # Download and install Gradle 16 | #RUN mkdir -p /opt/gradle && \ 17 | #cd /opt/gradle && \ 18 | #curl -L https://services.gradle.org/distributions/gradle-6.0-bin.zip -o gradle-6.0-bin.zip && \ 19 | #unzip gradle-6.0-bin.zip && \ 20 | #rm gradle-6.0-bin.zip 21 | 22 | #ENV GRADLE_HOME /opt/gradle/gradle-6.0 23 | 24 | # Install the Android SDK 25 | RUN cd /opt && wget --output-document=android-sdk.zip --quiet \ 26 | https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip \ 27 | && unzip android-sdk.zip -d /opt/android-sdk && rm -f android-sdk.zip 28 | 29 | # Install Flutter 30 | RUN cd /opt && wget --output-document=flutter-sdk.tar.xz --quiet \ 31 | https://storage.googleapis.com/flutter_infra/releases/stable/linux/flutter_linux_v1.12.13+hotfix.8-stable.tar.xz \ 32 | && tar xf flutter-sdk.tar.xz \ 33 | && rm -f flutter-sdk-tar.xz 34 | 35 | # Setup environment 36 | ENV FLUTTER_HOME /opt/flutter 37 | ENV ANDROID_SDK_ROOT /opt/android-sdk 38 | ENV ANDROID_HOME /opt/android-sdk 39 | #ENV PATH ${GRADLE_HOME}/bin:${ANDROID_HOME}/tools/bin:${ANDROID_HOME}/platform-tools:${FLUTTER_HOME}/bin:${PATH} 40 | ENV PATH ${PATH}:${ANDROID_HOME}/tools/bin:${ANDROID_HOME}/platform-tools:${FLUTTER_HOME}/bin 41 | 42 | # Install SDK elements. This might change depending on what your app needs 43 | # I'm installing the most basic ones. You should modify this to install the ones 44 | # you need. You can get a list of available elements by getting a shell to the 45 | # container and using `sdkmanager --list` 46 | RUN echo yes | sdkmanager "platform-tools" "platforms;android-28" "build-tools;28.0.3" 47 | 48 | # Perform an artifact precache so that no extra assets need to be downloaded on demand. 49 | RUN flutter precache 50 | 51 | # Accept licenses. 52 | RUN yes "y" | flutter doctor --android-licenses 53 | 54 | # Disable analytics and crash reporting on the builder. 55 | RUN flutter config --no-analytics 56 | 57 | # Perform a doctor run. 58 | RUN flutter doctor -v 59 | 60 | # Perform a flutter upgrade 61 | RUN flutter upgrade 62 | 63 | # Go to workspace 64 | RUN mkdir -p /opt/workspace/packages 65 | WORKDIR /opt/workspace 66 | 67 | COPY pubspec* ./ 68 | RUN flutter pub get 69 | -------------------------------------------------------------------------------- /lib/themes.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ThemeProvider { 4 | static const String BLUES_THEME = 'Blues'; 5 | static final ThemeData bluesThemeData = ThemeData( 6 | accentColor: const Color(0xFF005B96), 7 | primaryColor: const Color(0xFF00ACC1), 8 | brightness: Brightness.light, 9 | fontFamily: 'OpenSans', 10 | ); 11 | 12 | static const String DARK_THEME = 'Dark'; 13 | static final ThemeData darkThemeData = ThemeData( 14 | brightness: Brightness.dark, 15 | fontFamily: 'OpenSans', 16 | ); 17 | 18 | static const String LIGHT_THEME = 'Light'; 19 | static final ThemeData lightThemeData = ThemeData( 20 | brightness: Brightness.light, 21 | fontFamily: 'OpenSans', 22 | ); 23 | 24 | static const String MOONLIGHT_BYTES_THEME = 'Moonlight Bytes 6'; 25 | static final ThemeData moonlightBytesThemeData = ThemeData( 26 | accentColor: const Color(0xFFFF8870), 27 | brightness: Brightness.dark, 28 | buttonTheme: ButtonThemeData( 29 | buttonColor: const Color(0xFFF2CC63), 30 | highlightColor: const Color(0xFFFF8870), 31 | textTheme: ButtonTextTheme.primary, 32 | ), 33 | canvasColor: const Color(0xFF272822), 34 | fontFamily: 'OpenSans', 35 | primaryColor: const Color(0xFF0D99A6), 36 | toggleableActiveColor: const Color(0xFFFF8870), 37 | ); 38 | 39 | static const String MONOKAI_THEME = 'Monokai'; 40 | static final ThemeData monokaiThemeData = ThemeData( 41 | accentColor: const Color(0xFF66D9EF), 42 | brightness: Brightness.dark, 43 | canvasColor: const Color(0xFF272822), 44 | cardColor: const Color(0xFF272822), 45 | cursorColor: const Color(0xFF66D9EF), 46 | errorColor: const Color(0xFF90274A), 47 | fontFamily: 'OpenSans', 48 | highlightColor: const Color(0xFFAE81FF), 49 | hintColor: const Color(0xFFAE81FF), 50 | indicatorColor: const Color(0xFFAE81FF), 51 | primaryColor: const Color(0xFFF92672), 52 | primaryColorBrightness: Brightness.dark, 53 | toggleableActiveColor: const Color(0xFF065CBE), 54 | ); 55 | 56 | static final Map themes = {}; 57 | 58 | static const List THEME_LIST = [ 59 | BLUES_THEME, 60 | DARK_THEME, 61 | LIGHT_THEME, 62 | MONOKAI_THEME, 63 | MOONLIGHT_BYTES_THEME, 64 | ]; 65 | 66 | static const String DEFAULT_THEME = DARK_THEME; 67 | 68 | ThemeData call(String themeName) { 69 | themes[BLUES_THEME] = bluesThemeData; 70 | themes[DARK_THEME] = darkThemeData; 71 | themes[LIGHT_THEME] = lightThemeData; 72 | themes[MONOKAI_THEME] = monokaiThemeData; 73 | themes[MOONLIGHT_BYTES_THEME] = moonlightBytesThemeData; 74 | return themes[themeName]; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/helpers/podcast.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: always_specify_types 2 | import 'dart:async'; 3 | 4 | import 'package:hear2learn/app.dart'; 5 | import 'package:hear2learn/services/connectors/elastic.dart'; 6 | import 'package:hear2learn/models/podcast.dart'; 7 | import 'package:hear2learn/models/podcast_subscription.dart'; 8 | import 'package:netcastsoss_data_api/api.dart'; 9 | import 'package:netcastsoss_data_api/model/podcasts_filter.dart'; 10 | 11 | const String PODCAST_TYPE = 'podcast'; 12 | 13 | var jaguarApiGen = NetcastsossDataApi(); 14 | 15 | Future> getSubscriptions() { 16 | final App app = App(); 17 | final PodcastSubscriptionBean subscriptionModel = app.models['podcast_subscription']; 18 | 19 | return subscriptionModel.findWhere(subscriptionModel.isSubscribed.eq(true)).then((response) { 20 | return Future.wait(response.map((subscription) => Future.value(subscription.getPodcastFromDetails()))); 21 | }); 22 | } 23 | 24 | Future> fetchPopularPodcastsByGenre() async { 25 | var api_instance = jaguarApiGen.getPodcastsControllerApi(); 26 | var result = await api_instance.podcastsControllerFindPopularPodcasts(null); 27 | return result 28 | .map((podcastsByGenre) => ({ 29 | 'genre': podcastsByGenre.genre, 30 | 'items': remotePodcastsToPodcasts(podcastsByGenre.items), 31 | })) 32 | .toList(); 33 | } 34 | 35 | Future> searchPodcastsByTextQuery(String textQuery, { int pageSize = 10, int page = 0 }) async { 36 | var api_instance = jaguarApiGen.getPodcastsControllerApi(); 37 | var result = await api_instance.podcastsControllerSearchPodcastsByText(textQuery, PodcastsFilter( 38 | skip: page * pageSize, 39 | limit: pageSize, 40 | )); 41 | return remotePodcastsToPodcasts(result); 42 | } 43 | 44 | List remotePodcastsToPodcasts(remotePodcasts) { 45 | return remotePodcasts.map((remotePodcast) => 46 | Podcast.fromRemote(remotePodcast) 47 | ).toList(); 48 | } 49 | 50 | Future subscribeToPodcast(Podcast podcast) async { 51 | final App app = App(); 52 | final PodcastSubscriptionBean subscriptionModel = app.models['podcast_subscription']; 53 | final PodcastSubscription newSubscription = PodcastSubscription( 54 | created: DateTime.now(), 55 | details: podcast.toJson(), 56 | isSubscribed: true, 57 | podcastId: podcast.id, 58 | podcastUrl: podcast.feed, 59 | ); 60 | await subscriptionModel.insert(newSubscription); 61 | } 62 | 63 | Future unsubscribeFromPodcast(Podcast podcast) async { 64 | final App app = App(); 65 | final PodcastSubscriptionBean subscriptionModel = app.models['podcast_subscription']; 66 | await subscriptionModel.removeWhere(subscriptionModel.podcastUrl.eq(podcast.feed)); 67 | } 68 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /lib/widgets/episode/home.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hear2learn/widgets/common/episode_tile.dart'; 3 | import 'package:hear2learn/widgets/episode/options.dart'; 4 | import 'package:hear2learn/widgets/episode/player.dart'; 5 | import 'package:hear2learn/models/episode.dart'; 6 | 7 | class EpisodeHome extends StatelessWidget { 8 | final Episode episode; 9 | final Function onDelete; 10 | final Function onDownload; 11 | final Function onFavorite; 12 | final Function onFinish; 13 | final Function onPause; 14 | final Function onPlay; 15 | final Function onResume; 16 | final Function onShare; 17 | final Function onUnfavorite; 18 | final Function onUnfinish; 19 | 20 | const EpisodeHome({ 21 | Key key, 22 | this.episode, 23 | this.onDelete, 24 | this.onDownload, 25 | this.onFavorite, 26 | this.onFinish, 27 | this.onPause, 28 | this.onPlay, 29 | this.onResume, 30 | this.onShare, 31 | this.onUnfavorite, 32 | this.onUnfinish, 33 | }) : super(key: key); 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | return Column( 38 | children: [ 39 | Column( 40 | children: [ 41 | Container( 42 | child: Align( 43 | alignment: Alignment.centerLeft, 44 | child: Container( 45 | child: Text(episode.podcastTitle, style: Theme.of(context).textTheme.title, textAlign: TextAlign.left), 46 | ), 47 | ), 48 | margin: const EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0), 49 | ), 50 | EpisodeTile( 51 | subtitle: episode.getMetaLine(), 52 | title: episode.title, 53 | ), 54 | EpisodeOptions( 55 | episode: episode, 56 | onDelete: onDelete, 57 | onDownload: onDownload, 58 | onFavorite: onFavorite, 59 | onFinish: onFinish, 60 | onShare: onShare, 61 | onUnfavorite: onUnfavorite, 62 | onUnfinish: onUnfinish, 63 | ), 64 | ], 65 | ), 66 | Container( 67 | child: Divider( 68 | color: Theme.of(context).dividerColor, 69 | height: 8.0, 70 | ), 71 | padding: const EdgeInsets.all(16.0), 72 | ), 73 | Container( 74 | child: EpisodePlayer( 75 | episode: episode, 76 | onPause: onPause, 77 | onPlay: onPlay, 78 | onResume: onResume, 79 | ), 80 | padding: const EdgeInsets.only(bottom: 16.0), 81 | ), 82 | ], 83 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 84 | ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/test/new_podcasts_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:netcastsoss_data_api/api.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | // tests for NewPodcasts 5 | void main() { 6 | var instance = new NewPodcasts(); 7 | 8 | group('test NewPodcasts', () { 9 | // String artist (default value: null) 10 | test('to test the property `artist`', () async { 11 | // TODO 12 | }); 13 | 14 | // String artwork100 (default value: null) 15 | test('to test the property `artwork100`', () async { 16 | // TODO 17 | }); 18 | 19 | // String artwork30 (default value: null) 20 | test('to test the property `artwork30`', () async { 21 | // TODO 22 | }); 23 | 24 | // String artwork600 (default value: null) 25 | test('to test the property `artwork600`', () async { 26 | // TODO 27 | }); 28 | 29 | // String artwork60 (default value: null) 30 | test('to test the property `artwork60`', () async { 31 | // TODO 32 | }); 33 | 34 | // String artworkOrig (default value: null) 35 | test('to test the property `artworkOrig`', () async { 36 | // TODO 37 | }); 38 | 39 | // String description (default value: null) 40 | test('to test the property `description`', () async { 41 | // TODO 42 | }); 43 | 44 | // String episodes (default value: null) 45 | test('to test the property `episodes`', () async { 46 | // TODO 47 | }); 48 | 49 | // String feed (default value: null) 50 | test('to test the property `feed`', () async { 51 | // TODO 52 | }); 53 | 54 | // String genres (default value: null) 55 | test('to test the property `genres`', () async { 56 | // TODO 57 | }); 58 | 59 | // DateTime lastModifiedTimestamp (default value: null) 60 | test('to test the property `lastModifiedTimestamp`', () async { 61 | // TODO 62 | }); 63 | 64 | // DateTime lastUpdated (default value: null) 65 | test('to test the property `lastUpdated`', () async { 66 | // TODO 67 | }); 68 | 69 | // String name (default value: null) 70 | test('to test the property `name`', () async { 71 | // TODO 72 | }); 73 | 74 | // num popularity (default value: null) 75 | test('to test the property `popularity`', () async { 76 | // TODO 77 | }); 78 | 79 | // String primaryGenre (default value: null) 80 | test('to test the property `primaryGenre`', () async { 81 | // TODO 82 | }); 83 | 84 | // DateTime releaseDate (default value: null) 85 | test('to test the property `releaseDate`', () async { 86 | // TODO 87 | }); 88 | 89 | // String type (default value: null) 90 | test('to test the property `type`', () async { 91 | // TODO 92 | }); 93 | 94 | 95 | }); 96 | 97 | } 98 | -------------------------------------------------------------------------------- /lib/widgets/common/toggling_widget_pair.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:hear2learn/helpers/dash.dart' as dash; 4 | 5 | enum TogglingWidgetPairValue { initial, loading, active } 6 | 7 | class TogglingWidgetPair extends StatefulWidget { 8 | final TogglingWidgetPairController controller; 9 | 10 | final Widget activeWidget; 11 | final dynamic loadingWidget; 12 | final Widget initialWidget; 13 | 14 | const TogglingWidgetPair({ 15 | Key key, 16 | this.activeWidget, 17 | this.controller, 18 | this.initialWidget, 19 | this.loadingWidget, 20 | }) : super(key: key); 21 | 22 | @override 23 | TogglingWidgetPairState createState() => TogglingWidgetPairState(); 24 | } 25 | 26 | class TogglingWidgetPairController { 27 | Function listener; 28 | TogglingWidgetPairValue value; 29 | double progressValue; 30 | 31 | TogglingWidgetPairController({ 32 | this.progressValue, 33 | this.value, 34 | }); 35 | 36 | void setListener(Function newListener) { 37 | listener = newListener; 38 | } 39 | 40 | TogglingWidgetPairController setValues(TogglingWidgetPairValue newValue, {double updatedProgressValue}) { 41 | value = newValue; 42 | progressValue = updatedProgressValue; 43 | return this; 44 | } 45 | 46 | void setInitialValue() { 47 | value = TogglingWidgetPairValue.initial; 48 | listener(value); 49 | } 50 | 51 | void setLoadingValue() { 52 | value = TogglingWidgetPairValue.loading; 53 | listener(value); 54 | } 55 | 56 | void setActiveValue() { 57 | value = TogglingWidgetPairValue.active; 58 | listener(value); 59 | } 60 | 61 | void setProgressValue(double updatedProgressValue) { 62 | progressValue = updatedProgressValue; 63 | listener(value, progressValue: progressValue); 64 | } 65 | } 66 | 67 | class TogglingWidgetPairState extends State { 68 | TogglingWidgetPairController get controller => widget.controller; 69 | 70 | @override 71 | Widget build(BuildContext context) { 72 | final TogglingWidgetPairValue currValue = controller.value ?? TogglingWidgetPairValue.initial; 73 | controller.setListener((TogglingWidgetPairValue nextValue, {double progressValue}) { 74 | setState(() { 75 | controller.setValues(nextValue, updatedProgressValue: progressValue); 76 | }); 77 | }); 78 | 79 | final Function loadingWidget = widget.loadingWidget is Function 80 | ? widget.loadingWidget 81 | : ({ double progressValue }) => widget.loadingWidget; 82 | 83 | return currValue == TogglingWidgetPairValue.loading 84 | ? dash.apply(loadingWidget, [], { 'progressValue': controller.progressValue }) 85 | : currValue == TogglingWidgetPairValue.active 86 | ? widget.activeWidget 87 | : widget.initialWidget; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/helpers/dynamic_theme.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:shared_preferences/shared_preferences.dart'; 5 | 6 | typedef Widget ThemedWidgetBuilder(BuildContext context, ThemeData data); 7 | 8 | typedef ThemeData ThemeDataBuilder(String themeName); 9 | 10 | class DynamicTheme extends StatefulWidget { 11 | final ThemeData defaultTheme; 12 | final String defaultThemeName; 13 | final ThemeDataBuilder themeBuilder; 14 | final ThemedWidgetBuilder widgetBuilder; 15 | 16 | const DynamicTheme({ 17 | Key key, 18 | this.defaultTheme, 19 | this.defaultThemeName, 20 | this.themeBuilder, 21 | this.widgetBuilder, 22 | }) : super(key: key); 23 | 24 | @override 25 | DynamicThemeState createState() => new DynamicThemeState(); 26 | 27 | static DynamicThemeState of(BuildContext context) { 28 | return context.ancestorStateOfType(const TypeMatcher()); 29 | } 30 | } 31 | 32 | class DynamicThemeState extends State { 33 | static const String sharedPreferencesKey = "theme"; 34 | 35 | ThemeData theme; 36 | String themeName; 37 | 38 | get defaultTheme => widget.defaultTheme; 39 | get defaultThemeName => widget.defaultThemeName; 40 | get themeBuilder => widget.themeBuilder; 41 | get widgetBuilder => widget.widgetBuilder; 42 | 43 | @override 44 | void initState() { 45 | super.initState(); 46 | 47 | themeName = defaultThemeName; 48 | theme = themeBuilder(defaultThemeName); 49 | 50 | loadThemePreference().then((savedThemeName) { 51 | setState(() { 52 | if(savedThemeName != null) { 53 | themeName = savedThemeName; 54 | theme = themeBuilder(themeName); 55 | } 56 | }); 57 | }); 58 | } 59 | 60 | @override 61 | void didChangeDependencies() { 62 | super.didChangeDependencies(); 63 | theme = widget.themeBuilder(themeName); 64 | } 65 | 66 | @override 67 | void didUpdateWidget(DynamicTheme oldWidget) { 68 | super.didUpdateWidget(oldWidget); 69 | theme = widget.themeBuilder(themeName); 70 | } 71 | 72 | @override 73 | Widget build(BuildContext context) { 74 | return widgetBuilder(context, theme); 75 | } 76 | 77 | void setTheme(String themeName) async { 78 | setState(() { 79 | this.theme = widget.themeBuilder(themeName); 80 | this.themeName = themeName; 81 | }); 82 | SharedPreferences prefs = await SharedPreferences.getInstance(); 83 | await prefs.setString(sharedPreferencesKey, themeName); 84 | } 85 | 86 | void setThemeData(ThemeData theme) { 87 | setState(() { 88 | this.theme = theme; 89 | }); 90 | } 91 | 92 | Future loadThemePreference() async { 93 | SharedPreferences prefs = await SharedPreferences.getInstance(); 94 | return (prefs.getString(sharedPreferencesKey)); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/services/api/itunes_search.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | //import 'package:flutter/foundation.dart'; 4 | 5 | import 'package:hear2learn/helpers/dash.dart' as dash; 6 | import 'package:http/http.dart' as http; 7 | 8 | class ITunesSearchAPI { 9 | static const String AUTHORITY = 'itunes.apple.com'; 10 | static const String PATH = '/search'; 11 | 12 | String country; 13 | String entity; 14 | String lang; 15 | int limit; 16 | String media; 17 | 18 | Map defaultQueryOptions; 19 | 20 | ITunesSearchAPI({ 21 | this.country = 'US', 22 | this.entity = 'podcast', 23 | this.lang = 'en_us', 24 | this.limit = 20, 25 | this.media = 'podcast', 26 | }) { 27 | defaultQueryOptions = { 28 | 'country': country, 29 | 'entity': entity, 30 | 'lang': lang, 31 | 'limit': limit.toString(), 32 | 'media': media, 33 | }; 34 | } 35 | 36 | /// Search for results from iTunes 37 | /// 38 | /// Future results = api.search(query, media: 'podcast', ...); 39 | Future> search(String query, [ Map options ]) async { 40 | final Map queryOptions = options ?? defaultQueryOptions; 41 | final Uri uri = Uri.https(AUTHORITY, PATH, { 'term': query }..addAll(queryOptions)); 42 | return dash.compute(parseResults, (await http.get(uri)).body); 43 | } 44 | } 45 | 46 | class ITunesSearchAPIResult { 47 | final String artistName; 48 | final String artworkUrl; 49 | final String collectionName; 50 | final String description; 51 | final String feedUrl; 52 | List genres; 53 | final String kind; 54 | final DateTime releaseDate; 55 | final int trackCount; 56 | 57 | ITunesSearchAPIResult.fromJson(Map json) 58 | : artistName = json['artistName'], 59 | artworkUrl = json['artworkUrl600'], 60 | collectionName = json['collectionName'], 61 | description = json['description'], 62 | feedUrl = json['feedUrl'], 63 | //genres = (json['genres'] as List).asMap().entries.map( 64 | //(Map entry) => ITunesSearchAPIGenre(id: json['genreIds'][entry.key], name: entry.value) 65 | //).toList(), 66 | kind = json['kind'], 67 | releaseDate = DateTime.parse(json['releaseDate']), 68 | trackCount = json['trackCount']; 69 | } 70 | 71 | class ITunesSearchAPIGenre { 72 | final String id; 73 | final String name; 74 | 75 | ITunesSearchAPIGenre({ 76 | this.id, 77 | this.name, 78 | }); 79 | } 80 | 81 | List parseResults(String responseBody) { 82 | final Map parsedContent = json.decode(responseBody); 83 | final List results = parsedContent['results']; 84 | return results.map((dynamic result) => ITunesSearchAPIResult.fromJson(result)).toList(); 85 | } 86 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/lib/model/new_podcasts.dart: -------------------------------------------------------------------------------- 1 | import 'package:jaguar_serializer/jaguar_serializer.dart'; 2 | 3 | 4 | part 'new_podcasts.jser.dart'; 5 | 6 | class NewPodcasts { 7 | 8 | @Alias('artist', isNullable: false, ) 9 | final String artist; 10 | 11 | @Alias('artwork100', isNullable: false, ) 12 | final String artwork100; 13 | 14 | @Alias('artwork30', isNullable: false, ) 15 | final String artwork30; 16 | 17 | @Alias('artwork600', isNullable: false, ) 18 | final String artwork600; 19 | 20 | @Alias('artwork60', isNullable: false, ) 21 | final String artwork60; 22 | 23 | @Alias('artworkOrig', isNullable: false, ) 24 | final String artworkOrig; 25 | 26 | @Alias('description', isNullable: false, ) 27 | final String description; 28 | 29 | @Alias('episodes', isNullable: false, ) 30 | final String episodes; 31 | 32 | @Alias('feed', isNullable: false, ) 33 | final String feed; 34 | 35 | @Alias('genres', isNullable: false, ) 36 | final String genres; 37 | 38 | @Alias('lastModifiedTimestamp', isNullable: false, ) 39 | final DateTime lastModifiedTimestamp; 40 | 41 | @Alias('lastUpdated', isNullable: false, ) 42 | final DateTime lastUpdated; 43 | 44 | @Alias('name', isNullable: false, ) 45 | final String name; 46 | 47 | @Alias('popularity', isNullable: false, ) 48 | final num popularity; 49 | 50 | @Alias('primaryGenre', isNullable: false, ) 51 | final String primaryGenre; 52 | 53 | @Alias('releaseDate', isNullable: false, ) 54 | final DateTime releaseDate; 55 | 56 | @Alias('type', isNullable: false, ) 57 | final String type; 58 | 59 | 60 | NewPodcasts( 61 | 62 | 63 | { 64 | this.artist = null, 65 | this.artwork100 = null, 66 | this.artwork30 = null, 67 | this.artwork600 = null, 68 | this.artwork60 = null, 69 | this.artworkOrig = null, 70 | this.description = null, 71 | this.episodes = null, 72 | 73 | this.feed = null, this.genres = null, 74 | this.lastModifiedTimestamp = null, 75 | this.lastUpdated = null, 76 | this.name = null, 77 | this.popularity = null, 78 | this.primaryGenre = null, 79 | this.releaseDate = null, 80 | this.type = null 81 | 82 | } 83 | ); 84 | 85 | @override 86 | String toString() { 87 | return 'NewPodcasts[artist=$artist, artwork100=$artwork100, artwork30=$artwork30, artwork600=$artwork600, artwork60=$artwork60, artworkOrig=$artworkOrig, description=$description, episodes=$episodes, feed=$feed, genres=$genres, lastModifiedTimestamp=$lastModifiedTimestamp, lastUpdated=$lastUpdated, name=$name, popularity=$popularity, primaryGenre=$primaryGenre, releaseDate=$releaseDate, type=$type, ]'; 88 | } 89 | } 90 | 91 | @GenSerializer(nullableFields: true) 92 | class NewPodcastsSerializer extends Serializer with _$NewPodcastsSerializer { 93 | 94 | } 95 | 96 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/test/podcasts_fields_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:netcastsoss_data_api/api.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | // tests for PodcastsFields 5 | void main() { 6 | var instance = new PodcastsFields(); 7 | 8 | group('test PodcastsFields', () { 9 | // bool id (default value: null) 10 | test('to test the property `id`', () async { 11 | // TODO 12 | }); 13 | 14 | // bool artist (default value: null) 15 | test('to test the property `artist`', () async { 16 | // TODO 17 | }); 18 | 19 | // bool artwork100 (default value: null) 20 | test('to test the property `artwork100`', () async { 21 | // TODO 22 | }); 23 | 24 | // bool artwork30 (default value: null) 25 | test('to test the property `artwork30`', () async { 26 | // TODO 27 | }); 28 | 29 | // bool artwork600 (default value: null) 30 | test('to test the property `artwork600`', () async { 31 | // TODO 32 | }); 33 | 34 | // bool artwork60 (default value: null) 35 | test('to test the property `artwork60`', () async { 36 | // TODO 37 | }); 38 | 39 | // bool artworkOrig (default value: null) 40 | test('to test the property `artworkOrig`', () async { 41 | // TODO 42 | }); 43 | 44 | // bool description (default value: null) 45 | test('to test the property `description`', () async { 46 | // TODO 47 | }); 48 | 49 | // bool episodes (default value: null) 50 | test('to test the property `episodes`', () async { 51 | // TODO 52 | }); 53 | 54 | // bool feed (default value: null) 55 | test('to test the property `feed`', () async { 56 | // TODO 57 | }); 58 | 59 | // bool genres (default value: null) 60 | test('to test the property `genres`', () async { 61 | // TODO 62 | }); 63 | 64 | // bool lastModifiedTimestamp (default value: null) 65 | test('to test the property `lastModifiedTimestamp`', () async { 66 | // TODO 67 | }); 68 | 69 | // bool lastUpdated (default value: null) 70 | test('to test the property `lastUpdated`', () async { 71 | // TODO 72 | }); 73 | 74 | // bool name (default value: null) 75 | test('to test the property `name`', () async { 76 | // TODO 77 | }); 78 | 79 | // bool popularity (default value: null) 80 | test('to test the property `popularity`', () async { 81 | // TODO 82 | }); 83 | 84 | // bool primaryGenre (default value: null) 85 | test('to test the property `primaryGenre`', () async { 86 | // TODO 87 | }); 88 | 89 | // bool releaseDate (default value: null) 90 | test('to test the property `releaseDate`', () async { 91 | // TODO 92 | }); 93 | 94 | // bool type (default value: null) 95 | test('to test the property `type`', () async { 96 | // TODO 97 | }); 98 | 99 | 100 | }); 101 | 102 | } 103 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/test/podcasts_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:netcastsoss_data_api/api.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | // tests for Podcasts 5 | void main() { 6 | var instance = new Podcasts(); 7 | 8 | group('test Podcasts', () { 9 | // String id (default value: null) 10 | test('to test the property `id`', () async { 11 | // TODO 12 | }); 13 | 14 | // String artist (default value: null) 15 | test('to test the property `artist`', () async { 16 | // TODO 17 | }); 18 | 19 | // String artwork100 (default value: null) 20 | test('to test the property `artwork100`', () async { 21 | // TODO 22 | }); 23 | 24 | // String artwork30 (default value: null) 25 | test('to test the property `artwork30`', () async { 26 | // TODO 27 | }); 28 | 29 | // String artwork600 (default value: null) 30 | test('to test the property `artwork600`', () async { 31 | // TODO 32 | }); 33 | 34 | // String artwork60 (default value: null) 35 | test('to test the property `artwork60`', () async { 36 | // TODO 37 | }); 38 | 39 | // String artworkOrig (default value: null) 40 | test('to test the property `artworkOrig`', () async { 41 | // TODO 42 | }); 43 | 44 | // String description (default value: null) 45 | test('to test the property `description`', () async { 46 | // TODO 47 | }); 48 | 49 | // String episodes (default value: null) 50 | test('to test the property `episodes`', () async { 51 | // TODO 52 | }); 53 | 54 | // String feed (default value: null) 55 | test('to test the property `feed`', () async { 56 | // TODO 57 | }); 58 | 59 | // String genres (default value: null) 60 | test('to test the property `genres`', () async { 61 | // TODO 62 | }); 63 | 64 | // DateTime lastModifiedTimestamp (default value: null) 65 | test('to test the property `lastModifiedTimestamp`', () async { 66 | // TODO 67 | }); 68 | 69 | // DateTime lastUpdated (default value: null) 70 | test('to test the property `lastUpdated`', () async { 71 | // TODO 72 | }); 73 | 74 | // String name (default value: null) 75 | test('to test the property `name`', () async { 76 | // TODO 77 | }); 78 | 79 | // num popularity (default value: null) 80 | test('to test the property `popularity`', () async { 81 | // TODO 82 | }); 83 | 84 | // String primaryGenre (default value: null) 85 | test('to test the property `primaryGenre`', () async { 86 | // TODO 87 | }); 88 | 89 | // DateTime releaseDate (default value: null) 90 | test('to test the property `releaseDate`', () async { 91 | // TODO 92 | }); 93 | 94 | // String type (default value: null) 95 | test('to test the property `type`', () async { 96 | // TODO 97 | }); 98 | 99 | 100 | }); 101 | 102 | } 103 | -------------------------------------------------------------------------------- /lib/widgets/notifications.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:flushbar/flushbar.dart'; 4 | import 'package:hear2learn/widgets/settings/index.dart'; 5 | import 'package:hear2learn/models/episode.dart'; 6 | 7 | const int DEFAULT_DURATION = 4; 8 | 9 | void showBatchEpisodeDeleteNotification(BuildContext context, List episodes) { 10 | if(context == null) { 11 | return; 12 | } 13 | 14 | // ignore: always_specify_types 15 | Flushbar( 16 | backgroundColor: Theme.of(context).accentColor, 17 | titleText: Text('Deleted Episodes', style: Theme.of(context).accentTextTheme.title), 18 | messageText: Text( 19 | 'Finished deleting ${episodes.length} total ${episodes.length > 1 ? "episodes" : "episode"}', 20 | style: Theme.of(context).accentTextTheme.body1 21 | ), 22 | duration: const Duration(seconds: DEFAULT_DURATION), 23 | )..show(context); 24 | } 25 | 26 | void showEpisodeDeleteNotification(BuildContext context, Episode episode) { 27 | if(context == null) { 28 | return; 29 | } 30 | 31 | // ignore: always_specify_types 32 | Flushbar( 33 | backgroundColor: Theme.of(context).accentColor, 34 | titleText: Text('Deleted Episode', style: Theme.of(context).accentTextTheme.title), 35 | messageText: Text('Finished deleting "${episode.title}"', style: Theme.of(context).accentTextTheme.body1), 36 | duration: const Duration(seconds: DEFAULT_DURATION), 37 | )..show(context); 38 | } 39 | 40 | void showNoConnectivityNotification(BuildContext context) { 41 | if(context == null) { 42 | return; 43 | } 44 | 45 | // ignore: always_specify_types 46 | Flushbar( 47 | backgroundColor: Theme.of(context).accentColor, 48 | titleText: Text('No Connectivity', style: Theme.of(context).accentTextTheme.title), 49 | messageText: Text('Please try again later when connectivity is available to download episodes', style: Theme.of(context).accentTextTheme.body1), 50 | duration: const Duration(seconds: DEFAULT_DURATION), 51 | )..show(context); 52 | } 53 | 54 | void showNoWifiNotification(BuildContext context) { 55 | if(context == null) { 56 | return; 57 | } 58 | 59 | // ignore: always_specify_types 60 | Flushbar( 61 | backgroundColor: Theme.of(context).accentColor, 62 | titleText: Text('Wifi Unavailable', style: Theme.of(context).accentTextTheme.title), 63 | messageText: Text('Please try again later or update app settings to download without wifi', style: Theme.of(context).accentTextTheme.body1), 64 | mainButton: FlatButton( 65 | child: Text('Settings', style: Theme.of(context).accentTextTheme.button), 66 | onPressed: () { 67 | Navigator.push( 68 | context, 69 | MaterialPageRoute( 70 | builder: (BuildContext context) => Settings(), 71 | settings: const RouteSettings(name: Settings.routeName), 72 | ), 73 | ); 74 | } 75 | ), 76 | duration: const Duration(seconds: DEFAULT_DURATION), 77 | )..show(context); 78 | } 79 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/test/podcasts_partial_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:netcastsoss_data_api/api.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | // tests for PodcastsPartial 5 | void main() { 6 | var instance = new PodcastsPartial(); 7 | 8 | group('test PodcastsPartial', () { 9 | // String id (default value: null) 10 | test('to test the property `id`', () async { 11 | // TODO 12 | }); 13 | 14 | // String artist (default value: null) 15 | test('to test the property `artist`', () async { 16 | // TODO 17 | }); 18 | 19 | // String artwork100 (default value: null) 20 | test('to test the property `artwork100`', () async { 21 | // TODO 22 | }); 23 | 24 | // String artwork30 (default value: null) 25 | test('to test the property `artwork30`', () async { 26 | // TODO 27 | }); 28 | 29 | // String artwork600 (default value: null) 30 | test('to test the property `artwork600`', () async { 31 | // TODO 32 | }); 33 | 34 | // String artwork60 (default value: null) 35 | test('to test the property `artwork60`', () async { 36 | // TODO 37 | }); 38 | 39 | // String artworkOrig (default value: null) 40 | test('to test the property `artworkOrig`', () async { 41 | // TODO 42 | }); 43 | 44 | // String description (default value: null) 45 | test('to test the property `description`', () async { 46 | // TODO 47 | }); 48 | 49 | // String episodes (default value: null) 50 | test('to test the property `episodes`', () async { 51 | // TODO 52 | }); 53 | 54 | // String feed (default value: null) 55 | test('to test the property `feed`', () async { 56 | // TODO 57 | }); 58 | 59 | // String genres (default value: null) 60 | test('to test the property `genres`', () async { 61 | // TODO 62 | }); 63 | 64 | // DateTime lastModifiedTimestamp (default value: null) 65 | test('to test the property `lastModifiedTimestamp`', () async { 66 | // TODO 67 | }); 68 | 69 | // DateTime lastUpdated (default value: null) 70 | test('to test the property `lastUpdated`', () async { 71 | // TODO 72 | }); 73 | 74 | // String name (default value: null) 75 | test('to test the property `name`', () async { 76 | // TODO 77 | }); 78 | 79 | // num popularity (default value: null) 80 | test('to test the property `popularity`', () async { 81 | // TODO 82 | }); 83 | 84 | // String primaryGenre (default value: null) 85 | test('to test the property `primaryGenre`', () async { 86 | // TODO 87 | }); 88 | 89 | // DateTime releaseDate (default value: null) 90 | test('to test the property `releaseDate`', () async { 91 | // TODO 92 | }); 93 | 94 | // String type (default value: null) 95 | test('to test the property `type`', () async { 96 | // TODO 97 | }); 98 | 99 | 100 | }); 101 | 102 | } 103 | -------------------------------------------------------------------------------- /lib/models/app_settings.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:hear2learn/app.dart'; 4 | import 'package:hear2learn/models/episode.dart'; 5 | 6 | class AppSettings { 7 | // settings 8 | bool privacySetting; 9 | bool skipSilence; 10 | double speed; 11 | String themeName; 12 | bool wifiSetting; 13 | 14 | // other prefs 15 | String currentEpisode; 16 | String currentPodcast; 17 | EpisodeQueue episodeQueue; 18 | 19 | AppSettings({ 20 | this.currentEpisode, 21 | this.currentPodcast, 22 | this.episodeQueue, 23 | this.privacySetting = true, 24 | this.skipSilence = false, 25 | this.speed = 1.0, 26 | this.themeName = 'Dark', 27 | this.wifiSetting = false, 28 | }); 29 | 30 | AppSettings.prefs() { 31 | final App app = App(); 32 | currentEpisode = app.prefs.getString('currentEpisode'); 33 | currentPodcast = app.prefs.getString('currentPodcast'); 34 | episodeQueue = episodeQueueFromString(app.prefs.getString('episodeQueue')); 35 | privacySetting = app.prefs.getBool('privacySetting') ?? true; 36 | skipSilence = app.prefs.getBool('skipSilence') ?? false; 37 | speed = app.prefs.getDouble('speed') ?? 1.0; 38 | themeName = app.prefs.getString('themeName') ?? 'Dark'; 39 | wifiSetting = app.prefs.getBool('wifiSetting') ?? false; 40 | } 41 | 42 | AppSettings copyWith({ 43 | String currentEpisode, 44 | String currentPodcast, 45 | EpisodeQueue episodeQueue, 46 | bool privacySetting, 47 | bool skipSilence, 48 | double speed, 49 | String themeName, 50 | bool wifiSetting, 51 | }) { 52 | return AppSettings( 53 | currentEpisode: currentEpisode ?? this.currentEpisode, 54 | currentPodcast: currentPodcast ?? this.currentPodcast, 55 | episodeQueue: episodeQueue ?? this.episodeQueue, 56 | privacySetting: privacySetting ?? this.privacySetting, 57 | skipSilence: skipSilence ?? this.skipSilence, 58 | speed: speed ?? this.speed, 59 | themeName: themeName ?? this.themeName, 60 | wifiSetting: wifiSetting ?? this.wifiSetting, 61 | ); 62 | } 63 | 64 | @override 65 | String toString() { 66 | return 'AppSettings[currentEpisode=$currentEpisode, currentPodcast=$currentPodcast, episodeQueue=${episodeQueue.toString()}, wifiSetting=${wifiSetting.toString()}, skipSilence=${skipSilence.toString()}, speed=${speed.toString()}, privacySetting=${privacySetting.toString()}, themeName=$themeName]'; 67 | } 68 | 69 | Future persistPreferences() async { 70 | final App app = App(); 71 | await app.prefs.setString('currentEpisode', currentEpisode); 72 | await app.prefs.setString('currentPodcast', currentPodcast); 73 | await app.prefs.setString('episodeQueue', episodeQueue.toString()); 74 | await app.prefs.setBool('privacySetting', privacySetting); 75 | await app.prefs.setBool('skipSilence', skipSilence); 76 | await app.prefs.setDouble('speed', speed); 77 | await app.prefs.setString('themeName', themeName); 78 | await app.prefs.setBool('wifiSetting', wifiSetting); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/test/podcasts_with_relations_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:netcastsoss_data_api/api.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | // tests for PodcastsWithRelations 5 | void main() { 6 | var instance = new PodcastsWithRelations(); 7 | 8 | group('test PodcastsWithRelations', () { 9 | // String id (default value: null) 10 | test('to test the property `id`', () async { 11 | // TODO 12 | }); 13 | 14 | // String artist (default value: null) 15 | test('to test the property `artist`', () async { 16 | // TODO 17 | }); 18 | 19 | // String artwork100 (default value: null) 20 | test('to test the property `artwork100`', () async { 21 | // TODO 22 | }); 23 | 24 | // String artwork30 (default value: null) 25 | test('to test the property `artwork30`', () async { 26 | // TODO 27 | }); 28 | 29 | // String artwork600 (default value: null) 30 | test('to test the property `artwork600`', () async { 31 | // TODO 32 | }); 33 | 34 | // String artwork60 (default value: null) 35 | test('to test the property `artwork60`', () async { 36 | // TODO 37 | }); 38 | 39 | // String artworkOrig (default value: null) 40 | test('to test the property `artworkOrig`', () async { 41 | // TODO 42 | }); 43 | 44 | // String description (default value: null) 45 | test('to test the property `description`', () async { 46 | // TODO 47 | }); 48 | 49 | // String episodes (default value: null) 50 | test('to test the property `episodes`', () async { 51 | // TODO 52 | }); 53 | 54 | // String feed (default value: null) 55 | test('to test the property `feed`', () async { 56 | // TODO 57 | }); 58 | 59 | // String genres (default value: null) 60 | test('to test the property `genres`', () async { 61 | // TODO 62 | }); 63 | 64 | // DateTime lastModifiedTimestamp (default value: null) 65 | test('to test the property `lastModifiedTimestamp`', () async { 66 | // TODO 67 | }); 68 | 69 | // DateTime lastUpdated (default value: null) 70 | test('to test the property `lastUpdated`', () async { 71 | // TODO 72 | }); 73 | 74 | // String name (default value: null) 75 | test('to test the property `name`', () async { 76 | // TODO 77 | }); 78 | 79 | // num popularity (default value: null) 80 | test('to test the property `popularity`', () async { 81 | // TODO 82 | }); 83 | 84 | // String primaryGenre (default value: null) 85 | test('to test the property `primaryGenre`', () async { 86 | // TODO 87 | }); 88 | 89 | // DateTime releaseDate (default value: null) 90 | test('to test the property `releaseDate`', () async { 91 | // TODO 92 | }); 93 | 94 | // String type (default value: null) 95 | test('to test the property `type`', () async { 96 | // TODO 97 | }); 98 | 99 | 100 | }); 101 | 102 | } 103 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/lib/model/podcasts.dart: -------------------------------------------------------------------------------- 1 | import 'package:jaguar_serializer/jaguar_serializer.dart'; 2 | 3 | 4 | part 'podcasts.jser.dart'; 5 | 6 | class Podcasts { 7 | 8 | @Alias('id', isNullable: false, ) 9 | final String id; 10 | 11 | @Alias('artist', isNullable: false, ) 12 | final String artist; 13 | 14 | @Alias('artwork100', isNullable: false, ) 15 | final String artwork100; 16 | 17 | @Alias('artwork30', isNullable: false, ) 18 | final String artwork30; 19 | 20 | @Alias('artwork600', isNullable: false, ) 21 | final String artwork600; 22 | 23 | @Alias('artwork60', isNullable: false, ) 24 | final String artwork60; 25 | 26 | @Alias('artworkOrig', isNullable: false, ) 27 | final String artworkOrig; 28 | 29 | @Alias('description', isNullable: false, ) 30 | final String description; 31 | 32 | @Alias('episodes', isNullable: false, ) 33 | final String episodes; 34 | 35 | @Alias('feed', isNullable: false, ) 36 | final String feed; 37 | 38 | @Alias('genres', isNullable: false, ) 39 | final String genres; 40 | 41 | @Alias('lastModifiedTimestamp', isNullable: false, ) 42 | final DateTime lastModifiedTimestamp; 43 | 44 | @Alias('lastUpdated', isNullable: false, ) 45 | final DateTime lastUpdated; 46 | 47 | @Alias('name', isNullable: false, ) 48 | final String name; 49 | 50 | @Alias('popularity', isNullable: false, ) 51 | final num popularity; 52 | 53 | @Alias('primaryGenre', isNullable: false, ) 54 | final String primaryGenre; 55 | 56 | @Alias('releaseDate', isNullable: false, ) 57 | final DateTime releaseDate; 58 | 59 | @Alias('type', isNullable: false, ) 60 | final String type; 61 | 62 | 63 | Podcasts( 64 | 65 | 66 | { 67 | 68 | this.id = null, this.artist = null, 69 | this.artwork100 = null, 70 | this.artwork30 = null, 71 | this.artwork600 = null, 72 | this.artwork60 = null, 73 | this.artworkOrig = null, 74 | this.description = null, 75 | this.episodes = null, 76 | 77 | this.feed = null, this.genres = null, 78 | this.lastModifiedTimestamp = null, 79 | this.lastUpdated = null, 80 | this.name = null, 81 | this.popularity = null, 82 | this.primaryGenre = null, 83 | this.releaseDate = null, 84 | this.type = null 85 | 86 | } 87 | ); 88 | 89 | @override 90 | String toString() { 91 | return 'Podcasts[id=$id, artist=$artist, artwork100=$artwork100, artwork30=$artwork30, artwork600=$artwork600, artwork60=$artwork60, artworkOrig=$artworkOrig, description=$description, episodes=$episodes, feed=$feed, genres=$genres, lastModifiedTimestamp=$lastModifiedTimestamp, lastUpdated=$lastUpdated, name=$name, popularity=$popularity, primaryGenre=$primaryGenre, releaseDate=$releaseDate, type=$type, ]'; 92 | } 93 | } 94 | 95 | @GenSerializer(nullableFields: true) 96 | class PodcastsSerializer extends Serializer with _$PodcastsSerializer { 97 | 98 | } 99 | 100 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/lib/model/podcasts_fields.dart: -------------------------------------------------------------------------------- 1 | import 'package:jaguar_serializer/jaguar_serializer.dart'; 2 | 3 | 4 | part 'podcasts_fields.jser.dart'; 5 | 6 | class PodcastsFields { 7 | 8 | @Alias('id', isNullable: false, ) 9 | final bool id; 10 | 11 | @Alias('artist', isNullable: false, ) 12 | final bool artist; 13 | 14 | @Alias('artwork100', isNullable: false, ) 15 | final bool artwork100; 16 | 17 | @Alias('artwork30', isNullable: false, ) 18 | final bool artwork30; 19 | 20 | @Alias('artwork600', isNullable: false, ) 21 | final bool artwork600; 22 | 23 | @Alias('artwork60', isNullable: false, ) 24 | final bool artwork60; 25 | 26 | @Alias('artworkOrig', isNullable: false, ) 27 | final bool artworkOrig; 28 | 29 | @Alias('description', isNullable: false, ) 30 | final bool description; 31 | 32 | @Alias('episodes', isNullable: false, ) 33 | final bool episodes; 34 | 35 | @Alias('feed', isNullable: false, ) 36 | final bool feed; 37 | 38 | @Alias('genres', isNullable: false, ) 39 | final bool genres; 40 | 41 | @Alias('lastModifiedTimestamp', isNullable: false, ) 42 | final bool lastModifiedTimestamp; 43 | 44 | @Alias('lastUpdated', isNullable: false, ) 45 | final bool lastUpdated; 46 | 47 | @Alias('name', isNullable: false, ) 48 | final bool name; 49 | 50 | @Alias('popularity', isNullable: false, ) 51 | final bool popularity; 52 | 53 | @Alias('primaryGenre', isNullable: false, ) 54 | final bool primaryGenre; 55 | 56 | @Alias('releaseDate', isNullable: false, ) 57 | final bool releaseDate; 58 | 59 | @Alias('type', isNullable: false, ) 60 | final bool type; 61 | 62 | 63 | PodcastsFields( 64 | 65 | 66 | { 67 | this.id = null, 68 | this.artist = null, 69 | this.artwork100 = null, 70 | this.artwork30 = null, 71 | this.artwork600 = null, 72 | this.artwork60 = null, 73 | this.artworkOrig = null, 74 | this.description = null, 75 | this.episodes = null, 76 | this.feed = null, 77 | this.genres = null, 78 | this.lastModifiedTimestamp = null, 79 | this.lastUpdated = null, 80 | this.name = null, 81 | this.popularity = null, 82 | this.primaryGenre = null, 83 | this.releaseDate = null, 84 | this.type = null 85 | 86 | } 87 | ); 88 | 89 | @override 90 | String toString() { 91 | return 'PodcastsFields[id=$id, artist=$artist, artwork100=$artwork100, artwork30=$artwork30, artwork600=$artwork600, artwork60=$artwork60, artworkOrig=$artworkOrig, description=$description, episodes=$episodes, feed=$feed, genres=$genres, lastModifiedTimestamp=$lastModifiedTimestamp, lastUpdated=$lastUpdated, name=$name, popularity=$popularity, primaryGenre=$primaryGenre, releaseDate=$releaseDate, type=$type, ]'; 92 | } 93 | } 94 | 95 | @GenSerializer(nullableFields: true) 96 | class PodcastsFieldsSerializer extends Serializer with _$PodcastsFieldsSerializer { 97 | 98 | } 99 | 100 | -------------------------------------------------------------------------------- /lib/services/api/netcastsoss_data/lib/model/podcasts_partial.dart: -------------------------------------------------------------------------------- 1 | import 'package:jaguar_serializer/jaguar_serializer.dart'; 2 | 3 | 4 | part 'podcasts_partial.jser.dart'; 5 | 6 | class PodcastsPartial { 7 | 8 | @Alias('id', isNullable: false, ) 9 | final String id; 10 | 11 | @Alias('artist', isNullable: false, ) 12 | final String artist; 13 | 14 | @Alias('artwork100', isNullable: false, ) 15 | final String artwork100; 16 | 17 | @Alias('artwork30', isNullable: false, ) 18 | final String artwork30; 19 | 20 | @Alias('artwork600', isNullable: false, ) 21 | final String artwork600; 22 | 23 | @Alias('artwork60', isNullable: false, ) 24 | final String artwork60; 25 | 26 | @Alias('artworkOrig', isNullable: false, ) 27 | final String artworkOrig; 28 | 29 | @Alias('description', isNullable: false, ) 30 | final String description; 31 | 32 | @Alias('episodes', isNullable: false, ) 33 | final String episodes; 34 | 35 | @Alias('feed', isNullable: false, ) 36 | final String feed; 37 | 38 | @Alias('genres', isNullable: false, ) 39 | final String genres; 40 | 41 | @Alias('lastModifiedTimestamp', isNullable: false, ) 42 | final DateTime lastModifiedTimestamp; 43 | 44 | @Alias('lastUpdated', isNullable: false, ) 45 | final DateTime lastUpdated; 46 | 47 | @Alias('name', isNullable: false, ) 48 | final String name; 49 | 50 | @Alias('popularity', isNullable: false, ) 51 | final num popularity; 52 | 53 | @Alias('primaryGenre', isNullable: false, ) 54 | final String primaryGenre; 55 | 56 | @Alias('releaseDate', isNullable: false, ) 57 | final DateTime releaseDate; 58 | 59 | @Alias('type', isNullable: false, ) 60 | final String type; 61 | 62 | 63 | PodcastsPartial( 64 | 65 | 66 | { 67 | this.id = null, 68 | this.artist = null, 69 | this.artwork100 = null, 70 | this.artwork30 = null, 71 | this.artwork600 = null, 72 | this.artwork60 = null, 73 | this.artworkOrig = null, 74 | this.description = null, 75 | this.episodes = null, 76 | this.feed = null, 77 | this.genres = null, 78 | this.lastModifiedTimestamp = null, 79 | this.lastUpdated = null, 80 | this.name = null, 81 | this.popularity = null, 82 | this.primaryGenre = null, 83 | this.releaseDate = null, 84 | this.type = null 85 | 86 | } 87 | ); 88 | 89 | @override 90 | String toString() { 91 | return 'PodcastsPartial[id=$id, artist=$artist, artwork100=$artwork100, artwork30=$artwork30, artwork600=$artwork600, artwork60=$artwork60, artworkOrig=$artworkOrig, description=$description, episodes=$episodes, feed=$feed, genres=$genres, lastModifiedTimestamp=$lastModifiedTimestamp, lastUpdated=$lastUpdated, name=$name, popularity=$popularity, primaryGenre=$primaryGenre, releaseDate=$releaseDate, type=$type, ]'; 92 | } 93 | } 94 | 95 | @GenSerializer(nullableFields: true) 96 | class PodcastsPartialSerializer extends Serializer with _$PodcastsPartialSerializer { 97 | 98 | } 99 | 100 | --------------------------------------------------------------------------------