├── linux ├── .gitignore ├── main.cc ├── flutter │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ ├── generated_plugins.cmake │ └── CMakeLists.txt ├── my_application.h ├── my_application.cc └── CMakeLists.txt ├── ios ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner │ ├── Runner-Bridging-Header.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 │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── GoogleService-Info.plist │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── firebase_app_id_file.json └── .gitignore ├── .fvm └── fvm_config.json ├── extras ├── f_b.jpg ├── bottom_bar.png └── screenshot │ ├── 1.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ └── 5.png ├── macos ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── Runner │ ├── Configs │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ ├── Warnings.xcconfig │ │ └── AppInfo.xcconfig │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_512.png │ │ │ ├── app_icon_64.png │ │ │ ├── app_icon_1024.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Release.entitlements │ ├── DebugProfile.entitlements │ ├── MainFlutterWindow.swift │ ├── Info.plist │ └── GoogleService-Info.plist ├── .gitignore ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── firebase_app_id_file.json └── Runner.xcodeproj │ ├── project.xcworkspace │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ └── xcschemes │ └── Runner.xcscheme ├── assets ├── icon.png ├── logo.png └── lottie │ └── loading.json ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── manifest.json └── index.html ├── android ├── gradle.properties ├── app │ ├── src │ │ ├── main │ │ │ ├── ic_launcher-playstore.png │ │ │ ├── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ ├── ic_music.png │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ ├── ic_launcher_background.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ ├── ic_music.png │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ ├── ic_launcher_background.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ ├── ic_music.png │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ ├── ic_launcher_background.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ ├── ic_music.png │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ ├── ic_launcher_background.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ ├── ic_music.png │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ ├── ic_launcher_background.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ │ ├── ic_launcher.xml │ │ │ │ │ └── ic_launcher_round.xml │ │ │ │ ├── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21 │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── values │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night │ │ │ │ │ └── styles.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── sunday_suspense │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── google-services.json │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle └── build.gradle ├── .vscode ├── settings.json └── launch.json ├── server ├── requirements.txt ├── backup.py ├── checkup_db.py ├── checkplaylist.py ├── main.py └── build_new_item.py ├── .github ├── ISSUE_TEMPLATE │ ├── custom.md │ ├── feature_request.md │ └── bug_report.md ├── workflows │ ├── issuenotifier.yml │ ├── update_db.yml │ ├── prchecker.yml │ └── db_backup.yml └── FUNDING.yml ├── lib ├── screens │ ├── aboutpage.dart │ ├── historypage.dart │ ├── root.dart │ ├── show_all_page.dart │ ├── searchScreen.dart │ ├── homepage.dart │ ├── categorypage.dart │ └── player.dart ├── notifiers │ ├── play_button_notifier.dart │ ├── repeat_button_notifier.dart │ └── progress_notifier.dart ├── utils.dart ├── services │ ├── service_locator.dart │ ├── database_service.dart │ ├── database_service.txt │ └── notification_service.dart ├── generated_plugin_registrant.dart ├── widgets │ ├── searchbar.dart │ ├── miniplayer.dart │ └── art_glass_overlay.dart ├── ui │ ├── app_colors.dart │ ├── text_styles.dart │ └── my_theme.dart ├── main.dart ├── firebase_options.dart └── page_manager.dart ├── .gitignore ├── test └── widget_test.dart ├── pubspec.yaml ├── analysis_options.yaml ├── .metadata ├── CONTRIBUTING.md ├── README.md └── CODE_OF_CONDUCT.md /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /.fvm/fvm_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "flutterSdkVersion": "stable", 3 | "flavors": {} 4 | } -------------------------------------------------------------------------------- /extras/f_b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/extras/f_b.jpg -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "ephemeral/Flutter-Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/assets/icon.png -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/assets/logo.png -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "ephemeral/Flutter-Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/web/favicon.png -------------------------------------------------------------------------------- /extras/bottom_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/extras/bottom_bar.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/web/icons/Icon-512.png -------------------------------------------------------------------------------- /extras/screenshot/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/extras/screenshot/1.png -------------------------------------------------------------------------------- /extras/screenshot/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/extras/screenshot/2.png -------------------------------------------------------------------------------- /extras/screenshot/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/extras/screenshot/3.png -------------------------------------------------------------------------------- /extras/screenshot/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/extras/screenshot/4.png -------------------------------------------------------------------------------- /extras/screenshot/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/extras/screenshot/5.png -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmake.configureOnOpen": false, 3 | "cmake.sourceDirectory": "${workspaceFolder}/linux" 4 | } -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /android/app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/android/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/android/app/src/main/res/mipmap-hdpi/ic_music.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/android/app/src/main/res/mipmap-mdpi/ic_music.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_music.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_music.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_music.png -------------------------------------------------------------------------------- /server/requirements.txt: -------------------------------------------------------------------------------- 1 | blurhash==1.1.4 2 | blurhash_python==1.1.3 3 | meilisearch==0.22.1 4 | python-dotenv==0.21.0 5 | pytube==12.1.0 6 | requests==2.28.1 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/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/imsudip/sunday_suspense/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/imsudip/sunday_suspense/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/imsudip/sunday_suspense/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/imsudip/sunday_suspense/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/imsudip/sunday_suspense/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/imsudip/sunday_suspense/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/imsudip/sunday_suspense/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/imsudip/sunday_suspense/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/imsudip/sunday_suspense/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/imsudip/sunday_suspense/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/imsudip/sunday_suspense/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/imsudip/sunday_suspense/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/imsudip/sunday_suspense/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imsudip/sunday_suspense/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/imsudip/sunday_suspense/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/sunday_suspense/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.sunday_suspense 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | 10 | void fl_register_plugins(FlPluginRegistry* registry) { 11 | } 12 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip 6 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/firebase_app_id_file.json: -------------------------------------------------------------------------------- 1 | { 2 | "file_generated_by": "FlutterFire CLI", 3 | "purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory", 4 | "GOOGLE_APP_ID": "1:50945720064:ios:f5424ba3bfce633aba3de5", 5 | "FIREBASE_PROJECT_ID": "sunday-suspense-14243", 6 | "GCM_SENDER_ID": "50945720064" 7 | } -------------------------------------------------------------------------------- /macos/firebase_app_id_file.json: -------------------------------------------------------------------------------- 1 | { 2 | "file_generated_by": "FlutterFire CLI", 3 | "purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory", 4 | "GOOGLE_APP_ID": "1:50945720064:ios:f5424ba3bfce633aba3de5", 5 | "FIREBASE_PROJECT_ID": "sunday-suspense-14243", 6 | "GCM_SENDER_ID": "50945720064" 7 | } -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/screens/aboutpage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/src/widgets/container.dart'; 2 | import 'package:flutter/src/widgets/framework.dart'; 3 | 4 | class AboutPageScreen extends StatelessWidget { 5 | const AboutPageScreen({super.key}); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return Container(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/notifiers/play_button_notifier.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | class PlayButtonNotifier extends ValueNotifier { 4 | PlayButtonNotifier() : super(_initialValue); 5 | static const _initialValue = ButtonState.paused; 6 | } 7 | 8 | enum ButtonState { 9 | paused, 10 | playing, 11 | loading, 12 | } 13 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:date_time_format/date_time_format.dart'; 2 | 3 | String calculateDuration(var length) { 4 | int hours = (length ~/ 3600); 5 | int minutes = (length ~/ 60) % 60; 6 | if (hours == 0) { 7 | return "$minutes mins"; 8 | } else { 9 | return "$hours hrs $minutes mins"; 10 | } 11 | } 12 | 13 | String showDate(String date) { 14 | DateTime dateTime = DateTime.parse(date); 15 | return DateTimeFormat.format(dateTime, format: 'jS M Y'); 16 | } 17 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController.init() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/notifiers/repeat_button_notifier.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | class RepeatButtonNotifier extends ValueNotifier { 4 | RepeatButtonNotifier() : super(_initialValue); 5 | static const _initialValue = RepeatState.off; 6 | 7 | void nextState() { 8 | final next = (value.index + 1) % RepeatState.values.length; 9 | value = RepeatState.values[next]; 10 | } 11 | } 12 | 13 | enum RepeatState { 14 | off, 15 | repeatSong, 16 | repeatPlaylist, 17 | } 18 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/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/notifiers/progress_notifier.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | class ProgressNotifier extends ValueNotifier { 4 | ProgressNotifier() : super(_initialValue); 5 | static const _initialValue = ProgressBarState( 6 | current: Duration.zero, 7 | buffered: Duration.zero, 8 | total: Duration.zero, 9 | ); 10 | } 11 | 12 | class ProgressBarState { 13 | const ProgressBarState({ 14 | required this.current, 15 | required this.buffered, 16 | required this.total, 17 | }); 18 | final Duration current; 19 | final Duration buffered; 20 | final Duration total; 21 | } 22 | -------------------------------------------------------------------------------- /lib/services/service_locator.dart: -------------------------------------------------------------------------------- 1 | import 'package:audio_service/audio_service.dart'; 2 | import 'package:flutter_dotenv/flutter_dotenv.dart'; 3 | import 'package:meilisearch/meilisearch.dart'; 4 | 5 | import '../page_manager.dart'; 6 | import 'package:get_it/get_it.dart'; 7 | 8 | GetIt getIt = GetIt.instance; 9 | 10 | Future setupServiceLocator() async { 11 | // page state 12 | getIt.registerSingleton(PageManager()); 13 | 14 | // meilisearch 15 | getIt.registerSingleton( 16 | MeiliSearchClient('https://meilisearch-on-koyeb-imsudip.koyeb.app/', dotenv.env['MASTER_KEY'])); 17 | } 18 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = sunday_suspense 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.example.sundaySuspense 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2022 com.example. All rights reserved. 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /lib/generated_plugin_registrant.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // ignore_for_file: directives_ordering 6 | // ignore_for_file: lines_longer_than_80_chars 7 | // ignore_for_file: depend_on_referenced_packages 8 | 9 | import 'package:audio_service_web/audio_service_web.dart'; 10 | import 'package:audio_session/audio_session_web.dart'; 11 | import 'package:just_audio_web/just_audio_web.dart'; 12 | 13 | import 'package:flutter_web_plugins/flutter_web_plugins.dart'; 14 | 15 | // ignore: public_member_api_docs 16 | void registerPlugins(Registrar registrar) { 17 | AudioServiceWeb.registerWith(registrar); 18 | AudioSessionWeb.registerWith(registrar); 19 | JustAudioPlugin.registerWith(registrar); 20 | registrar.registerMessageHandler(); 21 | } 22 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.6.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.1.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | classpath 'com.google.gms:google-services:4.3.13' 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | mavenCentral() 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 | -------------------------------------------------------------------------------- /.github/workflows/issuenotifier.yml: -------------------------------------------------------------------------------- 1 | name: Issue Notifier 2 | on: 3 | issues: 4 | types: 5 | - reopened 6 | - opened 7 | 8 | jobs: 9 | notify: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | issues: read 13 | 14 | steps: 15 | - name: Commit on Telegram 16 | uses: appleboy/telegram-action@master 17 | with: 18 | to: ${{ secrets.TELEGRAM_TO }} 19 | token: ${{ secrets.TELEGRAM_TOKEN }} 20 | format: markdown 21 | disable_web_page_preview: true 22 | message: | 23 | New Issue ([#${{ github.event.issue.number }}](${{ github.event.issue.html_url }})) opened by [#${{ github.event.issue.user.login }}](${{ github.event.issue.user.html_url }}). 24 | Issue: ${{ github.event.issue.title }} 25 | 26 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: ["https://www.buymeacoffee.com/imsudip"] 14 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "sunday_suspense", 9 | "request": "launch", 10 | "type": "dart" 11 | }, 12 | { 13 | "name": "sunday_suspense (profile mode)", 14 | "request": "launch", 15 | "type": "dart", 16 | "flutterMode": "profile" 17 | }, 18 | { 19 | "name": "sunday_suspense (release mode)", 20 | "request": "launch", 21 | "type": "dart", 22 | "flutterMode": "release" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | ) 7 | 8 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 9 | ) 10 | 11 | set(PLUGIN_BUNDLED_LIBRARIES) 12 | 13 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 14 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 15 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 16 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 18 | endforeach(plugin) 19 | 20 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 21 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 23 | endforeach(ffi_plugin) 24 | -------------------------------------------------------------------------------- /server/backup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import load_dotenv 3 | import json 4 | import meilisearch 5 | 6 | 7 | load_dotenv() 8 | meiliClient = meilisearch.Client( 9 | "https://meilisearch-on-koyeb-imsudip.koyeb.app/", os.getenv('MASTER_KEY')) 10 | try: 11 | index = meiliClient.index('sunday') 12 | # get all documents and save to json file 13 | all_docs = index.get_documents({'limit': 100000}) 14 | d = all_docs.results 15 | d2 = [] 16 | for item in d: 17 | d2.append(item.__dict__['_Document__doc']) 18 | if len(d2) > 0: 19 | with open('sunday.json', 'w', encoding='utf-8') as f: 20 | json.dump(d2, f, ensure_ascii=False, indent=4) 21 | 22 | print("total docs count: "+str(len(d2))) 23 | 24 | # catch exception 25 | except Exception as e: 26 | print(e) 27 | print("error") 28 | finally: 29 | print("done") 30 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Symbolication related 36 | app.*.symbols 37 | 38 | # Obfuscation related 39 | app.*.map.json 40 | 41 | # Android Studio will place build artifacts here 42 | /android/app/debug 43 | /android/app/profile 44 | /android/app/release 45 | 46 | *.env 47 | 48 | .fvm/flutter_sdk 49 | node_modules 50 | -------------------------------------------------------------------------------- /server/checkup_db.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import load_dotenv 3 | import build_new_item 4 | import requests 5 | import json 6 | import meilisearch 7 | 8 | 9 | load_dotenv() 10 | meiliClient = meilisearch.Client( 11 | "https://meilisearch-on-koyeb-imsudip.koyeb.app/", os.getenv('MASTER_KEY')) 12 | # firebase_key = os.getenv('FIREBASE_KEY') 13 | 14 | 15 | # fetch newest item from meilisearch 16 | 17 | def get_newest_items(): 18 | index = meiliClient.index('sunday') 19 | # get the newest item 20 | all_docs = index.search(" ", {'limit': 5, 'sort': ['timestamp:desc']}) 21 | d = all_docs['hits'] 22 | # print(d) 23 | for item in d: 24 | if (item['length'] == 0): 25 | print('Updating item...') 26 | print(item['title']) 27 | new_item = build_new_item.getVideoDataFromLink(item['url']) 28 | index.update_documents([new_item]) 29 | print('Item updated!') 30 | 31 | 32 | get_newest_items() 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/workflows/update_db.yml: -------------------------------------------------------------------------------- 1 | name: update_db 2 | 3 | on: 4 | schedule: 5 | - cron: "0 */4 * * 0" # every 4 hours on Sunday 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | with: 14 | token: ${{ secrets.PAT }} 15 | - name: checkout repo content 16 | uses: actions/checkout@v2 # checkout the repository content to github runner 17 | 18 | - name: setup python 19 | uses: actions/setup-python@v4 20 | with: 21 | python-version: "3.9" # install the python version needed 22 | 23 | - name: install python packages 24 | run: | 25 | cd ./server 26 | python -m pip install --upgrade pip 27 | pip install -r requirements.txt 28 | 29 | - name: execute py script # run main.py 30 | env: 31 | MASTER_KEY: ${{ secrets.MASTER_KEY }} 32 | FIREBASE_KEY: ${{ secrets.FIREBASE_KEY }} 33 | run: | 34 | cd ./server 35 | python main.py 36 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sunday_suspense", 3 | "short_name": "sunday_suspense", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility in the flutter_test package. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:sunday_suspense/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /server/checkplaylist.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | adventure_id = 'PLq71IJk8mCV4bl9PzA1RJ6ipr0TBw-7VK' 4 | crime_id = 'PLq71IJk8mCV4YmG5ULzAq2QCZqdDRWLhO' 5 | horror_id = 'PLq71IJk8mCV4vFBqzx4JmLpWFtvpl93Sv' 6 | thriller_id = 'PLq71IJk8mCV7tZ7b3BLx5lZitMvlGL-9p' 7 | 8 | 9 | def get_rss_feed(playlist_id): 10 | url = 'https://api.rss2json.com/v1/api.json?rss_url=https://www.youtube.com/feeds/videos.xml?playlist_id=' + playlist_id 11 | response = requests.get(url) 12 | data = response.json() 13 | video_ids = [] 14 | for item in data['items']: 15 | video_ids.append(item['link'].split('=')[1]) 16 | return video_ids 17 | 18 | 19 | def check_playlist_inclusion(video_id): 20 | adventure_ids = get_rss_feed(adventure_id) 21 | crime_ids = get_rss_feed(crime_id) 22 | horror_ids = get_rss_feed(horror_id) 23 | thriller_ids = get_rss_feed(thriller_id) 24 | 25 | tags = [] 26 | if video_id in adventure_ids: 27 | tags.append('adventure') 28 | if video_id in crime_ids: 29 | tags.append('crime') 30 | if video_id in horror_ids: 31 | tags.append('horror') 32 | if video_id in thriller_ids: 33 | tags.append('thriller') 34 | return tags 35 | -------------------------------------------------------------------------------- /ios/Runner/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CLIENT_ID 6 | 50945720064-1a40ats3fthtkvi1p6in3ih89aj5dnkj.apps.googleusercontent.com 7 | REVERSED_CLIENT_ID 8 | com.googleusercontent.apps.50945720064-1a40ats3fthtkvi1p6in3ih89aj5dnkj 9 | API_KEY 10 | AIzaSyBGsEz7Ab__YfMU_ejLtsSww7JP4nI8Qig 11 | GCM_SENDER_ID 12 | 50945720064 13 | PLIST_VERSION 14 | 1 15 | BUNDLE_ID 16 | com.example.sundaySuspense 17 | PROJECT_ID 18 | sunday-suspense-14243 19 | STORAGE_BUCKET 20 | sunday-suspense-14243.appspot.com 21 | IS_ADS_ENABLED 22 | 23 | IS_ANALYTICS_ENABLED 24 | 25 | IS_APPINVITE_ENABLED 26 | 27 | IS_GCM_ENABLED 28 | 29 | IS_SIGNIN_ENABLED 30 | 31 | GOOGLE_APP_ID 32 | 1:50945720064:ios:f5424ba3bfce633aba3de5 33 | 34 | -------------------------------------------------------------------------------- /macos/Runner/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CLIENT_ID 6 | 50945720064-1a40ats3fthtkvi1p6in3ih89aj5dnkj.apps.googleusercontent.com 7 | REVERSED_CLIENT_ID 8 | com.googleusercontent.apps.50945720064-1a40ats3fthtkvi1p6in3ih89aj5dnkj 9 | API_KEY 10 | AIzaSyBGsEz7Ab__YfMU_ejLtsSww7JP4nI8Qig 11 | GCM_SENDER_ID 12 | 50945720064 13 | PLIST_VERSION 14 | 1 15 | BUNDLE_ID 16 | com.example.sundaySuspense 17 | PROJECT_ID 18 | sunday-suspense-14243 19 | STORAGE_BUCKET 20 | sunday-suspense-14243.appspot.com 21 | IS_ADS_ENABLED 22 | 23 | IS_ANALYTICS_ENABLED 24 | 25 | IS_APPINVITE_ENABLED 26 | 27 | IS_GCM_ENABLED 28 | 29 | IS_SIGNIN_ENABLED 30 | 31 | GOOGLE_APP_ID 32 | 1:50945720064:ios:f5424ba3bfce633aba3de5 33 | 34 | -------------------------------------------------------------------------------- /lib/widgets/searchbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:iconsax/iconsax.dart'; 4 | import '../ui/app_colors.dart'; 5 | import '../ui/text_styles.dart'; 6 | 7 | class SearchBar extends StatelessWidget { 8 | const SearchBar({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Container( 13 | height: 54, 14 | width: MediaQuery.of(context).size.width, 15 | margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 4), 16 | child: CupertinoTextField( 17 | placeholder: 'Search', 18 | readOnly: true, 19 | style: AppTextStyle.bodytext1, 20 | placeholderStyle: AppTextStyle.bodytext1.copyWith(color: AppColors.textSecondaryColor), 21 | padding: const EdgeInsets.symmetric(horizontal: 20), 22 | prefix: const Padding( 23 | padding: EdgeInsets.only(left: 20), 24 | child: Icon( 25 | Iconsax.search_normal_1, 26 | color: AppColors.textSecondaryColor, 27 | ), 28 | ), 29 | decoration: BoxDecoration( 30 | color: Colors.white.withOpacity(0.1), 31 | borderRadius: BorderRadius.circular(16), 32 | ), 33 | ), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import audio_service 9 | import audio_session 10 | import cloud_firestore 11 | import firebase_core 12 | import firebase_messaging 13 | import flutter_local_notifications 14 | import just_audio 15 | import package_info_plus_macos 16 | import path_provider_macos 17 | import sqflite 18 | 19 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 20 | AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin")) 21 | AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) 22 | FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin")) 23 | FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) 24 | FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) 25 | FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) 26 | JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) 27 | FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) 28 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 29 | SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) 30 | } 31 | -------------------------------------------------------------------------------- /android/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "50945720064", 4 | "project_id": "sunday-suspense-14243", 5 | "storage_bucket": "sunday-suspense-14243.appspot.com" 6 | }, 7 | "client": [ 8 | { 9 | "client_info": { 10 | "mobilesdk_app_id": "1:50945720064:android:ca46877ec54f647cba3de5", 11 | "android_client_info": { 12 | "package_name": "com.example.sunday_suspense" 13 | } 14 | }, 15 | "oauth_client": [ 16 | { 17 | "client_id": "50945720064-69rfml9vv77g3ou0414gf048uugl4i9j.apps.googleusercontent.com", 18 | "client_type": 3 19 | } 20 | ], 21 | "api_key": [ 22 | { 23 | "current_key": "AIzaSyAnCIzi6qKv1LJKmKhqR1GLu6fVqYsvVwg" 24 | } 25 | ], 26 | "services": { 27 | "appinvite_service": { 28 | "other_platform_oauth_client": [ 29 | { 30 | "client_id": "50945720064-69rfml9vv77g3ou0414gf048uugl4i9j.apps.googleusercontent.com", 31 | "client_type": 3 32 | }, 33 | { 34 | "client_id": "50945720064-1a40ats3fthtkvi1p6in3ih89aj5dnkj.apps.googleusercontent.com", 35 | "client_type": 2, 36 | "ios_info": { 37 | "bundle_id": "com.example.sundaySuspense" 38 | } 39 | } 40 | ] 41 | } 42 | } 43 | } 44 | ], 45 | "configuration_version": "1" 46 | } -------------------------------------------------------------------------------- /lib/ui/app_colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppColors { 4 | static const Color primaryColor = Color(0xFF9a0e13); 5 | static const Color accentColor = Color(0xFF282828); 6 | static const Color backgroundColor = Color(0xFF111111); 7 | static const Color textPrimaryColor = Color(0xFFffffff); 8 | static const Color textSecondaryColor = Color(0xFF6A6969); 9 | static const Color primaryWhiteColor = Color(0xFFFFFFFF); 10 | static const Color errorColor = Color.fromARGB(255, 255, 150, 150); 11 | 12 | static MaterialColor createMaterialColor(Color color) { 13 | List strengths = [.05]; 14 | final swatch = {}; 15 | final int r = color.red, g = color.green, b = color.blue; 16 | 17 | for (int i = 1; i < 10; i++) { 18 | strengths.add(0.1 * i); 19 | } 20 | for (var strength in strengths) { 21 | final double ds = 0.5 - strength; 22 | swatch[(strength * 1000).round()] = Color.fromRGBO( 23 | r + ((ds < 0 ? r : (255 - r)) * ds).round(), 24 | g + ((ds < 0 ? g : (255 - g)) * ds).round(), 25 | b + ((ds < 0 ? b : (255 - b)) * ds).round(), 26 | 1, 27 | ); 28 | } 29 | return MaterialColor(color.value, swatch); 30 | } 31 | 32 | static ColorScheme lightScheme = const ColorScheme.light( 33 | primary: primaryColor, 34 | secondary: accentColor, 35 | ); 36 | 37 | static ColorScheme darkScheme = const ColorScheme.dark( 38 | primary: primaryColor, 39 | secondary: accentColor, 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | // import 'package:firebase_core/firebase_core.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_dotenv/flutter_dotenv.dart'; 4 | import 'package:get/get_navigation/src/root/get_material_app.dart'; 5 | import 'package:get_storage/get_storage.dart'; 6 | import 'package:sunday_suspense/services/notification_service.dart'; 7 | import 'screens/root.dart'; 8 | import 'ui/my_theme.dart'; 9 | import 'page_manager.dart'; 10 | import 'services/service_locator.dart'; 11 | 12 | void main() async { 13 | await GetStorage.init("prefs"); 14 | await GetStorage.init('HistoryBox'); 15 | await dotenv.load(fileName: ".env"); 16 | await setupServiceLocator(); 17 | runApp(const MyApp()); 18 | await fcmFunctions.initApp(); 19 | await fcmFunctions.iosWebPermission(); 20 | fcmFunctions.foreGroundMessageListener(); 21 | await fcmFunctions.subscripeToTopics("news"); 22 | } 23 | 24 | class MyApp extends StatefulWidget { 25 | const MyApp({super.key}); 26 | 27 | @override 28 | State createState() => _MyAppState(); 29 | } 30 | 31 | class _MyAppState extends State { 32 | @override 33 | void initState() { 34 | super.initState(); 35 | getIt().init(); 36 | } 37 | 38 | @override 39 | void dispose() { 40 | getIt().dispose(); 41 | super.dispose(); 42 | } 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | return GetMaterialApp( 47 | theme: MyTheme.darkTheme, 48 | home: const RootWidget(), 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: sunday_suspense 2 | description: A new Flutter project. 3 | publish_to: "none" # Remove this line if you wish to publish to pub.dev 4 | 5 | version: 1.0.5+6 6 | 7 | environment: 8 | sdk: ">=2.18.1 <3.0.0" 9 | 10 | dependencies: 11 | animated_icon_button: ^1.0.2 12 | audio_video_progress_bar: ^0.10.0 13 | cached_network_image: ^3.2.2 14 | cloud_firestore: ^3.4.7 15 | cupertino_icons: ^1.0.2 16 | date_time_format: ^2.0.1 17 | debounce_throttle: ^2.0.0 18 | dot_navigation_bar: ^1.0.1+4 19 | eva_icons_flutter: ^3.1.0 20 | firebase_core: ^1.22.0 21 | firebase_messaging: ^11.4.2 22 | flutter: 23 | sdk: flutter 24 | flutter_bounceable: ^1.0.3 25 | flutter_dotenv: ^5.0.2 26 | flutter_local_notifications: any 27 | get: ^4.6.5 28 | get_it: ^7.2.0 29 | get_storage: ^2.0.3 30 | google_fonts: ^3.0.1 31 | iconsax: ^0.0.8 32 | just_audio: ^0.9.28 33 | just_audio_background: ^0.0.1-beta.7 34 | loadmore: ^2.0.1 35 | lottie: ^1.4.3 36 | meilisearch: ^0.6.0 37 | octo_image: ^1.0.2 38 | package_info_plus: ^1.4.3+1 39 | solid_bottom_sheet: ^0.1.10 40 | youtube_explode_dart: ^1.12.2 41 | 42 | dev_dependencies: 43 | flutter_lints: ^2.0.0 44 | flutter_test: 45 | sdk: flutter 46 | icons_launcher: ^2.0.5 47 | 48 | icons_launcher: 49 | image_path: "assets/icon.png" 50 | platforms: 51 | android: 52 | enable: true 53 | ios: 54 | enable: true 55 | 56 | flutter: 57 | uses-material-design: true 58 | assets: 59 | - assets/ 60 | - assets/lottie/ 61 | - .env 62 | -------------------------------------------------------------------------------- /.github/workflows/prchecker.yml: -------------------------------------------------------------------------------- 1 | name: PR Check 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v1 14 | 15 | # Setup Java environment in order to build the Android app. 16 | - uses: actions/setup-java@v1 17 | with: 18 | java-version: "12.x" 19 | 20 | # Gradle cache for faster builds 21 | - uses: actions/cache@v2 22 | with: 23 | path: | 24 | ~/.gradle/caches 25 | ~/.gradle/wrapper 26 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} 27 | restore-keys: | 28 | ${{ runner.os }}-gradle- 29 | # Setup the flutter environment. 30 | - uses: subosito/flutter-action@v1 31 | with: 32 | channel: "stable" 33 | 34 | # Get flutter dependencies. 35 | - run: flutter pub get 36 | 37 | # Check for any formatting issues in the code. 38 | - run: flutter format --set-exit-if-changed . 39 | 40 | # Statically analyze the Dart code for any errors. 41 | - run: flutter analyze . 42 | 43 | # Run widget tests for our flutter project. 44 | # - run: flutter test 45 | 46 | # Build apk. 47 | - run: flutter build apk --release 48 | 49 | # Upload generated apk to the artifacts. 50 | - uses: actions/upload-artifact@v1 51 | with: 52 | name: app-release.apk 53 | path: build/app/outputs/apk/release/app-release.apk 54 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 8 | channel: stable 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 17 | base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 18 | - platform: android 19 | create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 20 | base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 21 | - platform: ios 22 | create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 23 | base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 24 | - platform: linux 25 | create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 26 | base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 27 | - platform: macos 28 | create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 29 | base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 30 | - platform: web 31 | create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 32 | base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 33 | - platform: windows 34 | create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 35 | base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Sunday Suspense 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | sunday_suspense 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | UIApplicationSupportsIndirectInputEvents 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | sunday_suspense 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /lib/ui/text_styles.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_fonts/google_fonts.dart'; 3 | 4 | import 'app_colors.dart'; 5 | 6 | class AppTextStyle { 7 | static String? get fontFamily => GoogleFonts.poppins().fontFamily; 8 | //static String? get fontFamily => GoogleFonts.yanoneKaffeesatz().fontFamily; 9 | 10 | // Google font 11 | static TextStyle get defaultFontStyle => GoogleFonts.poppins(); 12 | 13 | // if we need to change a style 14 | 15 | // Headline 1 16 | static TextStyle get headline1 => GoogleFonts.poppins( 17 | fontSize: 28.0, 18 | fontWeight: FontWeight.w700, 19 | color: AppColors.primaryColor, 20 | ); 21 | // Headline 2 22 | static TextStyle get headline2 => GoogleFonts.poppins( 23 | fontSize: 22.0, 24 | fontWeight: FontWeight.w600, 25 | ); 26 | // Headline 3 27 | static TextStyle get headline3 => GoogleFonts.poppins( 28 | fontSize: 18.0, 29 | fontWeight: FontWeight.w600, 30 | ); 31 | static TextStyle get subHeading => GoogleFonts.poppins(fontSize: 16, fontWeight: FontWeight.w600, height: 1.3); 32 | // Bodytext 1 33 | static TextStyle get bodytext1 => GoogleFonts.poppins( 34 | fontSize: 16.0, 35 | color: Colors.white, 36 | ); 37 | // Bodytext 2 38 | static TextStyle get bodytext2 => GoogleFonts.poppins( 39 | fontSize: 14.0, 40 | color: Colors.white60, 41 | ); 42 | // Caption 43 | static TextStyle get caption => 44 | GoogleFonts.poppins(fontSize: 12.0, fontWeight: FontWeight.w400, color: AppColors.textSecondaryColor); 45 | static TextStyle get regular16 => GoogleFonts.poppins( 46 | fontSize: 16.0, 47 | fontWeight: FontWeight.w600, 48 | ); 49 | static TextStyle get button => GoogleFonts.poppins( 50 | fontSize: 14.0, 51 | fontWeight: FontWeight.w600, 52 | ); 53 | static TextTheme get textTheme => TextTheme( 54 | headline1: headline1, 55 | headline2: headline2, 56 | headline3: headline3, 57 | bodyText1: bodytext1, 58 | caption: caption, 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /.github/workflows/db_backup.yml: -------------------------------------------------------------------------------- 1 | name: backup db 2 | 3 | on: 4 | schedule: 5 | - cron: "0 6 * * 1" # on 12:00 PM UTC+5:30 every Monday 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | with: 14 | token: ${{ secrets.PAT }} 15 | - name: checkout repo content 16 | uses: actions/checkout@v2 # checkout the repository content to github runner 17 | 18 | - name: setup python 19 | uses: actions/setup-python@v4 20 | with: 21 | python-version: "3.9" # install the python version needed 22 | 23 | - name: install python packages 24 | run: | 25 | cd ./server 26 | python -m pip install --upgrade pip 27 | pip install -r requirements.txt 28 | 29 | - name: execute py script # run main.py 30 | env: 31 | MASTER_KEY: ${{ secrets.MASTER_KEY }} 32 | FIREBASE_KEY: ${{ secrets.FIREBASE_KEY }} 33 | run: | 34 | cd ./server 35 | python checkup_db.py 36 | python backup.py 37 | 38 | - name: create orphan # create orphan branch without deleting server/sunday.json 39 | run: | 40 | git add server/sunday.json 41 | git -c user.name='github-actions[bot]' -c user.email='41898282+github-actions[bot]@users.noreply.github.com' commit -m "backup" --no-verify --signoff 42 | 43 | git checkout --orphan backup 44 | git rm -rf . 45 | git clean -df 46 | git checkout master server/sunday.json 47 | - name: configure git # configure git 48 | run: | 49 | git config --global user.name "github-actions[bot]" 50 | git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" 51 | 52 | - name: commit and push # commit and push the changes to the orphan branch 53 | run: | 54 | git add server/sunday.json 55 | git -c user.name='github-actions[bot]' -c user.email='41898282+github-actions[bot]@users.noreply.github.com' commit -m "backup" --no-verify --signoff 56 | git push origin backup --force 57 | -------------------------------------------------------------------------------- /lib/ui/my_theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'app_colors.dart'; 4 | import 'text_styles.dart'; 5 | 6 | class MyTheme { 7 | static ThemeData get ligthTheme { 8 | return ThemeData( 9 | primarySwatch: AppColors.createMaterialColor(AppColors.primaryColor), 10 | colorScheme: AppColors.lightScheme, 11 | fontFamily: AppTextStyle.fontFamily, 12 | textTheme: AppTextStyle.textTheme, 13 | cardTheme: const CardTheme( 14 | color: Colors.white, 15 | ), 16 | bottomNavigationBarTheme: BottomNavigationBarThemeData( 17 | unselectedItemColor: Colors.grey.shade500, 18 | selectedItemColor: AppColors.lightScheme.primary, 19 | ), 20 | inputDecorationTheme: InputDecorationTheme( 21 | border: OutlineInputBorder(borderRadius: BorderRadius.circular(16)), 22 | fillColor: Colors.grey.shade300, 23 | ), 24 | bottomSheetTheme: const BottomSheetThemeData(backgroundColor: Colors.transparent)); 25 | } 26 | 27 | // on DarkMode the Swatch parameter is not working 28 | // https://github.com/flutter/flutter/issues/19089 29 | static ThemeData get darkTheme { 30 | return ThemeData( 31 | primarySwatch: AppColors.createMaterialColor(AppColors.primaryColor), 32 | colorScheme: AppColors.darkScheme, 33 | toggleableActiveColor: AppColors.darkScheme.secondary, 34 | // this can all be copied, waiting for verification 35 | fontFamily: AppTextStyle.fontFamily, 36 | textTheme: AppTextStyle.textTheme.copyWith( 37 | bodyText1: AppTextStyle.bodytext1.copyWith( 38 | color: Colors.white70, 39 | ), 40 | ), 41 | bottomNavigationBarTheme: BottomNavigationBarThemeData( 42 | unselectedItemColor: Colors.grey.shade400, 43 | selectedItemColor: AppColors.lightScheme.primary, 44 | ), 45 | // copy from ligthTheme 46 | inputDecorationTheme: ligthTheme.inputDecorationTheme, 47 | bottomSheetTheme: const BottomSheetThemeData(backgroundColor: Colors.transparent), 48 | bottomAppBarTheme: const BottomAppBarTheme(color: Colors.transparent), 49 | cardColor: AppColors.accentColor, 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /server/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import load_dotenv 3 | import build_new_item 4 | import requests 5 | import json 6 | import meilisearch 7 | 8 | 9 | load_dotenv() 10 | meiliClient = meilisearch.Client( 11 | "https://meilisearch-on-koyeb-imsudip.koyeb.app/", os.getenv('MASTER_KEY')) 12 | firebase_key = os.getenv('FIREBASE_KEY') 13 | 14 | 15 | def check_item_exists(video_id) -> bool: 16 | index = meiliClient.get_index('sunday') 17 | search = index.search(" ", {"filter": f"video_id = {video_id}"}) 18 | if search['estimatedTotalHits'] > 0: 19 | return True 20 | else: 21 | return False 22 | 23 | 24 | def send_notification(song_data): 25 | print('Sending notification...') 26 | url = 'https://fcm.googleapis.com/fcm/send' 27 | not_data = { 28 | "to": "/topics/news", 29 | "notification": { 30 | "title": song_data["title"], 31 | "body": "Listen to this week's Sunday Suspense! 😱", 32 | }, 33 | } 34 | headers = {"Content-Type": "application/json", 35 | "Authorization": firebase_key} 36 | response = requests.post(url, data=json.dumps(not_data), headers=headers) 37 | print(response.text) 38 | 39 | 40 | def update_db(): 41 | playlist_url = 'https://api.rss2json.com/v1/api.json?rss_url=https%3A%2F%2Fwww.youtube.com%2Ffeeds%2Fvideos.xml%3Fplaylist_id%3DPLq71IJk8mCV4-DqsZ7W6zRS7uzKOmmT8j' 42 | response = requests.get(playlist_url) 43 | data = response.json() 44 | count = 0 45 | new_items = [] 46 | for item in data['items']: 47 | video_id = item['link'].split('=')[1] 48 | link = 'https://www.youtube.com/watch?v=' + video_id 49 | if not check_item_exists(video_id): 50 | print('New item found!') 51 | print('Building new item...') 52 | new_item = build_new_item.getVideoDataFromLink(link) 53 | new_items.append(new_item) 54 | count += 1 55 | 56 | print(f'{count} new items added!') 57 | print(new_items) 58 | if count > 0: 59 | index = meiliClient.get_index('sunday') 60 | index.add_documents(new_items) 61 | send_notification(new_items[0]) 62 | return 63 | 64 | 65 | if __name__ == "__main__": 66 | update_db() 67 | 68 | # cron job 69 | # every 4 hours on every sunday 70 | # 0 */4 * * 0 python3 71 | -------------------------------------------------------------------------------- /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/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | apply plugin: 'com.google.gms.google-services' 28 | 29 | android { 30 | compileSdkVersion 33 31 | ndkVersion flutter.ndkVersion 32 | 33 | compileOptions { 34 | sourceCompatibility JavaVersion.VERSION_1_8 35 | targetCompatibility JavaVersion.VERSION_1_8 36 | } 37 | 38 | kotlinOptions { 39 | jvmTarget = '1.8' 40 | } 41 | 42 | sourceSets { 43 | main.java.srcDirs += 'src/main/kotlin' 44 | } 45 | 46 | defaultConfig { 47 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 48 | applicationId "com.example.sunday_suspense" 49 | // You can update the following values to match your application needs. 50 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. 51 | minSdkVersion 19 52 | targetSdkVersion flutter.targetSdkVersion 53 | versionCode flutterVersionCode.toInteger() 54 | versionName flutterVersionName 55 | multiDexEnabled true 56 | } 57 | 58 | buildTypes { 59 | release { 60 | // TODO: Add your own signing config for the release build. 61 | // Signing with the debug keys for now, so `flutter run --release` works. 62 | signingConfig signingConfigs.debug 63 | } 64 | } 65 | } 66 | 67 | flutter { 68 | source '../..' 69 | } 70 | 71 | dependencies { 72 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 73 | } 74 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 11 | 19 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | 37 | 38 | 41 | 42 | 43 | 44 | 45 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /lib/screens/historypage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get_storage/get_storage.dart'; 3 | import '../services/database_service.dart'; 4 | import '../ui/text_styles.dart'; 5 | import '../widgets/song_viewers.dart'; 6 | 7 | import '../ui/app_colors.dart'; 8 | 9 | class HistoryPageScreen extends StatefulWidget { 10 | const HistoryPageScreen({super.key}); 11 | 12 | @override 13 | State createState() => _HistoryPageScreenState(); 14 | } 15 | 16 | class _HistoryPageScreenState extends State { 17 | List history = []; 18 | 19 | final box = GetStorage('HistoryBox'); 20 | 21 | @override 22 | void initState() { 23 | box.listen(() { 24 | if (mounted) { 25 | setState(() {}); 26 | if (history.length != box.getKeys().length) { 27 | DatabaseService.getAudioFromList(box.getKeys().map((e) => e.toString()).toList()).then((value) { 28 | setState(() { 29 | history = value; 30 | }); 31 | }); 32 | } 33 | } 34 | }); 35 | DatabaseService.getAudioFromList(box.getKeys().map((e) => e.toString()).toList()).then((value) { 36 | setState(() { 37 | history = value; 38 | }); 39 | }); 40 | // print(box.getKeys()); 41 | super.initState(); 42 | } 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | return Scaffold( 47 | backgroundColor: AppColors.backgroundColor, 48 | body: SafeArea( 49 | child: Padding( 50 | padding: const EdgeInsets.symmetric(horizontal: 20), 51 | child: Column( 52 | crossAxisAlignment: CrossAxisAlignment.center, 53 | children: [ 54 | const SizedBox(height: 20), 55 | Text( 56 | "History", 57 | style: AppTextStyle.headline2, 58 | ), 59 | const SizedBox(height: 20), 60 | Expanded( 61 | child: ListView.builder( 62 | itemBuilder: (context, index) { 63 | return MinimalVerticalListCard( 64 | songs: history, 65 | index: index, 66 | history: Duration( 67 | seconds: box.read(history[index]['video_id']), 68 | ), 69 | ); 70 | }, 71 | itemCount: history.length, 72 | )), 73 | ], 74 | ), 75 | ), 76 | ), 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/screens/root.dart: -------------------------------------------------------------------------------- 1 | import 'package:dot_navigation_bar/dot_navigation_bar.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:iconsax/iconsax.dart'; 4 | import 'historypage.dart'; 5 | import 'categorypage.dart'; 6 | import 'homepage.dart'; 7 | import '../ui/app_colors.dart'; 8 | import '../widgets/art_glass_overlay.dart'; 9 | 10 | import '../widgets/miniplayer.dart'; 11 | 12 | class RootWidget extends StatefulWidget { 13 | const RootWidget({super.key}); 14 | 15 | @override 16 | State createState() => _RootWidgetState(); 17 | } 18 | 19 | class _RootWidgetState extends State { 20 | int _selectedIndex = 0; 21 | final List _widgetOptions = [ 22 | const HomePageScreen(), 23 | const CategoryPageScreen(), 24 | const HistoryPageScreen() 25 | ]; 26 | @override 27 | Widget build(BuildContext context) { 28 | return Scaffold( 29 | body: IndexedStack( 30 | index: _selectedIndex, 31 | children: _widgetOptions, 32 | ), 33 | backgroundColor: AppColors.backgroundColor, 34 | bottomNavigationBar: ArtGlassSmall( 35 | child: Column( 36 | mainAxisSize: MainAxisSize.min, 37 | children: [ 38 | const MiniPlayer(), 39 | DotNavigationBar( 40 | currentIndex: _selectedIndex, 41 | onTap: (index) => setState(() { 42 | _selectedIndex = index; 43 | }), 44 | marginR: const EdgeInsets.symmetric(vertical: 8), 45 | paddingR: EdgeInsets.symmetric(horizontal: MediaQuery.of(context).size.width * 0.2), 46 | // enableFloatingNavBar: false, 47 | backgroundColor: Colors.transparent, 48 | items: [ 49 | DotNavigationBarItem( 50 | icon: const Icon(Iconsax.home), 51 | selectedColor: AppColors.primaryWhiteColor, 52 | unselectedColor: AppColors.textSecondaryColor, 53 | ), 54 | DotNavigationBarItem( 55 | icon: const Icon(Iconsax.category), 56 | selectedColor: AppColors.primaryWhiteColor, 57 | unselectedColor: AppColors.textSecondaryColor, 58 | ), 59 | DotNavigationBarItem( 60 | icon: const Icon(Iconsax.save_2), 61 | selectedColor: AppColors.primaryWhiteColor, 62 | unselectedColor: AppColors.textSecondaryColor, 63 | ), 64 | ], 65 | ), 66 | ], 67 | ), 68 | ), 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/widgets/miniplayer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | import '../ui/text_styles.dart'; 5 | import 'player_parts.dart'; 6 | 7 | import '../page_manager.dart'; 8 | import '../screens/player.dart'; 9 | import '../services/service_locator.dart'; 10 | 11 | class MiniPlayer extends StatelessWidget { 12 | const MiniPlayer({super.key}); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | final pageManager = getIt(); 17 | return ValueListenableBuilder( 18 | valueListenable: pageManager.currentSongTitleNotifier, 19 | builder: (_, title, __) { 20 | // print('title: $title'); 21 | if (title == '') { 22 | return Container( 23 | height: 0, 24 | ); 25 | } else { 26 | return GestureDetector( 27 | onTap: () { 28 | Get.to(() => PlayerScreen()); 29 | }, 30 | onVerticalDragStart: (details) { 31 | Get.to(() => PlayerScreen()); 32 | }, 33 | child: ClipRRect( 34 | borderRadius: const BorderRadius.vertical(top: Radius.circular(12)), 35 | child: SizedBox( 36 | height: 68, 37 | // color: AppColors.accentColor, 38 | child: Column( 39 | children: [ 40 | const SizedBox(height: 2), 41 | const MinimalAudioProgressBar(), 42 | // const SizedBox(height: 10), 43 | Expanded( 44 | child: Row( 45 | crossAxisAlignment: CrossAxisAlignment.center, 46 | children: [ 47 | const SizedBox( 48 | width: 16, 49 | ), 50 | ClipRRect( 51 | borderRadius: BorderRadius.circular(12), 52 | child: SizedBox(height: 60, child: const CurrentSongArt(width: 60)), 53 | ), 54 | const SizedBox( 55 | width: 16, 56 | ), 57 | Expanded( 58 | child: Text( 59 | title, 60 | style: AppTextStyle.bodytext1.copyWith(fontSize: 14), 61 | maxLines: 2, 62 | )), 63 | const SizedBox( 64 | width: 10, 65 | ), 66 | const AudioControlButtons(), 67 | const SizedBox( 68 | width: 16, 69 | ), 70 | ], 71 | )), 72 | // const SizedBox(height: 10), 73 | ], 74 | )), 75 | ), 76 | ); 77 | } 78 | }, 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "filename": "Icon-App-20x20@2x.png", 5 | "idiom": "iphone", 6 | "scale": "2x", 7 | "size": "20x20" 8 | }, 9 | { 10 | "filename": "Icon-App-20x20@3x.png", 11 | "idiom": "iphone", 12 | "scale": "3x", 13 | "size": "20x20" 14 | }, 15 | { 16 | "filename": "Icon-App-29x29@1x.png", 17 | "idiom": "iphone", 18 | "scale": "1x", 19 | "size": "29x29" 20 | }, 21 | { 22 | "filename": "Icon-App-29x29@2x.png", 23 | "idiom": "iphone", 24 | "scale": "2x", 25 | "size": "29x29" 26 | }, 27 | { 28 | "filename": "Icon-App-29x29@3x.png", 29 | "idiom": "iphone", 30 | "scale": "3x", 31 | "size": "29x29" 32 | }, 33 | { 34 | "filename": "Icon-App-40x40@2x.png", 35 | "idiom": "iphone", 36 | "scale": "2x", 37 | "size": "40x40" 38 | }, 39 | { 40 | "filename": "Icon-App-40x40@3x.png", 41 | "idiom": "iphone", 42 | "scale": "3x", 43 | "size": "40x40" 44 | }, 45 | { 46 | "filename": "Icon-App-60x60@2x.png", 47 | "idiom": "iphone", 48 | "scale": "2x", 49 | "size": "60x60" 50 | }, 51 | { 52 | "filename": "Icon-App-60x60@3x.png", 53 | "idiom": "iphone", 54 | "scale": "3x", 55 | "size": "60x60" 56 | }, 57 | { 58 | "filename": "Icon-App-20x20@1x.png", 59 | "idiom": "ipad", 60 | "scale": "1x", 61 | "size": "20x20" 62 | }, 63 | { 64 | "filename": "Icon-App-20x20@2x.png", 65 | "idiom": "ipad", 66 | "scale": "2x", 67 | "size": "20x20" 68 | }, 69 | { 70 | "filename": "Icon-App-29x29@1x.png", 71 | "idiom": "ipad", 72 | "scale": "1x", 73 | "size": "29x29" 74 | }, 75 | { 76 | "filename": "Icon-App-29x29@2x.png", 77 | "idiom": "ipad", 78 | "scale": "2x", 79 | "size": "29x29" 80 | }, 81 | { 82 | "filename": "Icon-App-40x40@1x.png", 83 | "idiom": "ipad", 84 | "scale": "1x", 85 | "size": "40x40" 86 | }, 87 | { 88 | "filename": "Icon-App-40x40@2x.png", 89 | "idiom": "ipad", 90 | "scale": "2x", 91 | "size": "40x40" 92 | }, 93 | { 94 | "filename": "Icon-App-76x76@1x.png", 95 | "idiom": "ipad", 96 | "scale": "1x", 97 | "size": "76x76" 98 | }, 99 | { 100 | "filename": "Icon-App-76x76@2x.png", 101 | "idiom": "ipad", 102 | "scale": "2x", 103 | "size": "76x76" 104 | }, 105 | { 106 | "filename": "Icon-App-83.5x83.5@2x.png", 107 | "idiom": "ipad", 108 | "scale": "2x", 109 | "size": "83.5x83.5" 110 | }, 111 | { 112 | "filename": "Icon-App-1024x1024@1x.png", 113 | "idiom": "ios-marketing", 114 | "scale": "1x", 115 | "size": "1024x1024" 116 | } 117 | ], 118 | "info": { 119 | "author": "icons_launcher", 120 | "version": 1 121 | } 122 | } -------------------------------------------------------------------------------- /linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.10) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | 12 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 13 | # which isn't available in 3.10. 14 | function(list_prepend LIST_NAME PREFIX) 15 | set(NEW_LIST "") 16 | foreach(element ${${LIST_NAME}}) 17 | list(APPEND NEW_LIST "${PREFIX}${element}") 18 | endforeach(element) 19 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 20 | endfunction() 21 | 22 | # === Flutter Library === 23 | # System-level dependencies. 24 | find_package(PkgConfig REQUIRED) 25 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 26 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 27 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 28 | 29 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 30 | 31 | # Published to parent scope for install step. 32 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 33 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 34 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 35 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 36 | 37 | list(APPEND FLUTTER_LIBRARY_HEADERS 38 | "fl_basic_message_channel.h" 39 | "fl_binary_codec.h" 40 | "fl_binary_messenger.h" 41 | "fl_dart_project.h" 42 | "fl_engine.h" 43 | "fl_json_message_codec.h" 44 | "fl_json_method_codec.h" 45 | "fl_message_codec.h" 46 | "fl_method_call.h" 47 | "fl_method_channel.h" 48 | "fl_method_codec.h" 49 | "fl_method_response.h" 50 | "fl_plugin_registrar.h" 51 | "fl_plugin_registry.h" 52 | "fl_standard_message_codec.h" 53 | "fl_standard_method_codec.h" 54 | "fl_string_codec.h" 55 | "fl_value.h" 56 | "fl_view.h" 57 | "flutter_linux.h" 58 | ) 59 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 60 | add_library(flutter INTERFACE) 61 | target_include_directories(flutter INTERFACE 62 | "${EPHEMERAL_DIR}" 63 | ) 64 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 65 | target_link_libraries(flutter INTERFACE 66 | PkgConfig::GTK 67 | PkgConfig::GLIB 68 | PkgConfig::GIO 69 | ) 70 | add_dependencies(flutter flutter_assemble) 71 | 72 | # === Flutter tool backend === 73 | # _phony_ is a non-existent file to force this command to run every time, 74 | # since currently there's no way to get a full input/output list from the 75 | # flutter tool. 76 | add_custom_command( 77 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 78 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 79 | COMMAND ${CMAKE_COMMAND} -E env 80 | ${FLUTTER_TOOL_ENVIRONMENT} 81 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 82 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 83 | VERBATIM 84 | ) 85 | add_custom_target(flutter_assemble DEPENDS 86 | "${FLUTTER_LIBRARY}" 87 | ${FLUTTER_LIBRARY_HEADERS} 88 | ) 89 | -------------------------------------------------------------------------------- /lib/firebase_options.dart: -------------------------------------------------------------------------------- 1 | // File generated by FlutterFire CLI. 2 | // ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members 3 | import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; 4 | import 'package:flutter/foundation.dart' 5 | show defaultTargetPlatform, kIsWeb, TargetPlatform; 6 | 7 | /// Default [FirebaseOptions] for use with your Firebase apps. 8 | /// 9 | /// Example: 10 | /// ```dart 11 | /// import 'firebase_options.dart'; 12 | /// // ... 13 | /// await Firebase.initializeApp( 14 | /// options: DefaultFirebaseOptions.currentPlatform, 15 | /// ); 16 | /// ``` 17 | class DefaultFirebaseOptions { 18 | static FirebaseOptions get currentPlatform { 19 | if (kIsWeb) { 20 | return web; 21 | } 22 | switch (defaultTargetPlatform) { 23 | case TargetPlatform.android: 24 | return android; 25 | case TargetPlatform.iOS: 26 | return ios; 27 | case TargetPlatform.macOS: 28 | return macos; 29 | case TargetPlatform.windows: 30 | throw UnsupportedError( 31 | 'DefaultFirebaseOptions have not been configured for windows - ' 32 | 'you can reconfigure this by running the FlutterFire CLI again.', 33 | ); 34 | case TargetPlatform.linux: 35 | throw UnsupportedError( 36 | 'DefaultFirebaseOptions have not been configured for linux - ' 37 | 'you can reconfigure this by running the FlutterFire CLI again.', 38 | ); 39 | default: 40 | throw UnsupportedError( 41 | 'DefaultFirebaseOptions are not supported for this platform.', 42 | ); 43 | } 44 | } 45 | 46 | static const FirebaseOptions web = FirebaseOptions( 47 | apiKey: 'AIzaSyB-vkM-y5BI3hdLtgw_FWHPYWxrtjcG3V8', 48 | appId: '1:50945720064:web:48c3a1a486cdaa6cba3de5', 49 | messagingSenderId: '50945720064', 50 | projectId: 'sunday-suspense-14243', 51 | authDomain: 'sunday-suspense-14243.firebaseapp.com', 52 | storageBucket: 'sunday-suspense-14243.appspot.com', 53 | measurementId: 'G-CQDTDTPE5H', 54 | ); 55 | 56 | static const FirebaseOptions android = FirebaseOptions( 57 | apiKey: 'AIzaSyAnCIzi6qKv1LJKmKhqR1GLu6fVqYsvVwg', 58 | appId: '1:50945720064:android:ca46877ec54f647cba3de5', 59 | messagingSenderId: '50945720064', 60 | projectId: 'sunday-suspense-14243', 61 | storageBucket: 'sunday-suspense-14243.appspot.com', 62 | ); 63 | 64 | static const FirebaseOptions ios = FirebaseOptions( 65 | apiKey: 'AIzaSyBGsEz7Ab__YfMU_ejLtsSww7JP4nI8Qig', 66 | appId: '1:50945720064:ios:f5424ba3bfce633aba3de5', 67 | messagingSenderId: '50945720064', 68 | projectId: 'sunday-suspense-14243', 69 | storageBucket: 'sunday-suspense-14243.appspot.com', 70 | iosClientId: '50945720064-1a40ats3fthtkvi1p6in3ih89aj5dnkj.apps.googleusercontent.com', 71 | iosBundleId: 'com.example.sundaySuspense', 72 | ); 73 | 74 | static const FirebaseOptions macos = FirebaseOptions( 75 | apiKey: 'AIzaSyBGsEz7Ab__YfMU_ejLtsSww7JP4nI8Qig', 76 | appId: '1:50945720064:ios:f5424ba3bfce633aba3de5', 77 | messagingSenderId: '50945720064', 78 | projectId: 'sunday-suspense-14243', 79 | storageBucket: 'sunday-suspense-14243.appspot.com', 80 | iosClientId: '50945720064-1a40ats3fthtkvi1p6in3ih89aj5dnkj.apps.googleusercontent.com', 81 | iosBundleId: 'com.example.sundaySuspense', 82 | ); 83 | } 84 | -------------------------------------------------------------------------------- /lib/widgets/art_glass_overlay.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'dart:ui'; 3 | 4 | import 'package:cached_network_image/cached_network_image.dart'; 5 | import 'package:flutter/material.dart'; 6 | import '../ui/app_colors.dart'; 7 | 8 | import '../page_manager.dart'; 9 | import '../services/service_locator.dart'; 10 | 11 | class ArtGlass extends StatelessWidget { 12 | const ArtGlass( 13 | {super.key, required this.child, this.intensity = 0.0, this.backgroundColor = AppColors.backgroundColor}); 14 | final Widget child; 15 | final double intensity; 16 | final Color backgroundColor; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | final pageManager = getIt(); 21 | return ValueListenableBuilder( 22 | valueListenable: pageManager.currentSongImageNotifier, 23 | builder: (_, art, __) { 24 | if (art == "") { 25 | return Container( 26 | child: child, 27 | ); 28 | } else { 29 | return Stack( 30 | children: [ 31 | Container( 32 | color: backgroundColor, 33 | ), 34 | Positioned( 35 | top: -25, 36 | bottom: max(MediaQuery.of(context).size.height * 0.5, 60), 37 | child: CachedNetworkImage( 38 | imageUrl: art, 39 | width: MediaQuery.of(context).size.width, 40 | fit: BoxFit.cover, 41 | ), 42 | ), 43 | Container( 44 | color: backgroundColor.withOpacity(intensity), 45 | ), 46 | BackdropFilter(filter: ImageFilter.blur(sigmaX: 100, sigmaY: 100), child: child), 47 | ], 48 | ); 49 | } 50 | }, 51 | ); 52 | } 53 | } 54 | 55 | class ArtGlassSmall extends StatelessWidget { 56 | const ArtGlassSmall( 57 | {super.key, required this.child, this.intensity = 0.0, this.backgroundColor = AppColors.backgroundColor}); 58 | final Widget child; 59 | final double intensity; 60 | final Color backgroundColor; 61 | 62 | @override 63 | Widget build(BuildContext context) { 64 | final pageManager = getIt(); 65 | return ValueListenableBuilder( 66 | valueListenable: pageManager.currentSongImageNotifier, 67 | builder: (_, art, __) { 68 | if (art == "") { 69 | return SizedBox( 70 | height: 62, 71 | child: child, 72 | ); 73 | } else { 74 | return SizedBox( 75 | height: 130, 76 | child: Stack( 77 | children: [ 78 | Container( 79 | color: backgroundColor, 80 | ), 81 | Positioned( 82 | bottom: -25, 83 | top: 100, 84 | child: CachedNetworkImage( 85 | imageUrl: art, 86 | width: MediaQuery.of(context).size.width, 87 | fit: BoxFit.cover, 88 | ), 89 | ), 90 | Container( 91 | color: backgroundColor.withOpacity(intensity), 92 | ), 93 | BackdropFilter(filter: ImageFilter.blur(sigmaX: 100, sigmaY: 100), child: child), 94 | ], 95 | ), 96 | ); 97 | } 98 | }, 99 | ); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /server/build_new_item.py: -------------------------------------------------------------------------------- 1 | # { 2 | # "title": "The Cask of Amontillado - Edgar Allan Poe", 3 | # "video_id": "CziZzauAa5Y", 4 | # "date": "2022-09-11T00:00:00", 5 | # "url": "https://youtube.com/watch?v=CziZzauAa5Y", 6 | # "thumbnail": "https://i.ytimg.com/vi/CziZzauAa5Y/sddefault.jpg?v=631f040a", 7 | # "length": 2362, 8 | # "views": 507970, 9 | # "author": "Mirchi Bangla", 10 | # "description": "Mirchi Bangla presents Edgar Allan Poe's The Cask of Amontillado on Sunday Suspense\n\nDate of Broadcast - 11th September, 2022\n\nMontresor - Deep\nFortunato - Agni\n\nTranslated, Adapted and Directed by Pushpal\nProduction, Sound Design and Original Music - Pradyut\nPoster Design - Join the Dots\nExecutive Producer - Alto\n\nInterns\n\nArpan Chatterjee\nSaptanil Maji\nUtsa Dey\n\nEnjoy and stay connected with us!!\n\nSubscribe to us :\nhttp://bit.ly/SubscribeMirchiBangla\n\nLike us on Facebook\nhttps://www.facebook.com/MirchiBangla/\n\nFollow us on Instagram\nhttps://www.instagram.com/mirchibangla/", 11 | # "blurhash": "V34UvhD%EMyDIAs.aeWBofn$0K%g%1Mw%MM|ozt6WAbc", 12 | # "tags": [ 13 | # "horror" 14 | # ], 15 | # "timestamp": 1662834600 16 | # }, 17 | 18 | import requests 19 | import json 20 | import pytube 21 | import datetime 22 | import blurhash 23 | import io 24 | import checkplaylist 25 | 26 | 27 | def getBlurHashFromLink(url): 28 | img = requests.get(url).content 29 | img_io = io.BytesIO(img) 30 | blurString = blurhash.encode(img_io, 5, 4) 31 | return blurString 32 | 33 | 34 | def clean_up_title(title) -> str: 35 | wildcards = [ 36 | "Mirchi 98.3", 37 | "Radio Mirchi", 38 | "Radio Mirchi 98.3", 39 | "Radio Mirchi 98.3 FM", 40 | "Sunday Suspense", 41 | "#SundaySuspense", 42 | "Mirchi Bangla", 43 | "Radio Mirchi Bangla", 44 | "#Sunday Suspense", 45 | "#Sunday Suspense", 46 | "98.3", 47 | "#SundayNonsense", 48 | ] 49 | # remove all invisible characters 50 | title = title.replace("\u200b", "") 51 | 52 | splits = title.split("|") 53 | # strip whitespaces 54 | splits = [x.strip() for x in splits] 55 | if len(splits) > 1: 56 | # check if any of the wildcards are present 57 | for wildcard in wildcards: 58 | if wildcard in splits: 59 | # remove the wildcard 60 | splits.remove(wildcard) 61 | # join the splits 62 | title = " - ".join(splits) 63 | # update the title 64 | return title 65 | else: 66 | sp2 = title.split("-") 67 | sp2 = [x.strip() for x in sp2] 68 | if len(sp2) > 1: 69 | for wildcard in wildcards: 70 | if wildcard in sp2: 71 | sp2.remove(wildcard) 72 | title = " - ".join(sp2) 73 | return title 74 | else: 75 | return title 76 | 77 | 78 | def getVideoDataFromLink(url): 79 | yt = pytube.YouTube(url) 80 | v = { 81 | "title": clean_up_title(yt.title), 82 | "video_id": yt.video_id, 83 | "date": yt.publish_date.isoformat(), 84 | "url": yt.watch_url, 85 | "thumbnail": yt.thumbnail_url, 86 | "length": yt.length, 87 | "views": yt.views, 88 | "author": yt.author, 89 | "description": yt.description, 90 | "blurhash": getBlurHashFromLink(yt.thumbnail_url), 91 | "tags": checkplaylist.check_playlist_inclusion(yt.video_id), 92 | "timestamp": int(datetime.datetime.timestamp(yt.publish_date)) 93 | } 94 | 95 | return v 96 | -------------------------------------------------------------------------------- /lib/screens/show_all_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:animated_icon_button/animated_icon_button.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:loadmore/loadmore.dart'; 4 | import '../ui/app_colors.dart'; 5 | import '../widgets/player_parts.dart'; 6 | import '../widgets/song_viewers.dart'; 7 | 8 | class ShowAllPage extends StatefulWidget { 9 | const ShowAllPage({super.key, required this.title, required this.future}); 10 | final String title; 11 | // future func 12 | final Future Function(int page) future; 13 | 14 | @override 15 | State createState() => _ShowAllPageState(); 16 | } 17 | 18 | class _ShowAllPageState extends State { 19 | final List _songs = []; 20 | int _page = 1; 21 | bool _hasMore = true; 22 | 23 | bool isExpanded = true; 24 | 25 | Future _loadMore() async { 26 | print("loading more"); 27 | if (!_hasMore) return false; 28 | var songs = await widget.future(_page); 29 | if (songs.length < 10) { 30 | _hasMore = false; 31 | } 32 | setState(() { 33 | _songs.addAll(songs); 34 | _page++; 35 | }); 36 | return true; 37 | } 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | return Scaffold( 42 | appBar: AppBar( 43 | title: Text(widget.title), 44 | backgroundColor: AppColors.backgroundColor, 45 | elevation: 0, 46 | actions: [ 47 | AnimatedIconButton( 48 | onPressed: () { 49 | setState(() { 50 | isExpanded = !isExpanded; 51 | }); 52 | }, 53 | icons: const [ 54 | AnimatedIconItem( 55 | icon: Icon(Icons.unfold_less_rounded), 56 | ), 57 | AnimatedIconItem( 58 | icon: Icon(Icons.unfold_more_rounded), 59 | ), 60 | ], 61 | ), 62 | ], 63 | ), 64 | backgroundColor: AppColors.backgroundColor, 65 | body: LoadMore( 66 | onLoadMore: _loadMore, 67 | whenEmptyLoad: true, 68 | isFinish: !_hasMore, 69 | delegate: MyLoadMoreDelegate(), 70 | child: ListView.builder( 71 | scrollDirection: Axis.vertical, 72 | itemCount: _songs.length, 73 | itemBuilder: (context, index) { 74 | if (isExpanded) { 75 | return VerticalListCard( 76 | songs: _songs, 77 | index: index, 78 | ); 79 | } else { 80 | return Padding( 81 | padding: const EdgeInsets.symmetric(horizontal: 20), 82 | child: MinimalVerticalListCard(songs: _songs, index: index), 83 | ); 84 | } 85 | }, 86 | ), 87 | ), 88 | ); 89 | } 90 | } 91 | 92 | class MyLoadMoreDelegate extends LoadMoreDelegate { 93 | @override 94 | Widget buildChild(LoadMoreStatus status, {LoadMoreTextBuilder builder = DefaultLoadMoreTextBuilder.english}) { 95 | switch (status) { 96 | case LoadMoreStatus.loading: 97 | return const Center( 98 | child: LoadingAnimation(), 99 | ); 100 | case LoadMoreStatus.nomore: 101 | return const Center( 102 | child: Text('No more'), 103 | ); 104 | 105 | case LoadMoreStatus.idle: 106 | return const Center( 107 | child: Text('Idle'), 108 | ); 109 | default: 110 | return const Center( 111 | child: Text('Default'), 112 | ); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to this repository 2 | 3 | ## Getting started 4 | 5 | Before you begin: 6 | - This project is powered by Flutter. Make sure you have the latest version of Flutter. 7 | - Have you read the [code of conduct](CODE_OF_CONDUCT.md)? 8 | - Make sure to check if an [issue exists](https://github.com/imsudip/sunday_suspense/issues) already & see if the issue is assigned to anyone or not. 9 | - If no one is assigned you can start working on the issue. 10 | - Make sure to leave a comment stating that you are working on the issue. 11 | 12 | ### Issue already assigned to someone? Ask before starting 13 | 14 | If the issue is already assigned to someone, leave a comment asking how you can contribute. Please do not start working on your own without confirmation. 15 | 16 | ### Don't see your issue? Open one 17 | 18 | If you spot something new, open an issue using a [template](https://github.com/imsudip/sunday_suspense/issues/new/choose). We'll use the issue to have a conversation about the problem you want to fix. 19 | 20 | ### Ready to make a change? Fork the repo 21 | 22 | Fork using GitHub Desktop: 23 | 24 | - [Getting started with GitHub Desktop](https://docs.github.com/en/desktop/installing-and-configuring-github-desktop/getting-started-with-github-desktop) will guide you through setting up Desktop. 25 | - Once Desktop is set up, you can use it to [fork the repo](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/cloning-and-forking-repositories-from-github-desktop)! 26 | 27 | Fork using the command line: 28 | 29 | - [Fork the repo](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository) so that you can make your changes without affecting the original project until you're ready to merge them. 30 | 31 | Fork with [GitHub Codespaces](https://github.com/features/codespaces): 32 | 33 | - [Fork, edit, and preview](https://docs.github.com/en/free-pro-team@latest/github/developing-online-with-codespaces/creating-a-codespace) using [GitHub Codespaces](https://github.com/features/codespaces) without having to install and run the project locally. 34 | 35 | ### Make your update: 36 | Make your changes to the file(s) you'd like to update. 37 | 38 | ### Open a pull request 39 | When you're done making changes and you'd like to propose them for review, open your PR (pull request). You can use the GitHub user interface for some small changes, like fixing a typo or updating a readme. You can also fork the repo and then clone it locally, to view changes and run your tests on your machine. 40 | 41 | ### Submit your PR & get it reviewed 42 | - Once you submit your PR, others from the Docs community will review it with you. The first thing you're going to want to do is a [self review](#self-review). 43 | - After that, we may have questions, check back on your PR to keep up with the conversation. 44 | - We may ask for changes to be made before a PR can be merged. You can make any other changes in your fork, then commit them to your branch. 45 | 46 | ### Your PR is merged! 47 | Congratulations! The whole community thanks you. :sparkles: 48 | 49 | Once your PR is merged, you will be proudly listed as a contributor in the [contributor chart](https://github.com/imsudip/sunday_suspense/graphs/contributors). 50 | 51 | ### Self review 52 | You should always review your own PR first. 53 | 54 | For content changes, make sure that you: 55 | - [ ] Confirm the changes doesn't break anything else. 56 | - [ ] Compare your pull request's source changes to staging to confirm that the output matches the source and that everything is rendering as expected. This helps spot issues like typos, content that doesn't follow the style guide, or content that isn't rendering due to versioning problems. 57 | - [ ] Review the content for technical accuracy. 58 | - [ ] Copy-edit the changes for grammar or spelling mistakes. 59 | -------------------------------------------------------------------------------- /linux/my_application.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | #include 4 | #ifdef GDK_WINDOWING_X11 5 | #include 6 | #endif 7 | 8 | #include "flutter/generated_plugin_registrant.h" 9 | 10 | struct _MyApplication { 11 | GtkApplication parent_instance; 12 | char** dart_entrypoint_arguments; 13 | }; 14 | 15 | G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) 16 | 17 | // Implements GApplication::activate. 18 | static void my_application_activate(GApplication* application) { 19 | MyApplication* self = MY_APPLICATION(application); 20 | GtkWindow* window = 21 | GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); 22 | 23 | // Use a header bar when running in GNOME as this is the common style used 24 | // by applications and is the setup most users will be using (e.g. Ubuntu 25 | // desktop). 26 | // If running on X and not using GNOME then just use a traditional title bar 27 | // in case the window manager does more exotic layout, e.g. tiling. 28 | // If running on Wayland assume the header bar will work (may need changing 29 | // if future cases occur). 30 | gboolean use_header_bar = TRUE; 31 | #ifdef GDK_WINDOWING_X11 32 | GdkScreen* screen = gtk_window_get_screen(window); 33 | if (GDK_IS_X11_SCREEN(screen)) { 34 | const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); 35 | if (g_strcmp0(wm_name, "GNOME Shell") != 0) { 36 | use_header_bar = FALSE; 37 | } 38 | } 39 | #endif 40 | if (use_header_bar) { 41 | GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); 42 | gtk_widget_show(GTK_WIDGET(header_bar)); 43 | gtk_header_bar_set_title(header_bar, "sunday_suspense"); 44 | gtk_header_bar_set_show_close_button(header_bar, TRUE); 45 | gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); 46 | } else { 47 | gtk_window_set_title(window, "sunday_suspense"); 48 | } 49 | 50 | gtk_window_set_default_size(window, 1280, 720); 51 | gtk_widget_show(GTK_WIDGET(window)); 52 | 53 | g_autoptr(FlDartProject) project = fl_dart_project_new(); 54 | fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); 55 | 56 | FlView* view = fl_view_new(project); 57 | gtk_widget_show(GTK_WIDGET(view)); 58 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); 59 | 60 | fl_register_plugins(FL_PLUGIN_REGISTRY(view)); 61 | 62 | gtk_widget_grab_focus(GTK_WIDGET(view)); 63 | } 64 | 65 | // Implements GApplication::local_command_line. 66 | static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { 67 | MyApplication* self = MY_APPLICATION(application); 68 | // Strip out the first argument as it is the binary name. 69 | self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); 70 | 71 | g_autoptr(GError) error = nullptr; 72 | if (!g_application_register(application, nullptr, &error)) { 73 | g_warning("Failed to register: %s", error->message); 74 | *exit_status = 1; 75 | return TRUE; 76 | } 77 | 78 | g_application_activate(application); 79 | *exit_status = 0; 80 | 81 | return TRUE; 82 | } 83 | 84 | // Implements GObject::dispose. 85 | static void my_application_dispose(GObject* object) { 86 | MyApplication* self = MY_APPLICATION(object); 87 | g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); 88 | G_OBJECT_CLASS(my_application_parent_class)->dispose(object); 89 | } 90 | 91 | static void my_application_class_init(MyApplicationClass* klass) { 92 | G_APPLICATION_CLASS(klass)->activate = my_application_activate; 93 | G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; 94 | G_OBJECT_CLASS(klass)->dispose = my_application_dispose; 95 | } 96 | 97 | static void my_application_init(MyApplication* self) {} 98 | 99 | MyApplication* my_application_new() { 100 | return MY_APPLICATION(g_object_new(my_application_get_type(), 101 | "application-id", APPLICATION_ID, 102 | "flags", G_APPLICATION_NON_UNIQUE, 103 | nullptr)); 104 | } 105 | -------------------------------------------------------------------------------- /lib/services/database_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'package:meilisearch/meilisearch.dart'; 3 | import 'package:youtube_explode_dart/youtube_explode_dart.dart'; 4 | import '../page_manager.dart'; 5 | import '../widgets/song_viewers.dart'; 6 | import 'service_locator.dart'; 7 | 8 | class DatabaseService { 9 | DatabaseService(); 10 | static buildQualityStore(List data) { 11 | final pageManager = getIt(); 12 | var qualityStore = {}; 13 | if (data.length >= 4) { 14 | qualityStore['Low'] = data[0]['url']; 15 | qualityStore['Medium'] = data[1]['url']; 16 | qualityStore['High'] = data[2]['url']; 17 | qualityStore['HD'] = data[3]['url']; 18 | } else if (data.length == 3) { 19 | qualityStore['Low'] = data[0]['url']; 20 | qualityStore['Medium'] = data[1]['url']; 21 | qualityStore['High'] = data[2]['url']; 22 | } else if (data.length == 2) { 23 | qualityStore['Low'] = data[0]['url']; 24 | qualityStore['Medium'] = data[1]['url']; 25 | } else if (data.length == 1) { 26 | qualityStore['Low'] = data[0]['url']; 27 | } 28 | pageManager.audioQualityStoreNotifier.value = qualityStore; 29 | var cuurentQuality = pageManager.audioQualityNotifier.value; 30 | if (qualityStore.containsKey(cuurentQuality)) { 31 | return qualityStore[cuurentQuality]; 32 | } else { 33 | return qualityStore.values.last; 34 | } 35 | } 36 | 37 | static Future getStreamLink(String videoUrl) async { 38 | loadingDialog(); 39 | var yt = YoutubeExplode(); 40 | var videoId = videoUrl.split("v=")[1]; 41 | var manifest = await yt.videos.streamsClient.getManifest(videoId); 42 | List> audioUrls = 43 | manifest.audioOnly.sortByBitrate().map((e) => {'url': e.url.toString()}).toList(); 44 | var audio = buildQualityStore(audioUrls); 45 | return audio; 46 | } 47 | 48 | /// Get Trending Audios from database sorted by date 49 | static Future>> getTrending({int page = 1, int perPage = 20}) async { 50 | final client = getIt(); 51 | final index = client.index('sunday'); 52 | final res = await index.search(' ', limit: perPage, offset: (page - 1) * perPage, sort: ['timestamp:desc']); 53 | return res.hits ?? []; 54 | } 55 | 56 | /// Search for audios in database 57 | static Future>> searchAudio(String tag, {int page = 1, int perPage = 20}) async { 58 | final client = getIt(); 59 | final index = client.index('sunday'); 60 | final res = await index.search(tag, limit: perPage, offset: (page - 1) * perPage, sort: ['timestamp:desc']); 61 | return res.hits ?? []; 62 | } 63 | 64 | /// Get audios from a particular category like adventure, crime, etc 65 | static Future>> getAudioByTag(String tag, {int page = 1, int perPage = 20}) async { 66 | final client = getIt(); 67 | final index = client.index('sunday'); 68 | final res = await index 69 | .search('', limit: perPage, offset: (page - 1) * perPage, sort: ['timestamp:desc'], filter: ['tags = $tag']); 70 | return res.hits ?? []; 71 | } 72 | 73 | /// Get audios from a List of ids 74 | static Future>> getAudioFromList( 75 | List idList, 76 | ) async { 77 | final client = getIt(); 78 | final index = client.index('sunday'); 79 | // remove all empty strings 80 | idList.removeWhere((element) => element.isEmpty); 81 | final res = await index.search('', sort: ['timestamp:desc'], filter: ['video_id IN [${idList.join(',')}]']); 82 | return res.hits ?? []; 83 | } 84 | 85 | /// Get recommended audios from a particular audio title 86 | static Future>> moreLikethis( 87 | String title, 88 | ) async { 89 | final client = getIt(); 90 | final index = client.index('sunday'); 91 | final res = await index.search(" ", limit: 1, sort: ['timestamp:desc']); 92 | var totalDocs = res.estimatedTotalHits ?? 0; 93 | var randomoffset = Random().nextInt(totalDocs); 94 | final res2 = await index.search(" ", limit: 20, offset: randomoffset, sort: ['timestamp:desc']); 95 | return res2.hits ?? []; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/services/database_service.txt: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:developer'; 3 | 4 | import 'package:http/http.dart' as http; 5 | import 'package:sunday_suspense/services/service_locator.dart'; 6 | 7 | import '../page_manager.dart'; 8 | import '../widgets/song_viewers.dart'; 9 | 10 | const String baseUrl = 'https://zk3rid.deta.dev'; 11 | 12 | class DatabaseService { 13 | DatabaseService(); 14 | 15 | static Future getStreamLink(String videoUrl) async { 16 | loadingDialog(); 17 | final url = "$baseUrl/getLink?url=$videoUrl"; 18 | var res = await http.get(Uri.parse(url)); 19 | if (res.statusCode != 200) { 20 | return ""; 21 | } 22 | var jdata = jsonDecode(res.body); 23 | var audio = buildQualityStore(jdata); 24 | return audio; 25 | } 26 | 27 | static Future>> getSongsUsingTag(String tag, 28 | {bool fuzzy = false, String mode = "or", int count = 10, int page = 1}) async { 29 | final url = "$baseUrl/search?query=$tag&count=$count&page=$page"; 30 | var res = await http.get(Uri.parse(url)); 31 | var jdata = jsonDecode(res.body); 32 | return jdata['results'].map>((e) => e as Map).toList(); 33 | } 34 | 35 | static Future>> getSongsFromSearch(String tag, {int count = 10, int page = 1}) async { 36 | final url = "$baseUrl/search?query=$tag&count=$count&page=$page&fuzzy=true&mode=or"; 37 | var res = await http.get(Uri.parse(url)); 38 | var jdata = jsonDecode(res.body); 39 | return jdata['results'].map>((e) => e as Map).toList(); 40 | } 41 | 42 | static Future>> getLatestSongs({int count = 10, int page = 1}) async { 43 | final url = "$baseUrl/getAllSongs?count=$count&page=$page"; 44 | var res = await http.get(Uri.parse(url)); 45 | var jdata = jsonDecode(res.body); 46 | return jdata['results'].map>((e) => e as Map).toList(); 47 | } 48 | 49 | static Future>> moreLikeThis(String videoId) async { 50 | final url = "$baseUrl/moreLikeThis?videoId=$videoId"; 51 | var res = await http.get(Uri.parse(url)); 52 | var jdata = jsonDecode(res.body); 53 | return jdata['results'].map>((e) => e as Map).toList(); 54 | } 55 | 56 | static Future>> getSongByCategory(String category, {int count = 10, int page = 1}) async { 57 | final url = "$baseUrl/getCategorySongs?category=$category&count=$count&page=$page"; 58 | var res = await http.get(Uri.parse(url)); 59 | var jdata = jsonDecode(res.body); 60 | return jdata['results'].map>((e) => e as Map).toList(); 61 | } 62 | 63 | static Future>> getSongFromListt(List idList) async { 64 | idList.remove(""); 65 | var lString = jsonEncode(idList); 66 | final url = "$baseUrl/getSongsFromList?list=$lString"; 67 | var res = await http.get(Uri.parse(url)); 68 | var jdata = jsonDecode(res.body); 69 | return jdata.map>((e) => e as Map).toList(); 70 | } 71 | 72 | static buildQualityStore(List data) { 73 | final pageManager = getIt(); 74 | var qualityStore = {}; 75 | if (data.length == 4) { 76 | qualityStore['low'] = data[0]['url']; 77 | qualityStore['medium'] = data[1]['url']; 78 | qualityStore['high'] = data[2]['url']; 79 | qualityStore['hd'] = data[3]['url']; 80 | } else if (data.length == 3) { 81 | qualityStore['low'] = data[0]['url']; 82 | qualityStore['medium'] = data[1]['url']; 83 | qualityStore['high'] = data[2]['url']; 84 | } else if (data.length == 2) { 85 | qualityStore['low'] = data[0]['url']; 86 | qualityStore['medium'] = data[1]['url']; 87 | } else if (data.length == 1) { 88 | qualityStore['low'] = data[0]['url']; 89 | } 90 | pageManager.audioQualityStoreNotifier.value = qualityStore; 91 | log("Quality Store: ${qualityStore.keys}"); 92 | var cuurentQuality = pageManager.audioQualityNotifier.value; 93 | if (qualityStore.containsKey(cuurentQuality)) { 94 | return qualityStore[cuurentQuality]; 95 | } else { 96 | return qualityStore.values.first; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/screens/searchScreen.dart: -------------------------------------------------------------------------------- 1 | import 'package:debounce_throttle/debounce_throttle.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:iconsax/iconsax.dart'; 5 | import 'package:loadmore/loadmore.dart'; 6 | 7 | import '../services/database_service.dart'; 8 | import '../ui/app_colors.dart'; 9 | import '../ui/text_styles.dart'; 10 | import '../widgets/song_viewers.dart'; 11 | import 'show_all_page.dart'; 12 | 13 | class SearchScreen extends StatefulWidget { 14 | const SearchScreen({super.key}); 15 | 16 | @override 17 | State createState() => _SearchScreenState(); 18 | } 19 | 20 | class _SearchScreenState extends State { 21 | TextEditingController searchController = TextEditingController(); 22 | Duration debounceTime = const Duration(milliseconds: 300); 23 | String searchQuery = ''; 24 | 25 | List searchResults = []; 26 | int page = 2; 27 | 28 | void submitSearch(String query) async { 29 | print("searching..."); 30 | var results = await DatabaseService.searchAudio(query); 31 | setState(() { 32 | page = 2; 33 | searchResults = results; 34 | }); 35 | } 36 | 37 | bool _hasMore = true; 38 | 39 | Future _loadMore() async { 40 | print("loading more"); 41 | if (!_hasMore) return false; 42 | print(page); 43 | var songs = await DatabaseService.searchAudio(searchController.text, page: page); 44 | if (songs.length < 10) { 45 | _hasMore = false; 46 | } 47 | setState(() { 48 | searchResults.addAll(songs); 49 | page++; 50 | }); 51 | return true; 52 | } 53 | 54 | final debouncer = Debouncer(const Duration(milliseconds: 200), initialValue: ''); 55 | 56 | @override 57 | void initState() { 58 | // Run a search whenever the user pauses while typing. 59 | searchController.addListener(() => debouncer.value = searchController.text); 60 | debouncer.values.listen((search) => submitSearch(search)); 61 | super.initState(); 62 | } 63 | 64 | @override 65 | void dispose() { 66 | searchController.dispose(); 67 | super.dispose(); 68 | } 69 | 70 | @override 71 | Widget build(BuildContext context) { 72 | return Scaffold( 73 | backgroundColor: AppColors.backgroundColor, 74 | appBar: AppBar( 75 | backgroundColor: AppColors.backgroundColor, 76 | elevation: 0, 77 | title: Text( 78 | 'Search', 79 | style: AppTextStyle.subHeading, 80 | ), 81 | ), 82 | body: Column( 83 | children: [ 84 | Container( 85 | height: 54, 86 | width: MediaQuery.of(context).size.width, 87 | margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 4), 88 | child: CupertinoTextField( 89 | placeholder: 'Search', 90 | controller: searchController, 91 | style: AppTextStyle.bodytext1, 92 | placeholderStyle: AppTextStyle.bodytext1.copyWith(color: AppColors.textSecondaryColor), 93 | padding: const EdgeInsets.symmetric(horizontal: 20), 94 | prefix: const Padding( 95 | padding: EdgeInsets.only(left: 20), 96 | child: Icon( 97 | Iconsax.search_normal_1, 98 | color: AppColors.textSecondaryColor, 99 | ), 100 | ), 101 | decoration: BoxDecoration( 102 | color: Colors.white.withOpacity(0.1), 103 | borderRadius: BorderRadius.circular(16), 104 | ), 105 | ), 106 | ), 107 | Expanded( 108 | child: Padding( 109 | padding: const EdgeInsets.symmetric(horizontal: 20), 110 | child: LoadMore( 111 | onLoadMore: _loadMore, 112 | whenEmptyLoad: false, 113 | isFinish: !_hasMore, 114 | delegate: MyLoadMoreDelegate(), 115 | child: ListView.builder( 116 | scrollDirection: Axis.vertical, 117 | itemCount: searchResults.length, 118 | itemBuilder: (context, index) { 119 | return MinimalVerticalListCard(songs: searchResults, index: index); 120 | }, 121 | ), 122 | ), 123 | ), 124 | ) 125 | ], 126 | ), 127 | ); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![made with flutter](https://raw.githubusercontent.com/imsudip/sunday_suspense/master/extras/f_b.jpg) 2 | 3 | # Sunday Suspense App 4 | 5 | An open source flutter project to listen to our favourite Sunday Suspense 6 | 7 | [![made-with-flutter](https://img.shields.io/badge/Made%20with-Flutter-9a0e13.svg?style=for-the-badge&logo=flutter)](https://flutter.dev/) ![Release](https://img.shields.io/github/v/release/imsudip/sunday_suspense?style=for-the-badge) ![Downloads](https://img.shields.io/github/downloads/imsudip/sunday_suspense/total?style=for-the-badge) 8 | 9 | [![DB Update](https://github.com/imsudip/sunday_suspense/actions/workflows/update_db.yml/badge.svg?branch=master)](https://github.com/imsudip/sunday_suspense/actions/workflows/update_db.yml) [![DB backup](https://github.com/imsudip/sunday_suspense/actions/workflows/db_backup.yml/badge.svg)](https://github.com/imsudip/sunday_suspense/actions/workflows/db_backup.yml) 10 | 11 | 12 | 13 | ### Don't forget to :star: the repo 14 | 15 | [![GitHub stars](https://img.shields.io/github/stars/imsudip/sunday_suspense.svg?style=for-the-badge&label=Star)](https://github.com//imsudip/sunday_suspense) ![GitHub forks](https://img.shields.io/github/forks/imsudip/sunday_suspense.svg?style=for-the-badge&label=Forks) 16 | 17 | ## Download 18 | 19 | [Download from GitHub](https://github.com/imsudip/sunday_suspense/releases) 22 |        23 | 24 | ## Features 25 | 26 | - Smooth and responsive UI 27 | - Curation of different categories 28 | - Minimalistic yet beautiful Player 29 | - Categorized by different genres and authors 30 | - Search for your favourite Sunday Suspense 31 | - Playback History (Remember where you left off) 32 | - Updated weekly with new episodes 33 | - More features coming soon 34 | 35 | ## Screenshots 36 | 37 | 38 | 39 | ## License 40 | 41 | ``` 42 | Copyright © 2022 Sudip Ghosh 43 | 44 | This is a free software licensed under GPL v3.0 45 | It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 46 | without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 47 | ``` 48 | 49 | ``` 50 | Being Open Source doesn't mean you can just make a copy of the app and upload it on playstore or sell 51 | a closed source copy of the same. 52 | Read the following carefully: 53 | 1. Any copy of a software under GPL must be under same license. So you can't upload the app on a closed source 54 | app repository like PlayStore/AppStore without distributing the source code. 55 | 2. You can't sell any copied/modified version of the app under any "non-free" license. 56 | You must provide the copy with the original software or with instructions on how to obtain original software, 57 | should clearly state all changes, should clearly disclose full source code, should include same license 58 | and all copyrights should be retained. 59 | 60 | In simple words, You can ONLY use the source code of this app for `Open Source` Project under `GPL v3.0` or later 61 | with all your source code CLEARLY DISCLOSED on any code hosting platform like GitHub, with clear INSTRUCTIONS on 62 | how to obtain the original software, should clearly STATE ALL CHANGES made and should RETAIN all copyrights. 63 | Use of this software under any "non-free" license is NOT permitted. 64 | ``` 65 | 66 | See the [GNU General Public License](https://github.com/imsudip/sunday_suspense/blob/master/LICENSE) for more details. 67 | 68 | ## Building from Source 69 | 70 | 1. If you don't have Flutter SDK installed, please visit official [Flutter](https://flutter.dev/) site. 71 | 2. Fetch latest source code from master branch. 72 | 73 | ``` 74 | git clone https://github.com/imsudip/sunday_suspense.git 75 | ``` 76 | 77 | 3. Run the app with Android Studio or VS Code. Or the command line: 78 | 79 | ``` 80 | flutter pub get 81 | flutter run 82 | ``` 83 | 84 | ## Contribute 85 | 86 | Contributions are welcome. Please read our [contributing guidelines](https://github.com/imsudip/sunday_suspense/blob/master/CONTRIBUTING.md) before contributing. 87 | 88 | ![made with flutter](https://raw.githubusercontent.com/imsudip/sunday_suspense/master/extras/bottom_bar.png) 89 | -------------------------------------------------------------------------------- /lib/services/notification_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:firebase_messaging/firebase_messaging.dart'; 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:firebase_core/firebase_core.dart'; 5 | import 'package:flutter_local_notifications/flutter_local_notifications.dart'; 6 | import 'package:sunday_suspense/firebase_options.dart'; 7 | import 'package:sunday_suspense/ui/app_colors.dart'; 8 | 9 | class FCMFunctions { 10 | static final FCMFunctions _singleton = FCMFunctions._internal(); 11 | 12 | FCMFunctions._internal(); 13 | 14 | factory FCMFunctions() { 15 | return _singleton; 16 | } 17 | 18 | late FirebaseMessaging messaging; 19 | 20 | //************************************************************************************************************ */ 21 | /// Create a [AndroidNotificationChannel] for heads up notifications 22 | late AndroidNotificationChannel channel; 23 | 24 | /// Initialize the [FlutterLocalNotificationsPlugin] package. 25 | late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin; 26 | 27 | //************************************************************************************************************ */ 28 | 29 | Future initApp() async { 30 | await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); 31 | 32 | messaging = FirebaseMessaging.instance; 33 | 34 | if (!kIsWeb) { 35 | channel = const AndroidNotificationChannel( 36 | 'high_importance_channel', // id 37 | 'High Importance Notifications', // title 38 | importance: Importance.high, 39 | showBadge: true, 40 | ); 41 | 42 | flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); 43 | 44 | /// Create an Android Notification Channel. 45 | /// 46 | /// We use this channel in the `AndroidManifest.xml` file to override the 47 | /// default FCM channel to enable heads up notifications. 48 | await flutterLocalNotificationsPlugin 49 | .resolvePlatformSpecificImplementation() 50 | ?.createNotificationChannel(channel); 51 | 52 | //for IOS Foreground Notification 53 | await messaging.setForegroundNotificationPresentationOptions( 54 | alert: true, 55 | badge: true, 56 | sound: true, 57 | ); 58 | } 59 | } 60 | 61 | Future subscripeToTopics(String topic) async { 62 | if (!kIsWeb) { 63 | await messaging.subscribeToTopic(topic); 64 | } 65 | } 66 | 67 | ///Expire : https://firebase.google.com/docs/cloud-messaging/manage-tokens 68 | Future getFCMToken() async { 69 | final fcmToken = await messaging.getToken(); 70 | return fcmToken; 71 | } 72 | 73 | void tokenListener() { 74 | messaging.onTokenRefresh.listen((fcmToken) {}).onError((err) { 75 | print(err); 76 | }); 77 | } 78 | 79 | /// IOS 80 | Future iosWebPermission() async { 81 | if (kIsWeb || Platform.isIOS) { 82 | NotificationSettings settings = await messaging.requestPermission( 83 | alert: true, 84 | announcement: false, 85 | badge: true, 86 | carPlay: false, 87 | criticalAlert: false, 88 | provisional: false, 89 | sound: true, 90 | ); 91 | } 92 | } 93 | 94 | ///Foreground messages 95 | /// 96 | ///To handle messages while your application is in the foreground, listen to the onMessage stream. 97 | void foreGroundMessageListener() { 98 | FirebaseMessaging.onMessage.listen((RemoteMessage message) { 99 | RemoteNotification? notification = message.notification; 100 | AndroidNotification? android = message.notification?.android; 101 | if (notification != null && android != null && !kIsWeb) { 102 | flutterLocalNotificationsPlugin.show( 103 | notification.hashCode, 104 | notification.title, 105 | notification.body, 106 | NotificationDetails( 107 | android: AndroidNotificationDetails(channel.id, channel.name, 108 | channelDescription: channel.description, 109 | importance: Importance.max, 110 | priority: Priority.high, 111 | ticker: 'ticker', 112 | icon: "@mipmap/ic_music", 113 | color: AppColors.backgroundColor), 114 | ), 115 | ); 116 | } 117 | }); 118 | 119 | ///Background messages 120 | /// To handle messages while your app is in the background or terminated, listen to the onBackgroundMessage stream. 121 | /// This is only available on Android and iOS. 122 | 123 | FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { 124 | print('A new onMessageOpenedApp event was published!'); 125 | 126 | // Navigator.pushNamed( 127 | // context, 128 | // '/message', 129 | // arguments: MessageArguments(message, true), 130 | // ); 131 | }); 132 | } 133 | } 134 | 135 | final fcmFunctions = FCMFunctions(); 136 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | sudipghosh9333@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.10) 3 | project(runner LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "sunday_suspense") 8 | # The unique GTK application identifier for this application. See: 9 | # https://wiki.gnome.org/HowDoI/ChooseApplicationID 10 | set(APPLICATION_ID "com.example.sunday_suspense") 11 | 12 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 13 | # versions of CMake. 14 | cmake_policy(SET CMP0063 NEW) 15 | 16 | # Load bundled libraries from the lib/ directory relative to the binary. 17 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 18 | 19 | # Root filesystem for cross-building. 20 | if(FLUTTER_TARGET_PLATFORM_SYSROOT) 21 | set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) 22 | set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) 23 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 24 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) 25 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 26 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 27 | endif() 28 | 29 | # Define build configuration options. 30 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 31 | set(CMAKE_BUILD_TYPE "Debug" CACHE 32 | STRING "Flutter build mode" FORCE) 33 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 34 | "Debug" "Profile" "Release") 35 | endif() 36 | 37 | # Compilation settings that should be applied to most targets. 38 | # 39 | # Be cautious about adding new options here, as plugins use this function by 40 | # default. In most cases, you should add new options to specific targets instead 41 | # of modifying this function. 42 | function(APPLY_STANDARD_SETTINGS TARGET) 43 | target_compile_features(${TARGET} PUBLIC cxx_std_14) 44 | target_compile_options(${TARGET} PRIVATE -Wall -Werror) 45 | target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") 46 | target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") 47 | endfunction() 48 | 49 | # Flutter library and tool build rules. 50 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 51 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 52 | 53 | # System-level dependencies. 54 | find_package(PkgConfig REQUIRED) 55 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 56 | 57 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") 58 | 59 | # Define the application target. To change its name, change BINARY_NAME above, 60 | # not the value here, or `flutter run` will no longer work. 61 | # 62 | # Any new source files that you add to the application should be added here. 63 | add_executable(${BINARY_NAME} 64 | "main.cc" 65 | "my_application.cc" 66 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 67 | ) 68 | 69 | # Apply the standard set of build settings. This can be removed for applications 70 | # that need different build settings. 71 | apply_standard_settings(${BINARY_NAME}) 72 | 73 | # Add dependency libraries. Add any application-specific dependencies here. 74 | target_link_libraries(${BINARY_NAME} PRIVATE flutter) 75 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) 76 | 77 | # Run the Flutter tool portions of the build. This must not be removed. 78 | add_dependencies(${BINARY_NAME} flutter_assemble) 79 | 80 | # Only the install-generated bundle's copy of the executable will launch 81 | # correctly, since the resources must in the right relative locations. To avoid 82 | # people trying to run the unbundled copy, put it in a subdirectory instead of 83 | # the default top-level location. 84 | set_target_properties(${BINARY_NAME} 85 | PROPERTIES 86 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" 87 | ) 88 | 89 | # Generated plugin build rules, which manage building the plugins and adding 90 | # them to the application. 91 | include(flutter/generated_plugins.cmake) 92 | 93 | 94 | # === Installation === 95 | # By default, "installing" just makes a relocatable bundle in the build 96 | # directory. 97 | set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") 98 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 99 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 100 | endif() 101 | 102 | # Start with a clean build bundle directory every time. 103 | install(CODE " 104 | file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") 105 | " COMPONENT Runtime) 106 | 107 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 108 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") 109 | 110 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 111 | COMPONENT Runtime) 112 | 113 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 114 | COMPONENT Runtime) 115 | 116 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 117 | COMPONENT Runtime) 118 | 119 | foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) 120 | install(FILES "${bundled_library}" 121 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 122 | COMPONENT Runtime) 123 | endforeach(bundled_library) 124 | 125 | # Fully re-copy the assets directory on each build to avoid having stale files 126 | # from a previous install. 127 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 128 | install(CODE " 129 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 130 | " COMPONENT Runtime) 131 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 132 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 133 | 134 | # Install the AOT library on non-Debug builds only. 135 | if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") 136 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 137 | COMPONENT Runtime) 138 | endif() 139 | -------------------------------------------------------------------------------- /lib/screens/homepage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../services/database_service.dart'; 4 | import 'searchScreen.dart'; 5 | import 'show_all_page.dart'; 6 | import '../ui/app_colors.dart'; 7 | import '../ui/text_styles.dart'; 8 | import '../widgets/art_glass_overlay.dart'; 9 | import '../widgets/song_viewers.dart'; 10 | import '../widgets/player_parts.dart'; 11 | import '../widgets/searchbar.dart'; 12 | 13 | class HomePageScreen extends StatefulWidget { 14 | const HomePageScreen({super.key}); 15 | 16 | @override 17 | State createState() => _HomePageScreenState(); 18 | } 19 | 20 | class _HomePageScreenState extends State { 21 | @override 22 | Widget build(BuildContext context) { 23 | return Scaffold( 24 | // appBar: AppBar( 25 | // title: const Text('Audio Service Demo'), 26 | // ), 27 | backgroundColor: AppColors.backgroundColor, 28 | body: ArtGlass( 29 | intensity: 0.8, 30 | child: Stack( 31 | children: [ 32 | SingleChildScrollView( 33 | child: Column( 34 | mainAxisAlignment: MainAxisAlignment.start, 35 | children: [ 36 | const SizedBox( 37 | height: 52, 38 | ), 39 | Image.asset( 40 | 'assets/logo.png', 41 | height: 80, 42 | ), 43 | const SizedBox( 44 | height: 20, 45 | ), 46 | GestureDetector( 47 | onTap: () { 48 | Get.to(() => const SearchScreen()); 49 | }, 50 | child: AbsorbPointer(child: const SearchBar())), 51 | const TrendingBar(), 52 | const CategoryBar(title: "🎩 Sherlock Holmes", tag: "sherlock"), 53 | const CategoryBar(title: "🚬 Feluda", tag: "feluda"), 54 | const CategoryBar(title: "👓 Byomkesh Bakshi", tag: "byomkesh"), 55 | ], 56 | ), 57 | ), 58 | // add a gradient to the bottom of the page 59 | // Positioned( 60 | // bottom: 0, 61 | // child: IgnorePointer( 62 | // child: Container( 63 | // height: 50, 64 | // width: MediaQuery.of(context).size.width, 65 | // decoration: const BoxDecoration( 66 | // gradient: LinearGradient( 67 | // begin: Alignment.topCenter, 68 | // end: Alignment.bottomCenter, 69 | // colors: [ 70 | // Colors.transparent, 71 | // AppColors.backgroundColor, 72 | // ], 73 | // ), 74 | // ), 75 | // ), 76 | // ), 77 | // ), 78 | ], 79 | ), 80 | ), 81 | ); 82 | } 83 | } 84 | 85 | class TrendingBar extends StatelessWidget { 86 | const TrendingBar({super.key}); 87 | 88 | @override 89 | Widget build(BuildContext context) { 90 | return Padding( 91 | padding: const EdgeInsets.symmetric(horizontal: 16), 92 | child: Column( 93 | mainAxisSize: MainAxisSize.min, 94 | crossAxisAlignment: CrossAxisAlignment.start, 95 | children: [ 96 | Row( 97 | children: [ 98 | Text(' 🔥 Trending', style: AppTextStyle.headline2), 99 | const Spacer(), 100 | TextButton( 101 | onPressed: () { 102 | Get.to(() => ShowAllPage( 103 | title: "Trending", 104 | future: (page) { 105 | return DatabaseService.getTrending(page: page); 106 | })); 107 | }, 108 | child: Text( 109 | 'See All', 110 | style: AppTextStyle.headline3, 111 | ), 112 | ), 113 | ], 114 | ), 115 | const SizedBox( 116 | height: 8, 117 | ), 118 | FutureBuilder( 119 | future: DatabaseService.getTrending(), 120 | builder: (context, snapshot) { 121 | if (snapshot.hasData) { 122 | List songs = snapshot.data!; 123 | return HorizontalListView(songs: songs); 124 | } else { 125 | return const SizedBox( 126 | height: 200, 127 | child: Center( 128 | child: SizedBox(height: 50, child: LoadingAnimation()), 129 | ), 130 | ); 131 | } 132 | }), 133 | const SizedBox( 134 | height: 20, 135 | ), 136 | ], 137 | ), 138 | ); 139 | } 140 | } 141 | 142 | class CategoryBar extends StatelessWidget { 143 | const CategoryBar({super.key, required this.title, required this.tag}); 144 | final String title, tag; 145 | 146 | @override 147 | Widget build(BuildContext context) { 148 | return Padding( 149 | padding: const EdgeInsets.symmetric(horizontal: 16), 150 | child: Column( 151 | mainAxisSize: MainAxisSize.min, 152 | crossAxisAlignment: CrossAxisAlignment.start, 153 | children: [ 154 | Row( 155 | children: [ 156 | Text(' $title', style: AppTextStyle.headline2), 157 | const Spacer(), 158 | TextButton( 159 | onPressed: () { 160 | Get.to(() => ShowAllPage( 161 | title: title, 162 | future: (page) { 163 | return DatabaseService.searchAudio(tag, page: page); 164 | })); 165 | }, 166 | child: Text( 167 | 'See All', 168 | style: AppTextStyle.headline3, 169 | ), 170 | ), 171 | ], 172 | ), 173 | const SizedBox( 174 | height: 8, 175 | ), 176 | FutureBuilder( 177 | future: DatabaseService.searchAudio(tag), 178 | builder: (context, snapshot) { 179 | if (snapshot.hasData) { 180 | List> songs = snapshot.data!; 181 | return HorizontalListView(songs: songs); 182 | } else { 183 | return const SizedBox( 184 | height: 200, 185 | child: Center( 186 | child: SizedBox(height: 50, child: LoadingAnimation()), 187 | ), 188 | ); 189 | } 190 | }), 191 | const SizedBox( 192 | height: 20, 193 | ), 194 | ], 195 | ), 196 | ); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /lib/page_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:developer'; 3 | import 'dart:io'; 4 | 5 | import 'package:flutter/foundation.dart'; 6 | import 'package:get/get.dart'; 7 | import 'package:get_storage/get_storage.dart'; 8 | import 'package:just_audio/just_audio.dart'; 9 | import 'package:just_audio_background/just_audio_background.dart'; 10 | import 'services/database_service.dart'; 11 | import 'ui/app_colors.dart'; 12 | import 'notifiers/play_button_notifier.dart'; 13 | import 'notifiers/progress_notifier.dart'; 14 | import 'notifiers/repeat_button_notifier.dart'; 15 | 16 | class PageManager { 17 | // Listeners: Updates going to the UI 18 | final currentSongTitleNotifier = ValueNotifier(''); 19 | final currentSongImageNotifier = ValueNotifier(''); 20 | final currentSongIdNotifier = ValueNotifier(''); 21 | final playlistNotifier = ValueNotifier>([]); 22 | final progressNotifier = ProgressNotifier(); 23 | final repeatButtonNotifier = RepeatButtonNotifier(); 24 | final isFirstSongNotifier = ValueNotifier(true); 25 | final playButtonNotifier = PlayButtonNotifier(); 26 | final isLastSongNotifier = ValueNotifier(true); 27 | final isShuffleModeEnabledNotifier = ValueNotifier(false); 28 | final audioQualityNotifier = ValueNotifier(''); 29 | final audioQualityStoreNotifier = ValueNotifier>({}); // Map 30 | final mediaItemNotifier = ValueNotifier(null); 31 | // final _audioHandler = getIt(); 32 | late AudioPlayer audioPlayer; 33 | 34 | final box = GetStorage("HistoryBox"); 35 | final prefs = GetStorage("prefs"); 36 | 37 | // Events: Calls coming from the UI 38 | void init() async { 39 | await initiateBackgroundAudio(); 40 | _listenToPlaybackState(); 41 | _listenToCurrentPosition(); 42 | _listenToBufferedPosition(); 43 | _listenToTotalDuration(); 44 | _listenToChangesInSong(); 45 | audioQualityNotifier.value = prefs.read("audioQuality") ?? "high"; 46 | } 47 | 48 | Future initiateBackgroundAudio() async { 49 | await JustAudioBackground.init( 50 | androidNotificationChannelId: 'com.example.sunday_suspense.channel.audio', 51 | androidNotificationChannelName: 'Audio playback', 52 | androidNotificationOngoing: true, 53 | androidStopForegroundOnPause: true, 54 | ); 55 | audioPlayer = AudioPlayer(); 56 | } 57 | 58 | void _listenToPlaybackState() { 59 | audioPlayer.playerStateStream.listen((playbackState) { 60 | final isPlaying = playbackState.playing; 61 | final processingState = playbackState.processingState; 62 | if (processingState == ProcessingState.loading || 63 | processingState == ProcessingState.buffering || 64 | processingState == ProcessingState.idle) { 65 | playButtonNotifier.value = ButtonState.loading; 66 | } else if (!isPlaying) { 67 | playButtonNotifier.value = ButtonState.paused; 68 | } else if (processingState != ProcessingState.completed) { 69 | playButtonNotifier.value = ButtonState.playing; 70 | } else { 71 | audioPlayer.pause(); 72 | } 73 | }); 74 | } 75 | 76 | void _listenToCurrentPosition() { 77 | audioPlayer.positionStream.listen((position) { 78 | final oldState = progressNotifier.value; 79 | int seconds = position.inSeconds; 80 | 81 | if (seconds % 5 == 0 && seconds != oldState.current.inSeconds) { 82 | log("saving history..."); 83 | box.write(currentSongIdNotifier.value, seconds); 84 | } 85 | progressNotifier.value = ProgressBarState( 86 | current: position, 87 | buffered: oldState.buffered, 88 | total: oldState.total, 89 | ); 90 | }); 91 | } 92 | 93 | void _listenToBufferedPosition() { 94 | audioPlayer.bufferedPositionStream.listen((bufferedPosition) { 95 | final oldState = progressNotifier.value; 96 | progressNotifier.value = ProgressBarState( 97 | current: oldState.current, 98 | buffered: bufferedPosition, 99 | total: oldState.total, 100 | ); 101 | }); 102 | } 103 | 104 | void _listenToTotalDuration() { 105 | audioPlayer.durationStream.listen((duration) { 106 | final oldState = progressNotifier.value; 107 | progressNotifier.value = ProgressBarState( 108 | current: oldState.current, 109 | buffered: oldState.buffered, 110 | total: duration ?? Duration.zero, 111 | ); 112 | }); 113 | } 114 | 115 | void _listenToChangesInSong() { 116 | mediaItemNotifier.addListener(() { 117 | currentSongTitleNotifier.value = mediaItemNotifier.value?.title ?? ''; 118 | currentSongImageNotifier.value = mediaItemNotifier.value?.artUri.toString() ?? ''; 119 | currentSongIdNotifier.value = mediaItemNotifier.value?.id ?? ''; 120 | }); 121 | } 122 | 123 | Future seek(Duration position) async { 124 | await audioPlayer.seek(position); 125 | } 126 | 127 | Future addOne(dynamic audio) async { 128 | var streamUrl = await DatabaseService.getStreamLink(audio['url']); 129 | if (streamUrl == "") { 130 | Get.back(); 131 | Get.snackbar("Error", "There is a problem with this song. Please try another one.", 132 | snackPosition: SnackPosition.BOTTOM, 133 | backgroundColor: AppColors.accentColor, 134 | colorText: AppColors.primaryWhiteColor); 135 | return; 136 | } 137 | final mediaItem = MediaItem( 138 | id: audio['video_id'] ?? '', 139 | album: 'sunday suspense', 140 | title: audio['title'] ?? '', 141 | artUri: Uri.parse(audio['thumbnail']), 142 | extras: {'url': streamUrl}, 143 | ); 144 | 145 | mediaItemNotifier.value = mediaItem; 146 | AudioSource audioSource = LockCachingAudioSource( 147 | Uri.parse(streamUrl), 148 | tag: mediaItem, 149 | ); 150 | Get.back(); 151 | Duration initialPosition = Duration(seconds: box.read(audio['video_id'] ?? '') ?? 0); 152 | audioPlayer.setAudioSource(audioSource, initialPosition: initialPosition).whenComplete(() { 153 | audioPlayer.play(); 154 | }); 155 | } 156 | 157 | Future setAudioQuality(String quality) async { 158 | audioQualityNotifier.value = quality; 159 | prefs.write("audioQuality", quality); 160 | } 161 | 162 | Future changeAudioQuality(String url) async { 163 | final oldMediaItem = mediaItemNotifier.value; 164 | if (oldMediaItem == null) return; 165 | final mediaItem = MediaItem( 166 | id: oldMediaItem.id, 167 | album: oldMediaItem.album, 168 | title: oldMediaItem.title, 169 | artUri: oldMediaItem.artUri, 170 | extras: {'url': url}, 171 | ); 172 | mediaItemNotifier.value = mediaItem; 173 | AudioSource audioSource = LockCachingAudioSource( 174 | Uri.parse(url), 175 | tag: mediaItem, 176 | ); 177 | await audioPlayer.pause(); 178 | var currentDuration = audioPlayer.position; 179 | await audioPlayer.setAudioSource(audioSource, initialPosition: currentDuration); 180 | await audioPlayer.play(); 181 | } 182 | 183 | void dispose() { 184 | audioPlayer.dispose(); 185 | } 186 | 187 | void stop() { 188 | audioPlayer.stop(); 189 | } 190 | 191 | void pause() { 192 | audioPlayer.pause(); 193 | } 194 | 195 | void play() { 196 | audioPlayer.play(); 197 | } 198 | 199 | void fastForward() { 200 | audioPlayer.seek(audioPlayer.position + const Duration(seconds: 10)); 201 | } 202 | 203 | void rewind() { 204 | audioPlayer.seek(audioPlayer.position - const Duration(seconds: 10)); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /lib/screens/categorypage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bounceable/flutter_bounceable.dart'; 3 | import 'package:get/get.dart'; 4 | import '../services/database_service.dart'; 5 | import 'show_all_page.dart'; 6 | import '../ui/app_colors.dart'; 7 | import '../ui/text_styles.dart'; 8 | 9 | class CategoryPageScreen extends StatelessWidget { 10 | const CategoryPageScreen({super.key}); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Scaffold( 15 | backgroundColor: AppColors.backgroundColor, 16 | body: SafeArea( 17 | child: SingleChildScrollView( 18 | child: Padding( 19 | padding: const EdgeInsets.symmetric(horizontal: 20), 20 | child: Column( 21 | children: [ 22 | const SizedBox(height: 20), 23 | Text( 24 | "Categories", 25 | style: AppTextStyle.headline2, 26 | ), 27 | const SizedBox(height: 20), 28 | Row( 29 | children: const [ 30 | CategoryCard2( 31 | title: "🔪🩸 Crime", 32 | keyword: "crime", 33 | ), 34 | CategoryCard2( 35 | title: "👻💀 Horror", 36 | keyword: "horror", 37 | ), 38 | ], 39 | ), 40 | Row( 41 | children: const [ 42 | CategoryCard2( 43 | title: "🛩️🏖️ Adventure", 44 | keyword: "adventure", 45 | ), 46 | CategoryCard2( 47 | title: "😱🧟 Thriller", 48 | keyword: "thriller", 49 | ), 50 | ], 51 | ), 52 | const SizedBox(height: 20), 53 | Text( 54 | "Fictional Characters", 55 | style: AppTextStyle.headline2, 56 | ), 57 | const SizedBox(height: 20), 58 | Row( 59 | children: const [ 60 | CategoryCard( 61 | title: "🎩 Sherlock Holmes", 62 | keyword: "sherlock", 63 | ), 64 | ], 65 | ), 66 | Row( 67 | children: const [ 68 | CategoryCard( 69 | title: "🚬 Feluda", 70 | keyword: "feluda", 71 | ), 72 | ], 73 | ), 74 | Row( 75 | children: const [ 76 | CategoryCard( 77 | title: "👓 Byomkesh Bakshi", 78 | keyword: "byomkesh", 79 | ), 80 | ], 81 | ), 82 | Row( 83 | children: const [ 84 | CategoryCard( 85 | title: "👴 Professor Shonku", 86 | keyword: "shonku", 87 | ), 88 | ], 89 | ), 90 | Row( 91 | children: const [ 92 | CategoryCard( 93 | title: "Taranath Tantrik", 94 | keyword: "taranath", 95 | ), 96 | CategoryCard( 97 | title: "Tarini Khuro", 98 | keyword: "tarini", 99 | ), 100 | ], 101 | ), 102 | const SizedBox(height: 20), 103 | Text( 104 | "Reowned Authors", 105 | style: AppTextStyle.headline2, 106 | ), 107 | const SizedBox(height: 20), 108 | Row( 109 | children: const [ 110 | CategoryCard( 111 | title: "Sir Arthur Conan Doyle", 112 | keyword: "arthur conan doyle", 113 | ), 114 | ], 115 | ), 116 | Row( 117 | children: const [ 118 | CategoryCard( 119 | title: "Satyajit Ray", 120 | keyword: "satyajit ray", 121 | ), 122 | ], 123 | ), 124 | Row( 125 | children: const [ 126 | CategoryCard( 127 | title: "Shorodindu Bandopadhyay", 128 | keyword: "shorodindu bandopadhyay", 129 | ), 130 | ], 131 | ), 132 | Row( 133 | children: const [ 134 | CategoryCard( 135 | title: "Taradas Bandopadhyay", 136 | keyword: "taradas bandopadhyay", 137 | ), 138 | ], 139 | ), 140 | ], 141 | ), 142 | ), 143 | ), 144 | ), 145 | ); 146 | } 147 | } 148 | 149 | class CategoryCard extends StatelessWidget { 150 | const CategoryCard({ 151 | Key? key, 152 | required this.title, 153 | required this.keyword, 154 | }) : super(key: key); 155 | final String title, keyword; 156 | @override 157 | Widget build(BuildContext context) { 158 | return Expanded( 159 | child: Bounceable( 160 | onTap: () { 161 | Get.to(() => ShowAllPage( 162 | title: title, 163 | future: (page) { 164 | return DatabaseService.searchAudio(keyword, page: page); 165 | }, 166 | )); 167 | }, 168 | child: Container( 169 | margin: const EdgeInsets.all(8), 170 | padding: const EdgeInsets.all(4), 171 | height: 60, 172 | decoration: BoxDecoration( 173 | color: AppColors.accentColor, 174 | borderRadius: BorderRadius.circular(14), 175 | ), 176 | child: Center( 177 | child: Text( 178 | title, 179 | style: AppTextStyle.subHeading.copyWith(fontSize: 18), 180 | ), 181 | ), 182 | ), 183 | ), 184 | ); 185 | } 186 | } 187 | 188 | class CategoryCard2 extends StatelessWidget { 189 | const CategoryCard2({ 190 | Key? key, 191 | required this.title, 192 | required this.keyword, 193 | }) : super(key: key); 194 | final String title, keyword; 195 | @override 196 | Widget build(BuildContext context) { 197 | return Expanded( 198 | child: Bounceable( 199 | onTap: () { 200 | Get.to(() => ShowAllPage( 201 | title: title, 202 | future: (page) { 203 | return DatabaseService.getAudioByTag(keyword, page: page); 204 | }, 205 | )); 206 | }, 207 | child: Container( 208 | margin: const EdgeInsets.all(8), 209 | padding: const EdgeInsets.all(4), 210 | height: 60, 211 | decoration: BoxDecoration( 212 | color: AppColors.accentColor, 213 | borderRadius: BorderRadius.circular(14), 214 | ), 215 | child: Center( 216 | child: Text( 217 | title, 218 | style: AppTextStyle.subHeading.copyWith(fontSize: 18), 219 | ), 220 | ), 221 | ), 222 | ), 223 | ); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /assets/lottie/loading.json: -------------------------------------------------------------------------------- 1 | {"v":"5.4.1","fr":29.9700012207031,"ip":0,"op":33.0000013441176,"w":200,"h":200,"nm":"Comp 1","ddd":1,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":15,"s":[0],"e":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":20,"s":[100],"e":[0]},{"t":32.0000013033867}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[123.951,100,0],"ix":2},"a":{"a":0,"k":[-145.5,-16.5,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":15,"s":[108.551,16.73,100],"e":[108.551,163.73,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":20,"s":[108.551,163.73,100],"e":[108.551,16.73,100]},{"t":32.0000013033867}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[7,37],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":9,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":0,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":8},"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"g":{"p":3,"k":{"a":0,"k":[0,1,1,1,0.5,1,1,1,1,1,1,1],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[0.278,-18.483],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-145.5,-16.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":15.0000006109625,"op":915.000037268714,"st":15.0000006109625,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":10,"s":[0],"e":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":15,"s":[100],"e":[0]},{"t":27.0000010997325}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[109.451,100,0],"ix":2},"a":{"a":0,"k":[-145.5,-16.5,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":10,"s":[108.551,16.73,100],"e":[108.551,120.73,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":15,"s":[108.551,120.73,100],"e":[108.551,16.73,100]},{"t":26.0000010590017}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[7,37],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":9,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":0,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":8},"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"g":{"p":3,"k":{"a":0,"k":[0,1,1,1,0.5,1,1,1,1,1,1,1],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[0.047,-18.582],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-145.5,-16.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":10.0000004073083,"op":910.00003706506,"st":10.0000004073083,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":5,"s":[0],"e":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":10,"s":[100],"e":[0]},{"t":20.0000008146167}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[94.951,100,0],"ix":2},"a":{"a":0,"k":[-145.5,-16.5,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":5,"s":[108.551,16.73,100],"e":[108.551,163.73,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":10,"s":[108.551,163.73,100],"e":[108.551,16.73,100]},{"t":20.0000008146167}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[7,37],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":9,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":0,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":8},"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"g":{"p":3,"k":{"a":0,"k":[0,1,1,1,0.5,1,1,1,1,1,1,1],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[0.047,-18.323],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-145.5,-16.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":5.00000020365417,"op":905.000036861406,"st":5.00000020365417,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":0,"s":[0],"e":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":5,"s":[100],"e":[0]},{"t":16.0000006516934}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[80.451,100,0],"ix":2},"a":{"a":0,"k":[-145.5,-16.5,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":0,"s":[108.551,16.73,100],"e":[108.551,120.73,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":5,"s":[108.551,120.73,100],"e":[108.551,16.73,100]},{"t":16.0000006516934}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[7,37],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":9,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":0,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":8},"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"g":{"p":3,"k":{"a":0,"k":[0,1,1,1,0.5,1,1,1,1,1,1,1],"ix":9}},"s":{"a":0,"k":[0.23,4.97],"ix":5},"e":{"a":0,"k":[0.278,-18.223],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-145.5,-16.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900.000036657751,"st":0,"bm":0},{"ddd":1,"ind":5,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10,"x":"var $bm_rt;\n$bm_rt = transform.zRotation;"},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":0,"s":[76.062,100,0],"e":[105.062,100,0],"to":[4.83333349227905,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":14,"s":[105.062,100,0],"e":[76.062,100,0],"to":[0,0,0],"ti":[4.83333349227905,0,0]},{"t":31.0000012626559}],"ix":2},"a":{"a":0,"k":[-66.877,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":0,"s":[0,87,100],"e":[23,87,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0","0p667_1_0p333_0"],"t":14,"s":[23,87,100],"e":[0,87,100]},{"t":31.0000012626559}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[100,2],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":0,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":8},"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"g":{"p":3,"k":{"a":0,"k":[0,1,1,1,0.5,1,1,1,1,1,1,1],"ix":9}},"s":{"a":0,"k":[0,0],"ix":5},"e":{"a":0,"k":[51.671,0.287],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-17,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900.000036657751,"st":0,"bm":0}],"markers":[]} -------------------------------------------------------------------------------- /lib/screens/player.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:iconsax/iconsax.dart'; 4 | import 'package:solid_bottom_sheet/solid_bottom_sheet.dart'; 5 | import '../services/database_service.dart'; 6 | import '../ui/my_theme.dart'; 7 | import '../ui/text_styles.dart'; 8 | import '../widgets/art_glass_overlay.dart'; 9 | import '../widgets/song_viewers.dart'; 10 | 11 | import '../page_manager.dart'; 12 | import '../services/service_locator.dart'; 13 | import '../ui/app_colors.dart'; 14 | import '../widgets/player_parts.dart'; 15 | 16 | class PlayerScreen extends StatelessWidget { 17 | PlayerScreen({super.key}); 18 | 19 | final SolidController _solidController = SolidController(); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | final pageManager = getIt(); 24 | return WillPopScope( 25 | onWillPop: () async { 26 | if (_solidController.isOpened) { 27 | _solidController.hide(); 28 | return false; 29 | } 30 | return true; 31 | }, 32 | child: Theme( 33 | data: MyTheme.darkTheme 34 | .copyWith(bottomSheetTheme: const BottomSheetThemeData(backgroundColor: Colors.transparent)), 35 | child: Scaffold( 36 | backgroundColor: AppColors.backgroundColor, 37 | body: ArtGlass( 38 | child: Padding( 39 | padding: const EdgeInsets.all(8.0), 40 | child: Column( 41 | children: [ 42 | AppBar( 43 | title: Text( 44 | "Now Playing", 45 | style: AppTextStyle.subHeading, 46 | ), 47 | backgroundColor: Colors.transparent, 48 | elevation: 0, 49 | leading: IconButton( 50 | icon: const Icon(Iconsax.arrow_down_1), 51 | onPressed: () { 52 | Get.back(); 53 | }, 54 | ), 55 | actions: [ 56 | // quality button 57 | ValueListenableBuilder( 58 | valueListenable: pageManager.audioQualityNotifier, 59 | builder: (_, quality, __) { 60 | return PopupMenuButton( 61 | onSelected: (value) { 62 | pageManager.changeAudioQuality(pageManager.audioQualityStoreNotifier.value[value]!); 63 | pageManager.setAudioQuality(value); 64 | }, 65 | itemBuilder: (context) { 66 | // audio quality stored in {quality:url} format 67 | return List.generate( 68 | pageManager.audioQualityStoreNotifier.value.keys.length, (index) => index) 69 | .map((e) => PopupMenuItem( 70 | value: pageManager.audioQualityStoreNotifier.value.keys.elementAt(e), 71 | child: Text(pageManager.audioQualityStoreNotifier.value.keys.elementAt(e)), 72 | )) 73 | .toList(); 74 | }, 75 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), 76 | child: Padding( 77 | padding: const EdgeInsets.all(8.0), 78 | child: Row( 79 | children: [ 80 | const Icon( 81 | Iconsax.cd, 82 | color: AppColors.primaryColor, 83 | ), 84 | const SizedBox(width: 5), 85 | Text(quality), 86 | ], 87 | ), 88 | ), 89 | ); 90 | }), 91 | ], 92 | ), 93 | const SizedBox(height: 80), 94 | Container( 95 | padding: const EdgeInsets.symmetric(horizontal: 20), 96 | child: AspectRatio( 97 | aspectRatio: 16 / 9, 98 | child: ClipRRect( 99 | borderRadius: BorderRadius.circular(12), 100 | child: const CurrentSongArt( 101 | width: double.infinity, 102 | ), 103 | ), 104 | ), 105 | ), 106 | const SizedBox(height: 20), 107 | const Center( 108 | child: CurrentSongTitle( 109 | centered: true, 110 | )), 111 | Text("Mirchi Bangla", style: AppTextStyle.bodytext2), 112 | const SizedBox(height: 40), 113 | const Padding( 114 | padding: EdgeInsets.symmetric(horizontal: 20), 115 | child: AudioProgressBar(), 116 | ), 117 | const SizedBox(height: 20), 118 | const Padding( 119 | padding: EdgeInsets.symmetric(horizontal: 32), 120 | child: AudioControlButtons2(), 121 | ), 122 | ], 123 | ), 124 | ), 125 | ), 126 | bottomSheet: Container( 127 | padding: const EdgeInsets.symmetric(horizontal: 8), 128 | child: SolidBottomSheet( 129 | minHeight: 180, 130 | controller: _solidController, 131 | maxHeight: MediaQuery.of(context).size.height * 0.8, 132 | smoothness: Smoothness.high, 133 | // toggleVisibilityOnTap: true, 134 | headerBar: Container( 135 | height: 54, 136 | decoration: const BoxDecoration( 137 | borderRadius: BorderRadius.vertical(top: Radius.circular(12)), 138 | gradient: LinearGradient( 139 | begin: Alignment.topCenter, 140 | end: Alignment.bottomCenter, 141 | colors: [ 142 | AppColors.accentColor, 143 | AppColors.backgroundColor, 144 | ], 145 | ), 146 | ), 147 | child: Column( 148 | children: [ 149 | const SizedBox(height: 8), 150 | Container( 151 | height: 4, 152 | width: 40, 153 | decoration: BoxDecoration( 154 | color: AppColors.primaryWhiteColor, 155 | borderRadius: BorderRadius.circular(4), 156 | ), 157 | ), 158 | const SizedBox(height: 12), 159 | Text( 160 | "More Like This", 161 | style: AppTextStyle.subHeading, 162 | ), 163 | ], 164 | ), 165 | ), 166 | 167 | body: Container( 168 | color: AppColors.backgroundColor, 169 | padding: const EdgeInsets.symmetric(horizontal: 16), 170 | child: ValueListenableBuilder( 171 | valueListenable: pageManager.currentSongTitleNotifier, 172 | builder: (_, title, __) { 173 | if (title == '') { 174 | return const SizedBox( 175 | height: 200, 176 | child: Center( 177 | child: SizedBox(height: 50, child: LoadingAnimation()), 178 | ), 179 | ); 180 | } 181 | 182 | return FutureBuilder( 183 | future: DatabaseService.moreLikethis(title), 184 | builder: (context, snapshot) { 185 | if (snapshot.hasData) { 186 | List songs = snapshot.data!; 187 | return MinimalVerticalList( 188 | songs: songs, 189 | onExtraScroll: () { 190 | if (_solidController.isOpened) { 191 | print("pull down"); 192 | _solidController.hide(); 193 | } 194 | }, 195 | onFirstScroll: () { 196 | if (!_solidController.isOpened) { 197 | _solidController.show(); 198 | } 199 | }, 200 | ); 201 | } else { 202 | return const SizedBox( 203 | height: 200, 204 | child: Center( 205 | child: SizedBox(height: 50, child: LoadingAnimation()), 206 | ), 207 | ); 208 | } 209 | }, 210 | ); 211 | }), 212 | ), // Your body here 213 | ), 214 | ), 215 | ), 216 | ), 217 | ); 218 | } 219 | } 220 | --------------------------------------------------------------------------------