├── android
├── settings_aar.gradle
├── app
│ ├── .settings
│ │ └── org.eclipse.buildship.core.prefs
│ ├── src
│ │ ├── main
│ │ │ ├── res
│ │ │ │ ├── drawable-hdpi
│ │ │ │ │ ├── ic_clear.png
│ │ │ │ │ ├── ic_pause.png
│ │ │ │ │ ├── ic_stop.png
│ │ │ │ │ ├── ic_play_arrow.png
│ │ │ │ │ ├── ic_navigate_next.png
│ │ │ │ │ ├── ic_navigate_before.png
│ │ │ │ │ └── ic_stat_music_note.png
│ │ │ │ ├── drawable-mdpi
│ │ │ │ │ ├── ic_clear.png
│ │ │ │ │ ├── ic_pause.png
│ │ │ │ │ ├── ic_stop.png
│ │ │ │ │ ├── ic_play_arrow.png
│ │ │ │ │ ├── ic_navigate_next.png
│ │ │ │ │ ├── ic_navigate_before.png
│ │ │ │ │ └── ic_stat_music_note.png
│ │ │ │ ├── drawable-xhdpi
│ │ │ │ │ ├── ic_clear.png
│ │ │ │ │ ├── ic_pause.png
│ │ │ │ │ ├── ic_stop.png
│ │ │ │ │ ├── ic_play_arrow.png
│ │ │ │ │ ├── ic_navigate_before.png
│ │ │ │ │ ├── ic_navigate_next.png
│ │ │ │ │ └── ic_stat_music_note.png
│ │ │ │ ├── drawable-xxhdpi
│ │ │ │ │ ├── ic_stop.png
│ │ │ │ │ ├── ic_clear.png
│ │ │ │ │ ├── ic_pause.png
│ │ │ │ │ ├── ic_play_arrow.png
│ │ │ │ │ ├── ic_navigate_next.png
│ │ │ │ │ ├── ic_navigate_before.png
│ │ │ │ │ └── ic_stat_music_note.png
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── drawable-xxxhdpi
│ │ │ │ │ ├── ic_clear.png
│ │ │ │ │ ├── ic_pause.png
│ │ │ │ │ ├── ic_stop.png
│ │ │ │ │ ├── ic_play_arrow.png
│ │ │ │ │ ├── ic_navigate_next.png
│ │ │ │ │ ├── ic_navigate_before.png
│ │ │ │ │ └── ic_stat_music_note.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── drawable
│ │ │ │ │ ├── launch_background.xml
│ │ │ │ │ ├── ic_darktube.xml
│ │ │ │ │ └── ic_lighttube.xml
│ │ │ │ ├── drawable-night
│ │ │ │ │ └── launch_background.xml
│ │ │ │ └── values
│ │ │ │ │ └── styles.xml
│ │ │ └── AndroidManifest.xml
│ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ └── profile
│ │ │ └── AndroidManifest.xml
│ ├── .classpath
│ ├── .project
│ └── proguard-rules.pro
├── gradle.properties
├── .gitignore
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── .settings
│ └── org.eclipse.buildship.core.prefs
├── .project
├── settings.gradle
└── build.gradle
├── 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
│ ├── Base.lproj
│ │ ├── Main.storyboard
│ │ └── LaunchScreen.storyboard
│ └── Info.plist
├── Runner.xcworkspace
│ └── contents.xcworkspacedata
├── Runner.xcodeproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
└── .gitignore
├── assets
├── images
│ ├── logo.png
│ ├── airis.png
│ ├── github.png
│ ├── appReady.png
│ ├── appTheme.png
│ ├── facebook.png
│ ├── instagram.png
│ ├── playArrow.png
│ ├── telegram.png
│ ├── grantAccess.png
│ ├── ic_launcher.png
│ ├── logo_christmas.png
│ ├── youtube-music.png
│ ├── artworkPlaceholder_big.png
│ └── artworkPlaceholder_small.png
└── fonts
│ ├── productSans
│ ├── bold.ttf
│ └── regular.ttf
│ ├── youtube-sans-bold.ttf
│ ├── youtube-sans-light.ttf
│ └── youtube-sans-medium.ttf
├── lib
├── internal
│ ├── globals.dart
│ ├── models
│ │ ├── updateDetails.dart
│ │ ├── folder.dart
│ │ ├── videoFile.dart
│ │ ├── streamSegmentTrack.dart
│ │ ├── mediaItemSorts.dart
│ │ ├── songFile.dart
│ │ ├── subscription.dart
│ │ └── tagsControllers.dart
│ ├── download
│ │ ├── audioFilters.dart
│ │ └── tags.dart
│ ├── randomString.dart
│ ├── updateChecker.dart
│ ├── nativeMethods.dart
│ ├── lyricsProviders.dart
│ ├── systemUi.dart
│ ├── avatarHandler.dart
│ └── database
│ │ └── databaseService.dart
├── login
│ ├── registerPage.dart
│ └── loginPage.dart
├── players
│ ├── components
│ │ ├── youtubePlayer
│ │ │ ├── player
│ │ │ │ ├── gestures.dart
│ │ │ │ └── playPauseButton.dart
│ │ │ └── ui
│ │ │ │ ├── fab.dart
│ │ │ │ ├── details.dart
│ │ │ │ └── engagement.dart
│ │ ├── musicPlayer
│ │ │ ├── playerPadding.dart
│ │ │ ├── ui
│ │ │ │ ├── randomButton.dart
│ │ │ │ ├── repeatButton.dart
│ │ │ │ ├── marqueeWidget.dart
│ │ │ │ ├── playerBackground.dart
│ │ │ │ └── playerSlider.dart
│ │ │ └── expandedPanel.dart
│ │ └── videoPlayer
│ │ │ └── progressBar.dart
│ └── service
│ │ └── screenStateStream.dart
├── ui
│ ├── internal
│ │ ├── scrollBehavior.dart
│ │ ├── scrollDetector.dart
│ │ ├── lifecycleEvents.dart
│ │ ├── popupMenu.dart
│ │ └── snackbar.dart
│ ├── animations
│ │ ├── fadeSizeTransition.dart
│ │ ├── routeAnimations.dart
│ │ ├── FadeIn.dart
│ │ ├── showUp.dart
│ │ └── blurPageRoute.dart
│ ├── dialogs
│ │ ├── loadingDialog.dart
│ │ ├── alertdialog.dart
│ │ └── licenseDialog.dart
│ ├── components
│ │ ├── measureSize.dart
│ │ ├── emptyIndicator.dart
│ │ ├── textfieldTile.dart
│ │ ├── shimmerContainer.dart
│ │ ├── navigationBar.dart
│ │ ├── searchBar.dart
│ │ ├── autoHideScaffold.dart
│ │ └── searchHistory.dart
│ └── sheets
│ │ ├── downloadFix.dart
│ │ ├── disclaimer.dart
│ │ ├── searchFilters.dart
│ │ └── joinTelegram.dart
├── pages
│ ├── components
│ │ ├── channel
│ │ │ └── bottom_play_all_sheet.dart
│ │ ├── video
│ │ │ └── shimmer
│ │ │ │ ├── shimmerChannelLogo.dart
│ │ │ │ ├── shimmerVideoComments.dart
│ │ │ │ ├── shimmerArtworkEditor.dart
│ │ │ │ ├── shimmerVideoEngagement.dart
│ │ │ │ └── shimmerVideoTile.dart
│ │ └── localVideos
│ │ │ └── folderGridView.dart
│ ├── watchHistory.dart
│ └── localVideos.dart
├── screens
│ ├── homeScreen
│ │ └── pages
│ │ │ ├── trending.dart
│ │ │ ├── favorites.dart
│ │ │ └── watchLater.dart
│ ├── musicScreen
│ │ ├── components
│ │ │ ├── loadingListWidget.dart
│ │ │ ├── playlistEmpty.dart
│ │ │ ├── downloadsEmpty.dart
│ │ │ ├── mediaListBase.dart
│ │ │ └── noPermissionWidget.dart
│ │ ├── dialogs
│ │ │ ├── confirmDialog.dart
│ │ │ └── optionsMenuDialog.dart
│ │ └── tabs
│ │ │ ├── songs.dart
│ │ │ ├── genre.dart
│ │ │ ├── artist.dart
│ │ │ └── albums.dart
│ ├── libraryScreen
│ │ ├── socialLinksRow.dart
│ │ └── components
│ │ │ └── quickAcessTile.dart
│ └── downloadScreen
│ │ └── tabs
│ │ ├── downloadsTab.dart
│ │ ├── cancelledTab.dart
│ │ └── queueTab.dart
└── downloadMenu
│ └── components
│ └── loadingMenu.dart
├── .metadata
├── .gitignore
├── README.md
└── LICENSE
/android/settings_aar.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
--------------------------------------------------------------------------------
/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/assets/images/logo.png
--------------------------------------------------------------------------------
/assets/images/airis.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/assets/images/airis.png
--------------------------------------------------------------------------------
/assets/images/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/assets/images/github.png
--------------------------------------------------------------------------------
/assets/images/appReady.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/assets/images/appReady.png
--------------------------------------------------------------------------------
/assets/images/appTheme.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/assets/images/appTheme.png
--------------------------------------------------------------------------------
/assets/images/facebook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/assets/images/facebook.png
--------------------------------------------------------------------------------
/assets/images/instagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/assets/images/instagram.png
--------------------------------------------------------------------------------
/assets/images/playArrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/assets/images/playArrow.png
--------------------------------------------------------------------------------
/assets/images/telegram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/assets/images/telegram.png
--------------------------------------------------------------------------------
/android/app/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | connection.project.dir=..
2 | eclipse.preferences.version=1
3 |
--------------------------------------------------------------------------------
/assets/images/grantAccess.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/assets/images/grantAccess.png
--------------------------------------------------------------------------------
/assets/images/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/assets/images/ic_launcher.png
--------------------------------------------------------------------------------
/assets/images/logo_christmas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/assets/images/logo_christmas.png
--------------------------------------------------------------------------------
/assets/images/youtube-music.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/assets/images/youtube-music.png
--------------------------------------------------------------------------------
/assets/fonts/productSans/bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/assets/fonts/productSans/bold.ttf
--------------------------------------------------------------------------------
/assets/fonts/youtube-sans-bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/assets/fonts/youtube-sans-bold.ttf
--------------------------------------------------------------------------------
/assets/fonts/productSans/regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/assets/fonts/productSans/regular.ttf
--------------------------------------------------------------------------------
/assets/fonts/youtube-sans-light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/assets/fonts/youtube-sans-light.ttf
--------------------------------------------------------------------------------
/assets/fonts/youtube-sans-medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/assets/fonts/youtube-sans-medium.ttf
--------------------------------------------------------------------------------
/assets/images/artworkPlaceholder_big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/assets/images/artworkPlaceholder_big.png
--------------------------------------------------------------------------------
/assets/images/artworkPlaceholder_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/assets/images/artworkPlaceholder_small.png
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.enableR8=true
3 | android.useAndroidX=true
4 | android.enableJetifier=true
5 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/ic_clear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-hdpi/ic_clear.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/ic_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-hdpi/ic_pause.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/ic_stop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-hdpi/ic_stop.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/ic_clear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-mdpi/ic_clear.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/ic_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-mdpi/ic_pause.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/ic_stop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-mdpi/ic_stop.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/ic_clear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-xhdpi/ic_clear.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/ic_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-xhdpi/ic_pause.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/ic_stop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-xhdpi/ic_stop.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/ic_stop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_stop.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/ic_clear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_clear.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/ic_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_pause.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/ic_clear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_clear.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/ic_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_pause.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/ic_stop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_stop.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/ic_play_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-hdpi/ic_play_arrow.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/ic_play_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-mdpi/ic_play_arrow.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/ic_play_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-xhdpi/ic_play_arrow.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/ic_navigate_next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-hdpi/ic_navigate_next.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/ic_navigate_next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-mdpi/ic_navigate_next.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/ic_play_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_play_arrow.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/ic_play_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_play_arrow.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/ic_navigate_before.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-hdpi/ic_navigate_before.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/ic_stat_music_note.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-hdpi/ic_stat_music_note.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/ic_navigate_before.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-mdpi/ic_navigate_before.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/ic_stat_music_note.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-mdpi/ic_stat_music_note.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/ic_navigate_before.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-xhdpi/ic_navigate_before.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/ic_navigate_next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-xhdpi/ic_navigate_next.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/ic_stat_music_note.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-xhdpi/ic_stat_music_note.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/ic_navigate_next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_navigate_next.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/ic_navigate_next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_navigate_next.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/ic_navigate_before.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_navigate_before.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/ic_stat_music_note.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_stat_music_note.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/ic_navigate_before.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_navigate_before.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/ic_stat_music_note.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_stat_music_note.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/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/rwema3/SongTubeExactApp/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/rwema3/SongTubeExactApp/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/rwema3/SongTubeExactApp/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/rwema3/SongTubeExactApp/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/rwema3/SongTubeExactApp/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/rwema3/SongTubeExactApp/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/rwema3/SongTubeExactApp/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/rwema3/SongTubeExactApp/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/rwema3/SongTubeExactApp/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/rwema3/SongTubeExactApp/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/rwema3/SongTubeExactApp/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/rwema3/SongTubeExactApp/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/rwema3/SongTubeExactApp/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/SongTubeExactApp/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/rwema3/SongTubeExactApp/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/lib/internal/globals.dart:
--------------------------------------------------------------------------------
1 | import 'package:shared_preferences/shared_preferences.dart';
2 |
3 | // ------------------
4 | // Shared Preferences
5 | // ------------------
6 | SharedPreferences globalPrefs;
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/lib/login/registerPage.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class STRegisterPage extends StatelessWidget {
4 | @override
5 | Widget build(BuildContext context) {
6 | return Container(
7 |
8 | );
9 | }
10 | }
--------------------------------------------------------------------------------
/lib/players/components/youtubePlayer/player/gestures.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class PlayerGestures extends StatelessWidget {
4 | @override
5 | Widget build(BuildContext context) {
6 | return Container(
7 |
8 | );
9 | }
10 | }
--------------------------------------------------------------------------------
/lib/ui/internal/scrollBehavior.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class CustomScrollBehavior extends ScrollBehavior {
4 | @override
5 | Widget buildViewportChrome(
6 | BuildContext context, Widget child, AxisDirection axisDirection) {
7 | return child;
8 | }
9 | }
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
7 |
--------------------------------------------------------------------------------
/lib/login/loginPage.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'package:flutter/material.dart';
4 |
5 | class STLoginPage extends StatelessWidget {
6 | @override
7 | Widget build(BuildContext context) {
8 | return Container(
9 |
10 | );
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/lib/internal/models/updateDetails.dart:
--------------------------------------------------------------------------------
1 | class UpdateDetails {
2 |
3 | String version;
4 | String publishDate;
5 | String updateDetails;
6 | String downloadUrl;
7 |
8 | UpdateDetails(
9 | this.version,
10 | this.publishDate,
11 | this.updateDetails,
12 | this.downloadUrl
13 | );
14 |
15 | }
--------------------------------------------------------------------------------
/lib/internal/download/audioFilters.dart:
--------------------------------------------------------------------------------
1 | class AudioFilters {
2 |
3 | double volume;
4 | int bassGain;
5 | int trebleGain;
6 | bool normalizeAudio;
7 |
8 | AudioFilters({
9 | this.volume = 1,
10 | this.bassGain = 0,
11 | this.trebleGain = 0,
12 | this.normalizeAudio = false
13 | });
14 |
15 | }
--------------------------------------------------------------------------------
/lib/internal/models/folder.dart:
--------------------------------------------------------------------------------
1 | // Internal
2 | import 'package:songtube/internal/models/videoFile.dart';
3 |
4 | class FolderItem {
5 |
6 | String name;
7 | String path;
8 | List videos;
9 |
10 | FolderItem({
11 | this.name,
12 | this.path,
13 | }) {
14 | videos = [];
15 | }
16 |
17 | }
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: f139b11009aeb8ed2a3a3aa8b0066e482709dde3
8 | channel: stable
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/lib/internal/download/tags.dart:
--------------------------------------------------------------------------------
1 | class DownloadTags {
2 |
3 | String title;
4 | String album;
5 | String artist;
6 | String genre;
7 | String coverurl;
8 | String date;
9 | String disc;
10 | String track;
11 |
12 | DownloadTags({this.title, this.album, this.artist, this.genre,
13 | this.coverurl, this.date, this.disc, this.track});
14 |
15 | }
--------------------------------------------------------------------------------
/lib/internal/models/videoFile.dart:
--------------------------------------------------------------------------------
1 | class VideoFile {
2 |
3 | String name;
4 | String path;
5 | DateTime lastModified;
6 | String thumbnail;
7 | String duration;
8 | String size;
9 |
10 | VideoFile({
11 | this.name,
12 | this.path,
13 | this.lastModified,
14 | this.thumbnail,
15 | this.duration,
16 | this.size
17 | });
18 |
19 | }
--------------------------------------------------------------------------------
/lib/internal/models/streamSegmentTrack.dart:
--------------------------------------------------------------------------------
1 | import 'package:newpipeextractor_dart/newpipeextractor_dart.dart';
2 | import 'package:songtube/internal/models/tagsControllers.dart';
3 |
4 | class StreamSegmentTrack {
5 |
6 | StreamSegment segment;
7 | TagsControllers tags;
8 | bool selected;
9 |
10 | StreamSegmentTrack(this.segment, this.tags, this.selected);
11 |
12 | }
--------------------------------------------------------------------------------
/android/app/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/android/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | arguments=
2 | auto.sync=false
3 | build.scans.enabled=false
4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
5 | connection.project.dir=
6 | eclipse.preferences.version=1
7 | gradle.user.home=
8 | java.home=C\:/Program Files/Java/jdk1.8.0_241
9 | jvm.arguments=
10 | offline.mode=false
11 | override.workspace.settings=true
12 | show.console.view=true
13 | show.executions.view=true
14 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/lib/pages/components/channel/bottom_play_all_sheet.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class ChannelPageBottomSheet extends StatelessWidget {
4 | const ChannelPageBottomSheet({
5 | @required this.onPlayAll,
6 | @required this.onDownloadAll,
7 | Key key }) : super(key: key);
8 | final Function() onPlayAll;
9 | final Function() onDownloadAll;
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return Container(
14 |
15 | );
16 | }
17 | }
--------------------------------------------------------------------------------
/android/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | android
4 | Project android created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.buildship.core.gradleprojectbuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.buildship.core.gradleprojectnature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/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/screens/homeScreen/pages/trending.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 | import 'package:songtube/provider/managerProvider.dart';
4 | import 'package:songtube/ui/layout/streamsLargeThumbnail.dart';
5 |
6 | class HomePageTrending extends StatelessWidget {
7 | @override
8 | Widget build(BuildContext context) {
9 | ManagerProvider manager = Provider.of(context);
10 | return StreamsLargeThumbnailView(
11 | infoItems: manager.homeTrendingVideoList
12 | );
13 | }
14 | }
--------------------------------------------------------------------------------
/lib/ui/animations/fadeSizeTransition.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class FadeSizeTransition extends StatelessWidget {
4 | final Animation animator;
5 | final Widget child;
6 | FadeSizeTransition({
7 | @required this.animator,
8 | @required this.child
9 | });
10 | @override
11 | Widget build(BuildContext context) {
12 | return FadeTransition(
13 | opacity: animator,
14 | child: SizeTransition(
15 | sizeFactor: animator,
16 | child: child,
17 | ),
18 | );
19 | }
20 | }
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
4 |
5 | def plugins = new Properties()
6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
7 | if (pluginsFile.exists()) {
8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
9 | }
10 |
11 | plugins.each { name, path ->
12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
13 | include ":$name"
14 | project(":$name").projectDir = pluginDirectory
15 | }
16 |
--------------------------------------------------------------------------------
/lib/internal/randomString.dart:
--------------------------------------------------------------------------------
1 | // Dart
2 | import 'dart:math';
3 |
4 | class RandomString {
5 | static const _chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890';
6 | static const _letters = 'qwertyuiopasdfghjlcvbnm';
7 | static String getRandomString(int length) => String.fromCharCodes(Iterable.generate(
8 | length, (_) => _chars.codeUnitAt(Random().nextInt(_chars.length))));
9 | static String getRandomLetter() => String.fromCharCodes(Iterable.generate(
10 | 1, (_) => _letters
11 | .codeUnitAt(Random().nextInt(_letters.length))
12 | ));
13 | }
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | *.mode1v3
2 | *.mode2v3
3 | *.moved-aside
4 | *.pbxuser
5 | *.perspectivev3
6 | **/*sync/
7 | .sconsign.dblite
8 | .tags*
9 | **/.vagrant/
10 | **/DerivedData/
11 | Icon?
12 | **/Pods/
13 | **/.symlinks/
14 | profile
15 | xcuserdata
16 | **/.generated/
17 | Flutter/App.framework
18 | Flutter/Flutter.framework
19 | Flutter/Flutter.podspec
20 | Flutter/Generated.xcconfig
21 | Flutter/app.flx
22 | Flutter/app.zip
23 | Flutter/flutter_assets/
24 | Flutter/flutter_export_environment.sh
25 | ServiceDefinitions.json
26 | Runner/GeneratedPluginRegistrant.*
27 |
28 | # Exceptions to above rules.
29 | !default.mode1v3
30 | !default.mode2v3
31 | !default.pbxuser
32 | !default.perspectivev3
33 |
--------------------------------------------------------------------------------
/android/app/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | app
4 | Project app created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 | org.eclipse.buildship.core.gradleprojectbuilder
15 |
16 |
17 |
18 |
19 |
20 | org.eclipse.jdt.core.javanature
21 | org.eclipse.buildship.core.gradleprojectnature
22 |
23 |
24 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.4.10'
3 | repositories {
4 | google()
5 | jcenter()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:4.1.2'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | jcenter()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
--------------------------------------------------------------------------------
/lib/pages/components/video/shimmer/shimmerChannelLogo.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:shimmer/shimmer.dart';
3 |
4 | class ShimmerChannelLogo extends StatelessWidget {
5 | const ShimmerChannelLogo();
6 | @override
7 | Widget build(BuildContext context) {
8 | return Shimmer.fromColors(
9 | baseColor: Theme.of(context).cardColor.withOpacity(0.4),
10 | highlightColor: Theme.of(context).cardColor,
11 | child: Container(
12 | width: 60,
13 | height: 60,
14 | decoration: BoxDecoration(
15 | borderRadius: BorderRadius.circular(50),
16 | color: Theme.of(context).cardColor.withOpacity(0.4)
17 | ),
18 | ),
19 | );
20 | }
21 | }
--------------------------------------------------------------------------------
/lib/internal/models/mediaItemSorts.dart:
--------------------------------------------------------------------------------
1 | import 'package:audio_service/audio_service.dart';
2 |
3 | class MediaItemAlbum {
4 |
5 | String albumTitle;
6 | String albumAuthor;
7 | List mediaItems;
8 |
9 | MediaItemAlbum({
10 | this.albumAuthor,
11 | this.albumTitle,
12 | this.mediaItems
13 | });
14 |
15 | }
16 |
17 | class MediaItemArtist {
18 |
19 | String artistName;
20 | List mediaItems;
21 |
22 | MediaItemArtist({
23 | this.artistName,
24 | this.mediaItems
25 | });
26 |
27 | }
28 |
29 | class MediaItemGenre {
30 |
31 | String genreName;
32 | List mediaItems;
33 |
34 | MediaItemGenre({
35 | this.genreName,
36 | this.mediaItems
37 | });
38 |
39 | }
--------------------------------------------------------------------------------
/lib/players/service/screenStateStream.dart:
--------------------------------------------------------------------------------
1 | // Internal
2 | import 'package:songtube/players/service/playerService.dart';
3 |
4 | // Packages
5 | import 'package:audio_service/audio_service.dart';
6 | import 'package:rxdart/rxdart.dart';
7 |
8 | /// Encapsulate all the different data we're interested in into a single
9 | /// stream so we don't have to nest StreamBuilders.
10 | Stream get screenStateStream =>
11 | Rx.combineLatest3, MediaItem, PlaybackState, ScreenState>(
12 | AudioService.queueStream,
13 | AudioService.currentMediaItemStream,
14 | AudioService.playbackStateStream,
15 | (queue, mediaItem, playbackState) =>
16 | ScreenState(queue, mediaItem, playbackState));
--------------------------------------------------------------------------------
/lib/ui/dialogs/loadingDialog.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class LoadingDialog extends StatelessWidget {
4 | @override
5 | Widget build(BuildContext context) {
6 | return Container(
7 | height: 50,
8 | width: 50,
9 | child: Dialog(
10 | shape: RoundedRectangleBorder(
11 | borderRadius: BorderRadius.circular(20)
12 | ),
13 | backgroundColor: Colors.transparent,
14 | elevation: 0,
15 | child: Container(
16 | height: 100,
17 | width: 100,
18 | child: Center(
19 | child: CircularProgressIndicator(
20 | valueColor: AlwaysStoppedAnimation(Colors.white),
21 | ),
22 | ),
23 | )
24 | ),
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 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | .dart_tool/
26 | .flutter-plugins
27 | .flutter-plugins-dependencies
28 | .packages
29 | .pub-cache/
30 | .pub/
31 | /build/
32 |
33 | # Web related
34 | lib/generated_plugin_registrant.dart
35 |
36 | # Exceptions to above rules.
37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
38 |
39 | # Signing
40 | songtube.jks
41 | key.properties
--------------------------------------------------------------------------------
/lib/ui/animations/routeAnimations.dart:
--------------------------------------------------------------------------------
1 | // Flutter
2 | import 'package:flutter/material.dart';
3 |
4 | class FadeRoute extends PageRouteBuilder {
5 | final Widget page;
6 | FadeRoute({this.page})
7 | : super(
8 | pageBuilder: (
9 | BuildContext context,
10 | Animation animation,
11 | Animation secondaryAnimation,
12 | ) =>
13 | page,
14 | transitionDuration: Duration(milliseconds: 300),
15 | transitionsBuilder: (
16 | BuildContext context,
17 | Animation animation,
18 | Animation secondaryAnimation,
19 | Widget child,
20 | ) =>
21 | FadeTransition(
22 | opacity: animation,
23 | child: child,
24 | ),
25 | );
26 | }
--------------------------------------------------------------------------------
/lib/players/components/youtubePlayer/player/playPauseButton.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class PlayPauseButton extends StatelessWidget {
4 | final bool isPlaying;
5 | final Function onPlayPause;
6 | PlayPauseButton({
7 | @required this.isPlaying,
8 | @required this.onPlayPause
9 | });
10 | @override
11 | Widget build(BuildContext context) {
12 | return InkWell(
13 | onTap: onPlayPause,
14 | borderRadius: BorderRadius.circular(100),
15 | child: Ink(
16 | padding: const EdgeInsets.all(16.0),
17 | child: isPlaying
18 | ? Icon(
19 | Icons.pause,
20 | size: 32,
21 | color: Colors.white,
22 | )
23 | : Icon(
24 | Icons.play_arrow,
25 | size: 32,
26 | color: Colors.white,
27 | ),
28 | ),
29 | );
30 | }
31 | }
--------------------------------------------------------------------------------
/lib/downloadMenu/components/loadingMenu.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class LoadingDownloadMenu extends StatelessWidget {
4 | @override
5 | Widget build(BuildContext context) {
6 | return Row(
7 | mainAxisAlignment: MainAxisAlignment.start,
8 | crossAxisAlignment: CrossAxisAlignment.center,
9 | children: [
10 | SizedBox(width: 16),
11 | CircularProgressIndicator(
12 | valueColor: AlwaysStoppedAnimation(
13 | Theme.of(context).accentColor
14 | ),
15 | ),
16 | SizedBox(width: 16),
17 | Text(
18 | "Loading...",
19 | style: TextStyle(
20 | fontFamily: 'YTSans',
21 | color: Theme.of(context).textTheme.bodyText1.color,
22 | fontWeight: FontWeight.w600,
23 | fontSize: 18
24 | ),
25 | )
26 | ],
27 | );
28 | }
29 | }
--------------------------------------------------------------------------------
/lib/ui/internal/scrollDetector.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class ScrollDetector extends StatelessWidget {
4 | final Function onScrollUp;
5 | final Function onScrollDown;
6 | final Widget child;
7 | ScrollDetector({
8 | @required this.child,
9 | this.onScrollDown,
10 | this.onScrollUp
11 | });
12 | final sensitivityFactor = 5;
13 | @override
14 | Widget build(BuildContext context) {
15 | return NotificationListener(
16 | onNotification: (ScrollUpdateNotification details) {
17 | if (details.scrollDelta.abs() < sensitivityFactor)
18 | return false;
19 | if (details.scrollDelta > 0.0 && details.metrics.axis == Axis.vertical) {
20 | onScrollDown();
21 | } else {
22 | onScrollUp();
23 | }
24 | return false;
25 | },
26 | child: child,
27 | );
28 | }
29 | }
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 8.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/lib/internal/updateChecker.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'package:http/http.dart' as http;
3 | import 'package:songtube/internal/models/updateDetails.dart';
4 |
5 | Future getLatestRelease() async {
6 | try {
7 | var client = http.Client();
8 | var headers = {
9 | "Accept": "application/vnd.github.v3+json"
10 | };
11 | var response = await client
12 | .get(Uri.parse("https://api.github.com/repos/SongTube/SongTube-App/releases"),
13 | headers: headers);
14 | var jsonResponse = jsonDecode(response.body);
15 | UpdateDetails details = UpdateDetails(
16 | jsonResponse[0]["tag_name"],
17 | jsonResponse[0]["published_at"].split("T").first,
18 | jsonResponse[0]["body"],
19 | jsonResponse[0]["assets"][0]["browser_download_url"]
20 | );
21 | client.close();
22 | return details;
23 | } catch (_) {
24 | return null;
25 | }
26 | }
27 |
28 | Future queryUpdate() async {
29 |
30 | }
--------------------------------------------------------------------------------
/lib/players/components/youtubePlayer/ui/fab.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class VideoDownloadFab extends StatelessWidget {
4 | final bool readyToDownload;
5 | final Function onDownload;
6 | VideoDownloadFab({
7 | @required this.readyToDownload,
8 | @required this.onDownload,
9 | });
10 | @override
11 | Widget build(BuildContext context) {
12 | return FloatingActionButton(
13 | child: AnimatedSwitcher(
14 | duration: Duration(milliseconds: 400),
15 | child: readyToDownload
16 | ? Icon(Icons.file_download)
17 | : CircularProgressIndicator(
18 | backgroundColor: Theme.of(context).accentColor,
19 | valueColor: AlwaysStoppedAnimation(Colors.white),
20 | ),
21 | ),
22 | backgroundColor: Theme.of(context).accentColor,
23 | foregroundColor: Colors.white,
24 | onPressed: () {
25 | if (readyToDownload) {
26 | onDownload();
27 | }
28 | }
29 | );
30 | }
31 | }
--------------------------------------------------------------------------------
/lib/pages/components/video/shimmer/shimmerVideoComments.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:shimmer/shimmer.dart';
3 |
4 | class ShimmerVideoComments extends StatelessWidget {
5 | const ShimmerVideoComments();
6 | @override
7 | Widget build(BuildContext context) {
8 | return Container(
9 | height: 60,
10 | margin: EdgeInsets.only(left: 12),
11 | child: Center(
12 | child: Align(
13 | alignment: Alignment.centerLeft,
14 | child: Shimmer.fromColors(
15 | baseColor: Theme.of(context).cardColor.withOpacity(0.4),
16 | highlightColor: Theme.of(context).cardColor,
17 | child: Container(
18 | width: 150,
19 | height: 50,
20 | decoration: BoxDecoration(
21 | borderRadius: BorderRadius.circular(50),
22 | color: Theme.of(context).cardColor.withOpacity(0.4)
23 | ),
24 | ),
25 | ),
26 | ),
27 | ),
28 | );
29 | }
30 | }
--------------------------------------------------------------------------------
/lib/screens/musicScreen/components/loadingListWidget.dart:
--------------------------------------------------------------------------------
1 | // Flutter
2 | import 'package:flutter/material.dart';
3 | import 'package:songtube/internal/languages.dart';
4 |
5 | class MediaLoadingWidget extends StatelessWidget {
6 | const MediaLoadingWidget();
7 | @override
8 | Widget build(BuildContext context) {
9 | return Center(
10 | child: Column(
11 | mainAxisAlignment: MainAxisAlignment.center,
12 | children: [
13 | CircularProgressIndicator(
14 | backgroundColor: Theme.of(context).scaffoldBackgroundColor,
15 | valueColor: AlwaysStoppedAnimation(Theme.of(context).accentColor),
16 | ),
17 | Container(
18 | margin: EdgeInsets.only(top: 16),
19 | child: Text(
20 | Languages.of(context).labelGettingYourMedia,
21 | style: TextStyle(
22 | fontFamily: 'YTSans',
23 | fontSize: 20
24 | ),
25 | ),
26 | )
27 | ],
28 | ),
29 | );
30 | }
31 | }
--------------------------------------------------------------------------------
/lib/ui/dialogs/alertdialog.dart:
--------------------------------------------------------------------------------
1 | // Flutter
2 | import 'package:flutter/material.dart';
3 |
4 | class CustomAlert extends StatelessWidget {
5 | final Icon leadingIcon;
6 | final String title;
7 | final String content;
8 | final List actions;
9 | CustomAlert({
10 | this.leadingIcon,
11 | @required this.title,
12 | @required this.content,
13 | @required this.actions
14 | });
15 | @override
16 | Widget build(BuildContext context) {
17 | return AlertDialog(
18 | shape: RoundedRectangleBorder(
19 | borderRadius: BorderRadius.circular(10),
20 | ),
21 | title: Row(
22 | children: [
23 | leadingIcon,
24 | SizedBox(width: 6),
25 | Text(title, style: TextStyle(
26 | color: Theme.of(context).textTheme.bodyText1.color
27 | )),
28 | ],
29 | ),
30 | content: Text(content, style: TextStyle(
31 | color: Theme.of(context).textTheme.bodyText1.color
32 | )),
33 | actions: actions
34 | );
35 | }
36 | }
--------------------------------------------------------------------------------
/lib/ui/internal/lifecycleEvents.dart:
--------------------------------------------------------------------------------
1 | // Flutter
2 | import 'package:flutter/foundation.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | // Add this to any Widget in the App to detect if
6 | // the app is inactive, paused, detached or resumed
7 | class LifecycleEventHandler extends WidgetsBindingObserver {
8 | final AsyncCallback resumeCallBack;
9 | final AsyncCallback suspendingCallBack;
10 |
11 | LifecycleEventHandler({
12 | this.resumeCallBack,
13 | this.suspendingCallBack
14 | });
15 |
16 | @override
17 | Future didChangeAppLifecycleState(AppLifecycleState state) async {
18 | switch (state) {
19 | case AppLifecycleState.resumed:
20 | if (resumeCallBack != null) {
21 | await resumeCallBack();
22 | }
23 | break;
24 | case AppLifecycleState.inactive:
25 | case AppLifecycleState.paused:
26 | case AppLifecycleState.detached:
27 | if (suspendingCallBack != null) {
28 | await suspendingCallBack();
29 | }
30 | break;
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/ic_darktube.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
8 |
9 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/ic_lighttube.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
8 |
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | SongTube is a new beautiful and fast application made in flutter, it supports YouTube audio and video downloading at any quality, In-App YouTube Browser, audio conversion, Playlists and an Audio tags editor.
4 |
5 | ---
6 |
7 | ## Features
8 |
9 | + Video Download at any available Quality
10 | + Download HDR and 60fps Videos
11 | + Audio Download at best available Quality
12 | + Audio Tags & Artwork Editor
13 | + Audio Filters (Volume, Bass, Treble)
14 | + Audio Conversion (AAC, OGG and MP3) (optional)
15 | + Full Playlist Downloads (Only Audio)
16 | + Set custom path for Audio/Video download
17 | + Music Player built-in
18 | + Video Player built-in
19 | + Music Equalizer
20 | + Music Playlists
21 | + Youtube Videos Playlists
22 | + In-App Youtube Browser
23 | + Light/Dark/Black Themes
24 | + Accent Color Picker
25 | + UI Customizations
26 |
27 | ---
28 |
29 | ## Developer's Info
30 |
31 | >Bagirishya Rwema Dominique
32 |
33 | - Twitter: (Click [@Here](https://twitter.com/R_w_e_m_a))
34 | - GitHub Page (Click [@Here](https://github.com/rwema3))
35 |
36 |
--------------------------------------------------------------------------------
/lib/ui/components/measureSize.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/scheduler.dart';
3 |
4 | typedef void OnWidgetSizeChange(Size size);
5 |
6 | class MeasureSize extends StatefulWidget {
7 | final Widget child;
8 | final OnWidgetSizeChange onChange;
9 |
10 | const MeasureSize({
11 | Key key,
12 | @required this.onChange,
13 | @required this.child,
14 | }) : super(key: key);
15 |
16 | @override
17 | _MeasureSizeState createState() => _MeasureSizeState();
18 | }
19 |
20 | class _MeasureSizeState extends State {
21 | @override
22 | Widget build(BuildContext context) {
23 | SchedulerBinding.instance.addPostFrameCallback(postFrameCallback);
24 | return Container(
25 | key: widgetKey,
26 | child: widget.child,
27 | );
28 | }
29 |
30 | var widgetKey = GlobalKey();
31 | var oldSize;
32 |
33 | void postFrameCallback(_) {
34 | var context = widgetKey.currentContext;
35 | if (context == null) return;
36 |
37 | var newSize = context.size;
38 | if (oldSize == newSize) return;
39 |
40 | oldSize = newSize;
41 | widget.onChange(newSize);
42 | }
43 | }
--------------------------------------------------------------------------------
/lib/players/components/musicPlayer/playerPadding.dart:
--------------------------------------------------------------------------------
1 | // Flutter
2 | import 'package:flutter/material.dart';
3 |
4 | // Internal
5 | import 'package:songtube/players/service/screenStateStream.dart';
6 | import 'package:songtube/players/service/playerService.dart';
7 |
8 | // Packages
9 | import 'package:audio_service/audio_service.dart';
10 |
11 | class MusicPlayerPadding extends StatelessWidget {
12 | final bool searchBarOpen;
13 | MusicPlayerPadding(this.searchBarOpen);
14 | @override
15 | Widget build(BuildContext context) {
16 | return StreamBuilder(
17 | stream: screenStateStream,
18 | builder: (context, snapshot) {
19 | final screenState = snapshot.data;
20 | final state = screenState?.playbackState;
21 | final processingState =
22 | state?.processingState ?? AudioProcessingState.none;
23 | return Container(
24 | height: searchBarOpen
25 | ? 0 : processingState == AudioProcessingState.stopped ||
26 | processingState == AudioProcessingState.none
27 | ? 0
28 | : kToolbarHeight * 1.15
29 | );
30 | }
31 | );
32 | }
33 | }
--------------------------------------------------------------------------------
/lib/screens/musicScreen/dialogs/confirmDialog.dart:
--------------------------------------------------------------------------------
1 | // Flutter
2 | import 'package:flutter/material.dart';
3 |
4 | class ConfirmDialog extends StatelessWidget {
5 | final Function onConfirm;
6 | final Function onCancel;
7 | ConfirmDialog({
8 | @required this.onConfirm,
9 | @required this.onCancel
10 | });
11 | @override
12 | Widget build(BuildContext context) {
13 | return AlertDialog(
14 | shape: RoundedRectangleBorder(
15 | borderRadius: BorderRadius.circular(10)
16 | ),
17 | title: Text(
18 | "Are you sure?",
19 | style: TextStyle(color: Theme.of(context).textTheme.bodyText1.color)
20 | ),
21 | content: Text(
22 | "You are going to permanently delete this Song",
23 | style: TextStyle(color: Theme.of(context).textTheme.bodyText1.color)
24 | ),
25 | actions: [
26 | TextButton(
27 | child: Text("OK", style: TextStyle(color: Theme.of(context).accentColor)),
28 | onPressed: onConfirm
29 | ),
30 | TextButton(
31 | child: Text("Cancel", style: TextStyle(color: Theme.of(context).accentColor)),
32 | onPressed: onCancel,
33 | )
34 | ],
35 | );
36 | }
37 | }
--------------------------------------------------------------------------------
/lib/screens/libraryScreen/socialLinksRow.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:url_launcher/url_launcher.dart';
3 |
4 | class SocialLinksRow extends StatelessWidget {
5 | @override
6 | Widget build(BuildContext context) {
7 | return Container(
8 | margin: EdgeInsets.only(left: 12, right: 12, top: 16),
9 | height: 50,
10 | child: Row(
11 | mainAxisAlignment: MainAxisAlignment.spaceAround,
12 | children: [
13 | GestureDetector(
14 | onTap: () => launch("https://t.me/songtubechannel"),
15 | child: Image.asset('assets/images/telegram.png')
16 | ),
17 | GestureDetector(
18 | onTap: () => launch("https://github.com/SongTube"),
19 | child: Image.asset('assets/images/github.png')
20 | ),
21 | GestureDetector(
22 | onTap: () => launch("https://facebook.com/songtubeapp/"),
23 | child: Image.asset('assets/images/facebook.png')
24 | ),
25 | GestureDetector(
26 | onTap: () => launch("https://instagram.com/songtubeapp"),
27 | child: Image.asset('assets/images/instagram.png')
28 | ),
29 | ],
30 | ),
31 | );
32 | }
33 | }
--------------------------------------------------------------------------------
/lib/screens/downloadScreen/tabs/downloadsTab.dart:
--------------------------------------------------------------------------------
1 | // Flutter
2 | import 'package:flutter/material.dart';
3 |
4 | // Internal
5 | import 'package:songtube/provider/mediaProvider.dart';
6 | import 'package:songtube/screens/musicScreen/components/mediaListBase.dart';
7 |
8 | // Packages
9 | import 'package:provider/provider.dart';
10 | import 'package:songtube/screens/musicScreen/components/songsList.dart';
11 |
12 | // UI
13 | import 'package:songtube/screens/musicScreen/tabs/songs.dart';
14 |
15 | class DownloadsTab extends StatefulWidget {
16 | @override
17 | _DownloadsTabState createState() => _DownloadsTabState();
18 | }
19 |
20 | class _DownloadsTabState extends State {
21 |
22 | @override
23 | void initState() {
24 | Provider.of(context, listen: false).getDatabase();
25 | super.initState();
26 | }
27 |
28 | @override
29 | Widget build(BuildContext context) {
30 | MediaProvider mediaProvider = Provider.of(context);
31 | return MediaListBase(
32 | isLoading: mediaProvider.loadingDownloads,
33 | isEmpty: mediaProvider.databaseSongs.isEmpty,
34 | listType: MediaListBaseType.Downloads,
35 | child: SongsListView(
36 | songs: mediaProvider.databaseSongs,
37 | hasDownloadType: true,
38 | ),
39 | );
40 | }
41 | }
--------------------------------------------------------------------------------
/lib/screens/musicScreen/dialogs/optionsMenuDialog.dart:
--------------------------------------------------------------------------------
1 | import 'package:audio_service/audio_service.dart';
2 | import 'package:eva_icons_flutter/eva_icons_flutter.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:songtube/internal/languages.dart';
5 |
6 | enum DeleteFrom { downloads, music }
7 |
8 | class MediaOptionsMenuDialog extends StatelessWidget {
9 | final MediaItem song;
10 | final Function onDelete;
11 | MediaOptionsMenuDialog({
12 | @required this.song,
13 | @required this.onDelete
14 | });
15 | @override
16 | Widget build(BuildContext context) {
17 | return AlertDialog(
18 | shape: RoundedRectangleBorder(
19 | borderRadius: BorderRadius.circular(20)
20 | ),
21 | content: Column(
22 | mainAxisSize: MainAxisSize.min,
23 | children: [
24 | ListTile(
25 | leading: Icon(EvaIcons.trashOutline, color: Theme.of(context).accentColor),
26 | title: Text(
27 | Languages.of(context).labelDeleteSong,
28 | style: TextStyle(
29 | fontFamily: 'YTSans',
30 | color: Theme.of(context).textTheme.bodyText1.color.withOpacity(0.6)
31 | ),
32 | ),
33 | onTap: onDelete
34 | ),
35 | ],
36 | ),
37 | );
38 | }
39 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Boost Software License - Version 1.0 - August 17th, 2003
2 |
3 | Permission is hereby granted, free of charge, to any person or organization
4 | obtaining a copy of the software and accompanying documentation covered by
5 | this license (the "Software") to use, reproduce, display, distribute,
6 | execute, and transmit the Software, and to prepare derivative works of the
7 | Software, and to permit third-parties to whom the Software is furnished to
8 | do so, all subject to the following:
9 |
10 | The copyright notices in the Software and this entire statement, including
11 | the above license grant, this restriction and the following disclaimer,
12 | must be included in all copies of the Software, in whole or in part, and
13 | all derivative works of the Software, unless such copies or derivative
14 | works are solely in the form of machine-executable object code generated by
15 | a source language processor.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 | DEALINGS IN THE SOFTWARE.
24 |
--------------------------------------------------------------------------------
/lib/ui/dialogs/licenseDialog.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/services.dart' show rootBundle;
3 |
4 | class LicenseDialog extends StatelessWidget {
5 | @override
6 | Widget build(BuildContext context) {
7 | return AlertDialog(
8 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
9 | title: Text("LICENSE", style: TextStyle(
10 | color: Theme.of(context).textTheme.bodyText1.color,
11 | )),
12 | content: FutureBuilder(
13 | future: getLicense(),
14 | builder: (context, AsyncSnapshot license) {
15 | if (license.hasData) {
16 | return SingleChildScrollView(
17 | child: Text(license.data, style: TextStyle(
18 | color: Theme.of(context).textTheme.bodyText1.color,
19 | )),
20 | );
21 | } else {
22 | return Center(child: CircularProgressIndicator());
23 | }
24 | },
25 | ),
26 | actions: [
27 | TextButton(
28 | onPressed: () {
29 | Navigator.pop(context);
30 | },
31 | child: Text("OK", style: TextStyle(
32 | color: Theme.of(context).accentColor,
33 | )),
34 | )
35 | ],
36 | );
37 | }
38 |
39 | Future getLicense() async {
40 | return await rootBundle.loadString('assets/LICENSE');
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/lib/ui/components/emptyIndicator.dart:
--------------------------------------------------------------------------------
1 | // Flutter
2 | import 'package:flutter/material.dart';
3 | import 'package:songtube/internal/languages.dart';
4 | import 'package:songtube/ui/animations/showUp.dart';
5 |
6 | class EmptyIndicator extends StatelessWidget {
7 | const EmptyIndicator();
8 | @override
9 | Widget build(BuildContext context) {
10 | return ShowUpTransition(
11 | duration: Duration(milliseconds: 600),
12 | delay: Duration(milliseconds: 400),
13 | forward: true,
14 | slideSide: SlideFromSlide.TOP,
15 | child: Container(
16 | height: 30,
17 | margin: EdgeInsets.all(16),
18 | width: double.infinity,
19 | decoration: BoxDecoration(
20 | borderRadius: BorderRadius.circular(10),
21 | color: Theme.of(context).accentColor,
22 | boxShadow: [
23 | BoxShadow(
24 | color: Colors.black.withOpacity(0.2),
25 | offset: Offset(3,3),
26 | blurRadius: 8,
27 | spreadRadius: 1
28 | )
29 | ]
30 | ),
31 | child: Center(
32 | child: Text(
33 | Languages.of(context).labelEmpty,
34 | style: TextStyle(
35 | color: Colors.white,
36 | fontWeight: FontWeight.w600,
37 | fontFamily: 'Product Sans',
38 | fontSize: 14,
39 | ),
40 | ),
41 | ),
42 | ),
43 | );
44 | }
45 | }
--------------------------------------------------------------------------------
/lib/screens/musicScreen/tabs/songs.dart:
--------------------------------------------------------------------------------
1 | import 'package:audio_service/audio_service.dart';
2 | import 'package:flutter/foundation.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:provider/provider.dart';
5 | import 'package:songtube/provider/mediaProvider.dart';
6 | import 'package:songtube/screens/musicScreen/components/songsList.dart';
7 |
8 |
9 | class MusicScreenSongsTab extends StatefulWidget {
10 | final List songs;
11 | final bool hasDownloadType;
12 | final String searchQuery;
13 | MusicScreenSongsTab({
14 | @required this.songs,
15 | this.hasDownloadType = false,
16 | this.searchQuery = ""
17 | });
18 |
19 | @override
20 | State createState() => _MusicScreenSongsTabState();
21 | }
22 |
23 | class _MusicScreenSongsTabState extends State {
24 |
25 | // Scroll Controller
26 | ScrollController controller;
27 |
28 | @override
29 | void initState() {
30 | controller = ScrollController(initialScrollOffset:
31 | Provider.of(context, listen: false).musicScrollPosition);
32 | controller.addListener(() {
33 | Provider.of(context, listen: false)
34 | .musicScrollPosition = controller.position.pixels;
35 | });
36 | super.initState();
37 | }
38 |
39 | @override
40 | Widget build(BuildContext context) {
41 | return SongsListView(
42 | scrollController: controller,
43 | songs: widget.songs, searchQuery: widget.searchQuery);
44 | }
45 | }
--------------------------------------------------------------------------------
/lib/screens/musicScreen/components/playlistEmpty.dart:
--------------------------------------------------------------------------------
1 | import 'package:eva_icons_flutter/eva_icons_flutter.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:songtube/internal/languages.dart';
4 | import 'package:songtube/ui/animations/showUp.dart';
5 |
6 | class PlaylistEmptyWidget extends StatelessWidget {
7 | const PlaylistEmptyWidget();
8 | @override
9 | Widget build(BuildContext context) {
10 | return ShowUpTransition(
11 | duration: Duration(milliseconds: 400),
12 | slideSide: SlideFromSlide.BOTTOM,
13 | forward: true,
14 | child: Container(
15 | padding: EdgeInsets.all(8),
16 | height: 240,
17 | width: MediaQuery.of(context).size.width*0.6,
18 | child: Padding(
19 | padding: const EdgeInsets.all(24.0),
20 | child: Column(
21 | mainAxisSize: MainAxisSize.min,
22 | children: [
23 | Icon(EvaIcons.loaderOutline, size: 100, color: Theme.of(context).accentColor),
24 | SizedBox(height: 8),
25 | Text(
26 | Languages.of(context).labelNoMediaYet,
27 | style: TextStyle(
28 | fontWeight: FontWeight.w700,
29 | fontFamily: 'Varela'
30 | ),
31 | ),
32 | SizedBox(height: 4),
33 | Text(Languages.of(context).labelNoMediaYetJustification,
34 | textAlign: TextAlign.center),
35 | ],
36 | ),
37 | ),
38 | ),
39 | );
40 | }
41 | }
--------------------------------------------------------------------------------
/lib/screens/musicScreen/components/downloadsEmpty.dart:
--------------------------------------------------------------------------------
1 | import 'package:eva_icons_flutter/eva_icons_flutter.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:songtube/internal/languages.dart';
4 | import 'package:songtube/ui/animations/showUp.dart';
5 |
6 | class MediaDownloadsEmpty extends StatelessWidget {
7 | const MediaDownloadsEmpty();
8 | @override
9 | Widget build(BuildContext context) {
10 | return ShowUpTransition(
11 | duration: Duration(milliseconds: 400),
12 | slideSide: SlideFromSlide.BOTTOM,
13 | forward: true,
14 | child: Container(
15 | padding: EdgeInsets.all(8),
16 | height: 240,
17 | width: MediaQuery.of(context).size.width*0.6,
18 | child: Padding(
19 | padding: const EdgeInsets.all(24.0),
20 | child: Column(
21 | mainAxisSize: MainAxisSize.min,
22 | children: [
23 | Icon(EvaIcons.cloudDownloadOutline, size: 100, color: Theme.of(context).accentColor),
24 | SizedBox(height: 8),
25 | Text(
26 | Languages.of(context).labelNoMediaYet,
27 | style: TextStyle(
28 | fontWeight: FontWeight.w700,
29 | fontFamily: 'Varela'
30 | ),
31 | ),
32 | SizedBox(height: 4),
33 | Text(Languages.of(context).labelNoMediaYetJustification,
34 | textAlign: TextAlign.center),
35 | ],
36 | ),
37 | ),
38 | ),
39 | );
40 | }
41 | }
--------------------------------------------------------------------------------
/lib/ui/components/textfieldTile.dart:
--------------------------------------------------------------------------------
1 | // Flutter
2 | import 'package:flutter/material.dart';
3 |
4 | class TextFieldTile extends StatelessWidget {
5 | final TextEditingController textController;
6 | final String labelText;
7 | final IconData icon;
8 | final TextInputType inputType;
9 | TextFieldTile({
10 | @required this.textController,
11 | @required this.labelText,
12 | @required this.icon,
13 | @required this.inputType
14 | });
15 | @override
16 | Widget build(BuildContext context) {
17 | return ClipRRect(
18 | borderRadius: BorderRadius.circular(100),
19 | child: TextField(
20 | keyboardType: inputType,
21 | cursorColor: Theme.of(context).accentColor,
22 | controller: textController,
23 | decoration: InputDecoration(
24 | prefixIcon: Icon(icon,
25 | color: Theme.of(context).iconTheme.color
26 | ),
27 | filled: true,
28 | fillColor: Theme.of(context).scaffoldBackgroundColor,
29 | border: UnderlineInputBorder(
30 | borderSide: BorderSide(
31 | width: 0,
32 | style: BorderStyle.none,
33 | ),
34 | ),
35 | labelText: labelText,
36 | labelStyle: TextStyle(
37 | fontWeight: FontWeight.w600,
38 | color: Theme.of(context).accentColor,
39 | )
40 | ),
41 | style: TextStyle(
42 | color: Theme.of(context).textTheme.bodyText1.color,
43 | fontSize: 16,
44 | ),
45 | ),
46 | );
47 | }
48 | }
--------------------------------------------------------------------------------
/lib/pages/components/video/shimmer/shimmerArtworkEditor.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:shimmer/shimmer.dart';
3 |
4 | class ShimmerArtworkEditor extends StatelessWidget {
5 | const ShimmerArtworkEditor();
6 | @override
7 | Widget build(BuildContext context) {
8 | return Container(
9 | height: 60,
10 | margin: EdgeInsets.only(left: 12, right: 12),
11 | child: Center(
12 | child: Row(
13 | children: [
14 | Shimmer.fromColors(
15 | baseColor: Theme.of(context).cardColor.withOpacity(0.4),
16 | highlightColor: Theme.of(context).cardColor,
17 | child: Container(
18 | decoration: BoxDecoration(
19 | borderRadius: BorderRadius.circular(20),
20 | color: Theme.of(context).cardColor.withOpacity(0.4)
21 | ),
22 | width: 100,
23 | height: 50,
24 | ),
25 | ),
26 | Spacer(),
27 | Shimmer.fromColors(
28 | baseColor: Theme.of(context).cardColor.withOpacity(0.4),
29 | highlightColor: Theme.of(context).cardColor,
30 | child: Container(
31 | decoration: BoxDecoration(
32 | borderRadius: BorderRadius.circular(20),
33 | color: Theme.of(context).cardColor.withOpacity(0.4)
34 | ),
35 | width: 100,
36 | height: 50,
37 | ),
38 | ),
39 | ],
40 | ),
41 | ),
42 | );
43 | }
44 | }
--------------------------------------------------------------------------------
/lib/ui/sheets/downloadFix.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:google_fonts/google_fonts.dart';
3 | import 'package:songtube/internal/languages.dart';
4 | import 'package:songtube/internal/nativeMethods.dart';
5 | import 'package:songtube/ui/components/styledBottomSheet.dart';
6 |
7 | class DownloadFixSheet extends StatelessWidget {
8 | @override
9 | Widget build(BuildContext context) {
10 | return StyledBottomSheet(
11 | title: Languages.of(context).labelAndroid11Detected,
12 | content: Text(
13 | Languages.of(context).labelAndroid11DetectedJustification,
14 | style: GoogleFonts.poppins(
15 | color: Theme.of(context).textTheme.bodyText1.color,
16 | fontSize: 16,
17 | ),
18 | ),
19 | actions: [
20 | TextButton(
21 | child: Text(
22 | "Allow",
23 | style: GoogleFonts.poppins(
24 | color: Theme.of(context).accentColor,
25 | fontSize: 18,
26 | fontWeight: FontWeight.w600,
27 | ),
28 | ),
29 | onPressed: () {
30 | NativeMethod.requestAllFilesPermission();
31 | Navigator.pop(context);
32 | },
33 | ),
34 | TextButton(
35 | child: Text(
36 | "Not Now",
37 | style: GoogleFonts.poppins(
38 | color: Theme.of(context).accentColor,
39 | fontSize: 18,
40 | fontWeight: FontWeight.w600,
41 | ),
42 | ),
43 | onPressed: () => Navigator.pop(context)
44 | )
45 | ],
46 | );
47 | }
48 | }
--------------------------------------------------------------------------------
/lib/screens/homeScreen/pages/favorites.dart:
--------------------------------------------------------------------------------
1 | import 'package:eva_icons_flutter/eva_icons_flutter.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:newpipeextractor_dart/models/infoItems/video.dart';
4 | import 'package:provider/provider.dart';
5 | import 'package:songtube/provider/preferencesProvider.dart';
6 | import 'package:songtube/ui/components/emptyIndicator.dart';
7 | import 'package:songtube/ui/internal/snackbar.dart';
8 | import 'package:songtube/ui/layout/streamsLargeThumbnail.dart';
9 |
10 | class HomePageFavorites extends StatelessWidget {
11 | @override
12 | Widget build(BuildContext context) {
13 | PreferencesProvider prefs = Provider.of(context);
14 | return AnimatedSwitcher(
15 | duration: Duration(milliseconds: 300),
16 | child: prefs.favoriteVideos.isNotEmpty
17 | ? StreamsLargeThumbnailView(
18 | infoItems: prefs.favoriteVideos,
19 | allowSaveToFavorites: false,
20 | allowSaveToWatchLater: true,
21 | onDelete: (infoItem) {
22 | List videos = prefs.favoriteVideos;
23 | videos.removeWhere((element) => element.url == infoItem.url);
24 | prefs.favoriteVideos = videos;
25 | AppSnack.showSnackBar(
26 | icon: EvaIcons.alertCircleOutline,
27 | title: "Video removed from Favorites",
28 | context: context,
29 | );
30 | },
31 | )
32 | : Container(
33 | alignment: Alignment.topCenter,
34 | child: EmptyIndicator()
35 | )
36 | );
37 | }
38 | }
--------------------------------------------------------------------------------
/lib/screens/homeScreen/pages/watchLater.dart:
--------------------------------------------------------------------------------
1 | import 'package:eva_icons_flutter/eva_icons_flutter.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:newpipeextractor_dart/models/infoItems/video.dart';
4 | import 'package:provider/provider.dart';
5 | import 'package:songtube/provider/preferencesProvider.dart';
6 | import 'package:songtube/ui/components/emptyIndicator.dart';
7 | import 'package:songtube/ui/internal/snackbar.dart';
8 | import 'package:songtube/ui/layout/streamsLargeThumbnail.dart';
9 |
10 | class HomePageWatchLater extends StatelessWidget {
11 | @override
12 | Widget build(BuildContext context) {
13 | PreferencesProvider prefs = Provider.of(context);
14 | return AnimatedSwitcher(
15 | duration: Duration(milliseconds: 300),
16 | child: prefs.watchLaterVideos.isNotEmpty
17 | ? StreamsLargeThumbnailView(
18 | infoItems: prefs.watchLaterVideos,
19 | allowSaveToFavorites: true,
20 | allowSaveToWatchLater: false,
21 | onDelete: (infoItem) {
22 | List videos = prefs.watchLaterVideos;
23 | videos.removeWhere((element) => element.url == infoItem.url);
24 | prefs.watchLaterVideos = videos;
25 | AppSnack.showSnackBar(
26 | icon: EvaIcons.alertCircleOutline,
27 | title: "Video removed from Watch Later",
28 | context: context,
29 | );
30 | },
31 | )
32 | : Container(
33 | alignment: Alignment.topCenter,
34 | child: EmptyIndicator()
35 | )
36 | );
37 | }
38 | }
--------------------------------------------------------------------------------
/lib/ui/components/shimmerContainer.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:shimmer/shimmer.dart';
3 |
4 | class ShimmerContainer extends StatelessWidget {
5 | final double height;
6 | final double width;
7 | final BorderRadiusGeometry borderRadius;
8 | final EdgeInsetsGeometry margin;
9 | final double aspectRatio;
10 | ShimmerContainer({
11 | this.height,
12 | this.width,
13 | this.borderRadius,
14 | this.margin,
15 | this.aspectRatio
16 | });
17 | @override
18 | Widget build(BuildContext context) {
19 | return Shimmer.fromColors(
20 | baseColor: Theme.of(context).scaffoldBackgroundColor.withOpacity(0.6),
21 | highlightColor: Theme.of(context).cardColor,
22 | child: aspectRatio != null
23 | ? AspectRatio(
24 | aspectRatio: aspectRatio,
25 | child: Container(
26 | height: height,
27 | width: width,
28 | margin: margin == null ? EdgeInsets.zero : margin,
29 | decoration: BoxDecoration(
30 | borderRadius: borderRadius == null ? BorderRadius.zero : borderRadius,
31 | color: Theme.of(context).scaffoldBackgroundColor
32 | ),
33 | ),
34 | )
35 | : Container(
36 | height: height,
37 | width: width,
38 | margin: margin == null ? EdgeInsets.zero : margin,
39 | decoration: BoxDecoration(
40 | borderRadius: borderRadius == null ? BorderRadius.zero : borderRadius,
41 | color: Theme.of(context).scaffoldBackgroundColor
42 | ),
43 | ),
44 | );
45 | }
46 | }
--------------------------------------------------------------------------------
/lib/screens/libraryScreen/components/quickAcessTile.dart:
--------------------------------------------------------------------------------
1 | // Flutter
2 | import 'package:flutter/material.dart';
3 |
4 | // Packages
5 | import 'package:eva_icons_flutter/eva_icons_flutter.dart';
6 |
7 | class QuickAccessTile extends StatelessWidget {
8 | final Icon tileIcon;
9 | final String title;
10 | final Function onTap;
11 | QuickAccessTile({
12 | @required this.tileIcon,
13 | @required this.title,
14 | @required this.onTap
15 | });
16 | @override
17 | Widget build(BuildContext context) {
18 | return GestureDetector(
19 | onTap: onTap,
20 | child: Padding(
21 | padding: EdgeInsets.only(
22 | left: 32,
23 | right: 32,
24 | top: 8,
25 | bottom: 8
26 | ),
27 | child: Container(
28 | color: Theme.of(context).scaffoldBackgroundColor,
29 | height: kToolbarHeight,
30 | child: Row(
31 | children: [
32 | SizedBox(width: 32),
33 | tileIcon,
34 | SizedBox(width: 8),
35 | Text(
36 | title,
37 | style: TextStyle(
38 | fontSize: 15,
39 | fontFamily: "Varela",
40 | fontWeight: FontWeight.w700,
41 | color: Theme.of(context).iconTheme.color
42 | ),
43 | ),
44 | Spacer(),
45 | Container(
46 | padding: EdgeInsets.all(8),
47 | child: Icon(EvaIcons.arrowForwardOutline)
48 | ),
49 | SizedBox(width: 32)
50 | ],
51 | ),
52 | ),
53 | ),
54 | );
55 | }
56 | }
--------------------------------------------------------------------------------
/lib/internal/nativeMethods.dart:
--------------------------------------------------------------------------------
1 | // Dart
2 | import 'dart:io';
3 |
4 | // Flutter
5 | import 'package:flutter/services.dart';
6 |
7 | class NativeMethod {
8 |
9 | static const media = const MethodChannel("registerMedia");
10 | static const platform = const MethodChannel("sharedTextChannel");
11 | static const intentPlatform = const MethodChannel("intentChannel");
12 | static const imageProcessing = const MethodChannel("imageProcessing");
13 |
14 | // Handle Intent (Ej: when you share a YouTube link to this app)
15 | static Future handleIntent() async {
16 | String _intent = await platform.invokeMethod('getSharedText');
17 | await platform.invokeMethod('clearSharedText');
18 | if (_intent == null) return null;
19 | print("IntentHandler: Result: " + _intent);
20 | return _intent;
21 | }
22 |
23 | // Exit FullScreen
24 | static Future exitFullScreen() async {
25 | await platform.invokeMethod('exitFullScreen');
26 | }
27 |
28 | // Update android MediaStore with a new File
29 | // This allows music/video players to detect the new media
30 | static void registerFile(String file) async {
31 | await media.invokeMethod('registerFile', {"file":file});
32 | }
33 |
34 | // Open a local video with the default video player
35 | static void openVideo(String videoPath) async {
36 | if (await File(videoPath).exists()) {
37 | intentPlatform.invokeMethod('openVideo', {"videoPath": videoPath});
38 | }
39 | }
40 |
41 | // (TEMP FIX) Request All Files Access to The App
42 | // to fix Downloads on Android 11
43 | static void requestAllFilesPermission() {
44 | intentPlatform.invokeListMethod('requestAllFilesPermission');
45 | }
46 | }
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | songtube
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | $(FLUTTER_BUILD_NAME)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UIViewControllerBasedStatusBarAppearance
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/lib/ui/sheets/disclaimer.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:google_fonts/google_fonts.dart';
3 | import 'package:songtube/ui/components/styledBottomSheet.dart';
4 | import 'package:songtube/ui/dialogs/licenseDialog.dart';
5 |
6 | class DisclaimerSheet extends StatelessWidget {
7 | @override
8 | Widget build(BuildContext context) {
9 | return StyledBottomSheet(
10 | title: "Disclaimer",
11 | content: Text(
12 | "This Software is released \"as-is\", without any warranty, responsibility or liability. " +
13 | "In no event shall the Author of this Software be liable for any special, consequential, " +
14 | "incidental or indirect damages whatsoever (including, without limitation, damages for " +
15 | "loss of business profits, business interruption, loss of business information, or any " +
16 | "other pecuniary loss) arising out of the use of inability to use this product, even if " +
17 | "Author of this Sotware is aware of the possibility of such damages and known defect.",
18 | style: GoogleFonts.poppins(
19 | color: Theme.of(context).textTheme.bodyText1.color,
20 | fontSize: 16,
21 | ),
22 | ),
23 | actions: [
24 | TextButton(
25 | onPressed: () {
26 | showDialog(
27 | context: context,
28 | builder: (context) => LicenseDialog()
29 | );
30 | },
31 | child: Text("License", style: GoogleFonts.poppins(
32 | color: Theme.of(context).accentColor,
33 | fontWeight: FontWeight.w600,
34 | fontSize: 18
35 | )),
36 | ),
37 | ],
38 | );
39 | }
40 | }
--------------------------------------------------------------------------------
/lib/players/components/musicPlayer/ui/randomButton.dart:
--------------------------------------------------------------------------------
1 | // Flutter
2 | import 'package:flutter/material.dart';
3 |
4 | // Packages
5 | import 'package:audio_service/audio_service.dart';
6 | import 'package:eva_icons_flutter/eva_icons_flutter.dart';
7 |
8 | class MusicPlayerRandomButton extends StatefulWidget {
9 | final Color iconColor;
10 | final Color enabledColor;
11 | MusicPlayerRandomButton({
12 | @required this.iconColor,
13 | @required this.enabledColor
14 | });
15 | @override
16 | _MusicPlayerRandomButtonState createState() => _MusicPlayerRandomButtonState();
17 | }
18 |
19 | class _MusicPlayerRandomButtonState extends State {
20 |
21 | // Button Status
22 | bool enabled = false;
23 |
24 | @override
25 | Widget build(BuildContext context) {
26 | return AnimatedContainer(
27 | margin: EdgeInsets.only(right: 8),
28 | duration: Duration(milliseconds: 50),
29 | decoration: BoxDecoration(
30 | borderRadius: BorderRadius.circular(50),
31 | boxShadow: [
32 | BoxShadow(
33 | color: enabled
34 | ? widget.enabledColor.withOpacity(0.3)
35 | : Colors.transparent,
36 | spreadRadius: 0.1,
37 | blurRadius: 15
38 | )
39 | ]
40 | ),
41 | child: IconButton(
42 | icon: Icon(
43 | EvaIcons.shuffle2Outline,
44 | size: 16,
45 | color: enabled
46 | ? widget.enabledColor
47 | : widget.iconColor.withOpacity(0.7)
48 | ),
49 | onPressed: () async {
50 | enabled = await AudioService.customAction("enableRandom");
51 | setState(() {});
52 | }
53 | ),
54 | );
55 | }
56 | }
--------------------------------------------------------------------------------
/lib/players/components/musicPlayer/ui/repeatButton.dart:
--------------------------------------------------------------------------------
1 | // Flutter
2 | import 'package:flutter/material.dart';
3 |
4 | // Packages
5 | import 'package:audio_service/audio_service.dart';
6 | import 'package:eva_icons_flutter/eva_icons_flutter.dart';
7 |
8 | class MusicPlayerRepeatButton extends StatefulWidget {
9 | final Color iconColor;
10 | final Color enabledColor;
11 | MusicPlayerRepeatButton({
12 | @required this.iconColor,
13 | @required this.enabledColor
14 | });
15 | @override
16 | _MusicPlayerRepeatButtonState createState() => _MusicPlayerRepeatButtonState();
17 | }
18 |
19 | class _MusicPlayerRepeatButtonState extends State {
20 |
21 | // Button Status
22 | bool enabled = false;
23 |
24 | @override
25 | Widget build(BuildContext context) {
26 | return AnimatedContainer(
27 | duration: Duration(milliseconds: 50),
28 | margin: EdgeInsets.only(left: 8),
29 | decoration: BoxDecoration(
30 | borderRadius: BorderRadius.circular(50),
31 | boxShadow: [
32 | BoxShadow(
33 | color: enabled
34 | ? widget.enabledColor.withOpacity(0.3)
35 | : Colors.transparent,
36 | spreadRadius: 0.1,
37 | blurRadius: 15
38 | )
39 | ]
40 | ),
41 | child: IconButton(
42 | icon: Icon(
43 | EvaIcons.repeatOutline,
44 | size: 16,
45 | color: enabled
46 | ? widget.enabledColor
47 | : widget.iconColor.withOpacity(0.7)
48 | ),
49 | onPressed: () async {
50 | enabled = await AudioService.customAction("enableRepeat");
51 | setState(() {});
52 | }
53 | ),
54 | );
55 | }
56 | }
--------------------------------------------------------------------------------
/lib/internal/lyricsProviders.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:http/http.dart';
4 |
5 | class LyricsProviders {
6 |
7 | static Future lyricsOvh({String author, String title}) async {
8 | Client client = Client();
9 | Response response;
10 | try {
11 | response = await client.get(Uri.parse(
12 | "https://api.lyrics.ovh/v1/"
13 | "${author.replaceAll('&', '')}/"
14 | "${title.replaceAll('&', '')}")
15 | ).timeout(Duration(seconds: 5));
16 | } catch (_) {
17 | return "";
18 | }
19 | client.close();
20 | if ((jsonDecode(response.body) as Map).containsKey('error')) {
21 | return "";
22 | } else {
23 | return jsonDecode(response.body)["lyrics"];
24 | }
25 | }
26 |
27 | static final happiDevKey = "e1de5fbTOztuNxXBGZ1m39MbY0SPfUUQQm2pbLSdEADsMMm1duk4xQBa";
28 |
29 | static Future lyricsHappiDev({String title}) async {
30 | Client client = Client();
31 | var response = await client.get(Uri.parse(
32 | "https://api.happi.dev/v1/music?q=${title.replaceAll('&', '')}"
33 | "&limit=1&apikey=$happiDevKey&type=track")
34 | );
35 | var responseJson = jsonDecode(response.body);
36 | if (responseJson["success"] == true) {
37 | var lyricsResponse = await client.get(
38 | responseJson["result"][0]["api_lyrics"] +
39 | "?apikey=$happiDevKey"
40 | );
41 | var lyricsJson = jsonDecode(lyricsResponse.body);
42 | if (lyricsJson["success"] == true) {
43 | return lyricsJson["result"]["lyrics"];
44 | } else {
45 | client.close();
46 | return "";
47 | }
48 | } else {
49 | client.close();
50 | return "";
51 | }
52 | }
53 |
54 |
55 |
56 | }
--------------------------------------------------------------------------------
/lib/ui/animations/FadeIn.dart:
--------------------------------------------------------------------------------
1 | // Dart
2 | import 'dart:async';
3 |
4 | // Flutter
5 | import 'package:flutter/material.dart';
6 |
7 | class FadeInTransition extends StatefulWidget {
8 | /// [child] to be Animated
9 | final Widget child;
10 | /// Animation Duration, default is 200 Milliseconds
11 | final Duration duration;
12 | /// Animation Curve, default is Linear
13 | final Curve curve;
14 | /// Delay before starting Animation
15 | final Duration delay;
16 |
17 | FadeInTransition({
18 | @required this.child,
19 | this.duration,
20 | this.curve,
21 | this.delay
22 | });
23 |
24 | @override
25 | _FadeInTransitionState createState() => _FadeInTransitionState();
26 | }
27 |
28 | class _FadeInTransitionState extends State with TickerProviderStateMixin {
29 | AnimationController _animController;
30 |
31 | @override
32 | void initState() {
33 | super.initState();
34 | _animController =
35 | AnimationController(vsync: this, duration: widget.duration == null
36 | ? Duration(milliseconds: 200)
37 | : widget.duration
38 | );
39 | if (widget.delay == null) {
40 | _animController.forward();
41 | } else {
42 | Timer(widget.delay, () {
43 | _animController.forward();
44 | });
45 | }
46 | }
47 |
48 | @override
49 | void dispose() {
50 | _animController.dispose();
51 | super.dispose();
52 | }
53 |
54 | @override
55 | Widget build(BuildContext context) {
56 | return FadeTransition(
57 | opacity: new CurvedAnimation(
58 | parent: _animController,
59 | curve: widget.curve == null
60 | ? Curves.linear
61 | : widget.curve
62 | ),
63 | child: widget.child,
64 | );
65 | }
66 | }
--------------------------------------------------------------------------------
/lib/internal/models/songFile.dart:
--------------------------------------------------------------------------------
1 | // Dart
2 | import 'dart:core';
3 |
4 | // Flutter
5 | import 'package:flutter/material.dart';
6 |
7 | class SongFile {
8 |
9 | String id;
10 | String title;
11 | String album;
12 | String author;
13 | String duration;
14 | String downloadType;
15 | String path;
16 | String fileSize;
17 | String coverUrl;
18 | String coverPath;
19 |
20 | SongFile({
21 | @required this.id,
22 | @required this.title,
23 | @required this.album,
24 | @required this.author,
25 | @required this.duration,
26 | @required this.downloadType,
27 | @required this.path,
28 | @required this.fileSize,
29 | @required this.coverUrl,
30 | this.coverPath,
31 | });
32 |
33 | SongFile.toDatabase({
34 | @required this.title,
35 | @required this.album,
36 | @required this.author,
37 | @required this.duration,
38 | @required this.downloadType,
39 | @required this.path,
40 | @required this.fileSize,
41 | @required this.coverUrl,
42 | });
43 |
44 | SongFile.fromMap(Map map) {
45 | id = map["id"].toString();
46 | title = map["title"];
47 | album = map["album"];
48 | author = map["author"];
49 | duration = map["duration"].toString();
50 | downloadType = map["downloadType"];
51 | path = map["path"];
52 | fileSize = map["fileSize"].toString();
53 | coverUrl = map["coverUrl"];
54 | }
55 |
56 | Map toMap() {
57 | return {
58 | "title": this.title,
59 | "album": this.album,
60 | "author": this.author,
61 | "duration": this.duration.toString(),
62 | "downloadType": this.downloadType,
63 | "path": this.path,
64 | "fileSize": this.fileSize,
65 | "coverUrl": this.coverUrl,
66 | };
67 | }
68 | }
--------------------------------------------------------------------------------
/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | -keep class org.schabi.newpipe.extractor.** { *; }
2 | -keep class org.ocpsoft.prettytime.i18n.** { *; }
3 |
4 | -keep class org.mozilla.javascript.** { *; }
5 |
6 | -keep class org.mozilla.classfile.ClassFileWriter
7 | -keep class com.google.android.exoplayer2.** { *; }
8 |
9 | -dontwarn org.mozilla.javascript.tools.**
10 | -dontwarn android.arch.util.paging.CountedDataSource
11 | -dontwarn android.arch.persistence.room.paging.LimitOffsetDataSource
12 | -keep class com.artxdev.** { *; }
13 | -keep class androidx.lifecycle.** { *; }
14 | -keep class org.jaudiotagger.** { *; }
15 | -keep class com.example.audio_tagger.** { *; }
16 | -dontobfuscate
17 |
18 | ## Flutter wrapper
19 | -keep class io.flutter.app.** { *; }
20 | -keep class io.flutter.plugin.** { *; }
21 | -keep class io.flutter.util.** { *; }
22 | -keep class io.flutter.view.** { *; }
23 | -keep class io.flutter.** { *; }
24 | -keep class io.flutter.plugins.** { *; }
25 |
26 | # -keep class com.google.firebase.** { *; } // uncomment this if you are using firebase in the project
27 |
28 | -keep class com.arthenica.mobileffmpeg.Config {
29 | native ;
30 | void log(long, int, byte[]);
31 | void statistics(long, int, float, float, long , int, double, double);
32 | }
33 |
34 | -keep class com.arthenica.mobileffmpeg.AbiDetect {
35 | native ;
36 | }
37 |
38 | # Rules for OkHttp. Copy paste from https://github.com/square/okhttp
39 | -dontwarn okhttp3.**
40 | -dontwarn okio.**
41 | -dontwarn javax.annotation.**
42 | # A resource is loaded with a relative path so the package of this class must be preserved.
43 | -keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
44 | -keepclassmembers class * implements java.io.Serializable {
45 | static final long serialVersionUID;
46 | !static !transient ;
47 | private void writeObject(java.io.ObjectOutputStream);
48 | private void readObject(java.io.ObjectInputStream);
49 | }
--------------------------------------------------------------------------------
/lib/ui/internal/popupMenu.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class FlexiblePopupItem {
4 |
5 | String title;
6 | String value;
7 |
8 | FlexiblePopupItem({
9 | this.title,
10 | this.value
11 | });
12 |
13 | }
14 |
15 | class FlexiblePopupMenu extends StatelessWidget {
16 | final Widget child;
17 | final List items;
18 | final Function(String) onItemTap;
19 | final double borderRadius;
20 | final EdgeInsetsGeometry padding;
21 | FlexiblePopupMenu({
22 | @required this.child,
23 | @required this.items,
24 | @required this.onItemTap,
25 | this.borderRadius,
26 | this.padding
27 | });
28 | @override
29 | Widget build(BuildContext context) {
30 | return GestureDetector(
31 | onTapDown: (details) {
32 | showMenu(
33 | color: Theme.of(context).popupMenuTheme.color,
34 | shape: RoundedRectangleBorder(
35 | borderRadius: borderRadius == null
36 | ? BorderRadius.zero
37 | : BorderRadius.circular(borderRadius),
38 | ),
39 | context: context,
40 | position: RelativeRect.fromLTRB(
41 | details.globalPosition.dx,
42 | details.globalPosition.dy,
43 | 0, 0
44 | ),
45 | items: items.map((e) {
46 | return PopupMenuItem(
47 | child: Text(
48 | e.title, style: TextStyle(
49 | color: Theme.of(context)
50 | .textTheme.bodyText1.color,
51 | fontSize: 14
52 | ),
53 | ),
54 | value: "${e.value}",
55 | );
56 | }).toList()
57 | ).then((value) {
58 | onItemTap(value);
59 | });
60 | },
61 | child: Padding(
62 | padding: padding == null ? EdgeInsets.zero : padding,
63 | child: child
64 | ),
65 | );
66 | }
67 | }
--------------------------------------------------------------------------------
/lib/players/components/musicPlayer/ui/marqueeWidget.dart:
--------------------------------------------------------------------------------
1 | // Flutter
2 | import 'package:flutter/material.dart';
3 |
4 | class MarqueeWidget extends StatefulWidget {
5 | final Widget child;
6 | final Axis direction;
7 | final Duration animationDuration, backDuration, pauseDuration;
8 |
9 | MarqueeWidget({
10 | @required this.child,
11 | this.direction: Axis.horizontal,
12 | this.animationDuration: const Duration(milliseconds: 3000),
13 | this.backDuration: const Duration(milliseconds: 800),
14 | this.pauseDuration: const Duration(milliseconds: 800),
15 | });
16 |
17 | @override
18 | _MarqueeWidgetState createState() => _MarqueeWidgetState();
19 | }
20 |
21 | class _MarqueeWidgetState extends State {
22 | ScrollController scrollController;
23 |
24 | @override
25 | void initState() {
26 | scrollController = ScrollController(initialScrollOffset: 0);
27 | WidgetsBinding.instance.addPostFrameCallback(scroll);
28 | super.initState();
29 | }
30 |
31 | @override
32 | void dispose(){
33 | scrollController.dispose();
34 | super.dispose();
35 | }
36 |
37 | @override
38 | Widget build(BuildContext context) {
39 | return SingleChildScrollView(
40 | child: widget.child,
41 | scrollDirection: widget.direction,
42 | controller: scrollController,
43 | );
44 | }
45 |
46 | void scroll(_) async {
47 | while (scrollController.hasClients) {
48 | await Future.delayed(widget.pauseDuration);
49 | if(scrollController.hasClients)
50 | await scrollController.animateTo(
51 | scrollController.position.maxScrollExtent,
52 | duration: widget.animationDuration,
53 | curve: Curves.ease);
54 | await Future.delayed(widget.pauseDuration);
55 | if(scrollController.hasClients)
56 | await scrollController.animateTo(0.0,
57 | duration: widget.backDuration, curve: Curves.easeOut);
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/lib/internal/models/subscription.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:newpipeextractor_dart/newpipeextractor_dart.dart';
4 |
5 | class ChannelSubscription {
6 |
7 | String url;
8 | String id;
9 | String name;
10 | String avatarUrl;
11 | DateTime date;
12 | bool enableNotifications;
13 |
14 | ChannelSubscription(
15 | this.url,
16 | this.id,
17 | this.name,
18 | this.avatarUrl,
19 | this.date,
20 | this.enableNotifications
21 | );
22 |
23 | static ChannelSubscription generateFromChannel(YoutubeChannel channel) {
24 | return ChannelSubscription(
25 | channel.url,
26 | channel.id,
27 | channel.name,
28 | channel.avatarUrl,
29 | DateTime.now(),
30 | false
31 | );
32 | }
33 |
34 | Map toMap() {
35 | return {
36 | 'url': url,
37 | 'id': id,
38 | 'name': name,
39 | 'avatarUrl': avatarUrl,
40 | 'date': date.toString(),
41 | 'enableNotifications': enableNotifications.toString()
42 | };
43 | }
44 |
45 | static ChannelSubscription fromMap(map) {
46 | return ChannelSubscription(
47 | map['url'],
48 | map['id'],
49 | map['name'],
50 | map['avatarUrl'],
51 | DateTime.parse(map['date']),
52 | map['enableNotifications'] == "true" ? true : false
53 | );
54 | }
55 |
56 | static String toJsonList(List channels) {
57 | if (channels == null || channels.isEmpty) return "";
58 | return jsonEncode(List.generate(channels.length, (index) {
59 | return channels[index].toMap();
60 | }).toList());
61 | }
62 |
63 | static List fromJsonList(String json) {
64 | if (json == null || json == "") return [];
65 | var channelsMap = jsonDecode(json);
66 | return channelsMap.isNotEmpty
67 | ? List.generate(channelsMap.length, (index) {
68 | return ChannelSubscription.fromMap(channelsMap[index]);
69 | }).toList()
70 | : [];
71 | }
72 |
73 | }
--------------------------------------------------------------------------------
/lib/screens/musicScreen/components/mediaListBase.dart:
--------------------------------------------------------------------------------
1 | // Flutter
2 | import 'package:flutter/material.dart';
3 | import 'package:permission_handler/permission_handler.dart';
4 | import 'package:provider/provider.dart';
5 | import 'package:songtube/provider/mediaProvider.dart';
6 | import 'package:songtube/screens/musicScreen/components/downloadsEmpty.dart';
7 | import 'package:songtube/screens/musicScreen/components/loadingListWidget.dart';
8 | import 'package:songtube/screens/musicScreen/components/noPermissionWidget.dart';
9 | import 'package:songtube/screens/musicScreen/components/playlistEmpty.dart';
10 |
11 | enum MediaListBaseType { Downloads, Any }
12 |
13 | class MediaListBase extends StatelessWidget {
14 | final Widget child;
15 | final bool isLoading;
16 | final bool isEmpty;
17 | final MediaListBaseType listType;
18 | MediaListBase({
19 | @required this.child,
20 | @required this.isLoading,
21 | @required this.isEmpty,
22 | @required this.listType,
23 | });
24 | @override
25 | Widget build(BuildContext context) {
26 | MediaProvider mediaProvider = Provider.of(context);
27 | return AnimatedSwitcher(
28 | duration: Duration(milliseconds: 200),
29 | child: animatedSwitcherChild(mediaProvider),
30 | );
31 | }
32 |
33 | Widget animatedSwitcherChild(mediaProvider) {
34 | if (!mediaProvider.storagePermission) {
35 | return NoPermissionWidget(
36 | onPermissionRequest: () {
37 | Permission.storage.request().then((value) {
38 | if (value == PermissionStatus.granted) {
39 | mediaProvider.storagePermission = true;
40 | mediaProvider.loadSongList();
41 | }
42 | });
43 | }
44 | );
45 | } else if (!isEmpty) {
46 | return child;
47 | } else if (isLoading) {
48 | return MediaLoadingWidget();
49 | } else {
50 | if (listType == MediaListBaseType.Downloads) {
51 | return MediaDownloadsEmpty();
52 | } else {
53 | return PlaylistEmptyWidget();
54 | }
55 | }
56 | }
57 |
58 | }
--------------------------------------------------------------------------------
/lib/ui/components/navigationBar.dart:
--------------------------------------------------------------------------------
1 | import 'package:eva_icons_flutter/eva_icons_flutter.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
4 | import 'package:songtube/internal/languages.dart';
5 |
6 | class AppBottomNavigationBar extends StatelessWidget {
7 | final int currentIndex;
8 | final Function(int) onItemTap;
9 | AppBottomNavigationBar({
10 | @required this.currentIndex,
11 | @required this.onItemTap,
12 | });
13 | @override
14 | Widget build(BuildContext context) {
15 | return BottomNavigationBar(
16 | backgroundColor: Theme.of(context).cardColor,
17 | currentIndex: currentIndex,
18 | selectedLabelStyle: TextStyle(
19 | fontFamily: 'Product Sans',
20 | fontWeight: FontWeight.w600,
21 | letterSpacing: 0.2
22 | ),
23 | unselectedLabelStyle: TextStyle(
24 | fontFamily: 'Product Sans',
25 | fontWeight: FontWeight.w600,
26 | letterSpacing: 0.2
27 | ),
28 | iconSize: 22,
29 | selectedFontSize: 12,
30 | unselectedFontSize: 12,
31 | elevation: 8,
32 | selectedItemColor: Theme.of(context).accentColor,
33 | unselectedItemColor: Theme.of(context).iconTheme.color,
34 | type: BottomNavigationBarType.fixed,
35 | onTap: (int index) => onItemTap(index),
36 | items: [
37 | BottomNavigationBarItem(
38 | icon: Icon(EvaIcons.homeOutline),
39 | label: Languages.of(context).labelHome
40 | ),
41 | BottomNavigationBarItem(
42 | icon: Icon(EvaIcons.bookOpenOutline),
43 | label: "Channels"
44 | ),
45 | BottomNavigationBarItem(
46 | icon: Icon(EvaIcons.cloudDownloadOutline),
47 | label: Languages.of(context).labelDownloads
48 | ),
49 | BottomNavigationBarItem(
50 | icon: Icon(EvaIcons.musicOutline),
51 | label: Languages.of(context).labelMusic
52 | ),
53 | BottomNavigationBarItem(
54 | icon: Icon(MdiIcons.folderOutline),
55 | label: Languages.of(context).labelLibrary
56 | )
57 | ]
58 | );
59 | }
60 | }
--------------------------------------------------------------------------------
/lib/ui/sheets/searchFilters.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:newpipeextractor_dart/models/filters.dart';
3 | import 'package:provider/provider.dart';
4 | import 'package:songtube/provider/managerProvider.dart';
5 | import 'package:songtube/ui/components/styledBottomSheet.dart';
6 |
7 | class SearchFiltersSheet extends StatelessWidget {
8 | @override
9 | Widget build(BuildContext context) {
10 | ManagerProvider manager = Provider.of(context);
11 | return StyledBottomSheet(
12 | title: "Search Filters",
13 | contentPadding: EdgeInsets.all(12),
14 | content: ListView.builder(
15 | shrinkWrap: true,
16 | itemCount: YoutubeSearchFilter.searchFilters.length,
17 | itemBuilder: (context, index) {
18 | String filter = YoutubeSearchFilter.searchFilters[index];
19 | return CheckboxListTile(
20 | title: Text(
21 | (filter[0].toUpperCase() + filter.substring(1))
22 | .replaceAll("_", " "),
23 | style: TextStyle(
24 | color: Theme.of(context).textTheme.bodyText1.color,
25 | fontSize: 16,
26 | fontFamily: 'Product Sans',
27 | fontWeight: FontWeight.w600
28 | ),
29 | ),
30 | value: manager.searchFilters.contains(filter),
31 | onChanged: (_) {
32 | if (manager.searchFilters.contains(filter)) {
33 | manager.searchFilters.removeWhere((element) => element == filter);
34 | } else {
35 | manager.searchFilters.add(filter);
36 | }
37 | manager.setState();
38 | }
39 | );
40 | },
41 | ),
42 | actions: [
43 | TextButton(
44 | child: Text(
45 | "Close",
46 | style: TextStyle(
47 | color: Theme.of(context).accentColor,
48 | fontFamily: 'Product Sans',
49 | fontWeight: FontWeight.w700,
50 | fontSize: 18
51 | ),
52 | ),
53 | onPressed: () => Navigator.pop(context)
54 | )
55 | ],
56 | );
57 | }
58 | }
--------------------------------------------------------------------------------
/lib/ui/sheets/joinTelegram.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
3 | import 'package:provider/provider.dart';
4 | import 'package:songtube/internal/languages.dart';
5 | import 'package:songtube/provider/preferencesProvider.dart';
6 | import 'package:songtube/ui/components/styledBottomSheet.dart';
7 | import 'package:url_launcher/url_launcher.dart';
8 |
9 | class JoinTelegramSheet extends StatelessWidget {
10 | @override
11 | Widget build(BuildContext context) {
12 | PreferencesProvider prefs = Provider.of(context, listen: false);
13 | return StyledBottomSheet(
14 | actionsPadding: EdgeInsets.only(
15 | right: 24, left: 24, bottom: 12
16 | ),
17 | addBottomPadding: true,
18 | leading: Icon(MdiIcons.telegram, color: Colors.blue),
19 | title: Languages.of(context).labelJoinTelegramChannel,
20 | content: Text(
21 | Languages.of(context).labelJoinTelegramJustification,
22 | style: TextStyle(
23 | color: Theme.of(context).textTheme.bodyText1.color,
24 | fontSize: 16
25 | ),
26 | ),
27 | actions: [
28 | TextButton(
29 | child: Text(Languages.of(context).labelJoin,
30 | style: TextStyle(
31 | fontWeight: FontWeight.w600
32 | )),
33 | onPressed: () {
34 | prefs.showJoinTelegramDialog = false;
35 | launch("https://t.me/songtubechannel");
36 | Navigator.pop(context);
37 | },
38 | ),
39 | TextButton(
40 | child: Text(Languages.of(context).labelRemindLater,
41 | style: TextStyle(
42 | fontWeight: FontWeight.w600
43 | )),
44 | onPressed: () {
45 | prefs.remindTelegramLater = true;
46 | Navigator.pop(context);
47 | },
48 | ),
49 | TextButton(
50 | child: Text(Languages.of(context).labelNo,
51 | style: TextStyle(
52 | fontWeight: FontWeight.w600
53 | )),
54 | onPressed: () {
55 | prefs.showJoinTelegramDialog = false;
56 | Navigator.pop(context);
57 | },
58 | )
59 | ],
60 | );
61 | }
62 | }
--------------------------------------------------------------------------------
/lib/screens/musicScreen/components/noPermissionWidget.dart:
--------------------------------------------------------------------------------
1 | // Flutter
2 | import 'package:flutter/material.dart';
3 |
4 | // Packages
5 | import 'package:eva_icons_flutter/eva_icons_flutter.dart';
6 | import 'package:songtube/internal/languages.dart';
7 |
8 | class NoPermissionWidget extends StatelessWidget {
9 | final Function onPermissionRequest;
10 | NoPermissionWidget({
11 | @required this.onPermissionRequest
12 | });
13 | @override
14 | Widget build(BuildContext context) {
15 | return Center(
16 | child: Column(
17 | mainAxisAlignment: MainAxisAlignment.center,
18 | children: [
19 | Icon(EvaIcons.saveOutline, size: 80),
20 | Container(
21 | margin: EdgeInsets.only(top: 16, bottom: 16),
22 | child: Text(
23 | Languages.of(context).labelNoPermissionJustification,
24 | style: TextStyle(
25 | fontFamily: 'YTSans',
26 | fontSize: 20
27 | ),
28 | textAlign: TextAlign.center,
29 | ),
30 | ),
31 | GestureDetector(
32 | onTap: () => onPermissionRequest(),
33 | child: Container(
34 | margin: EdgeInsets.only(bottom: 32),
35 | height: 50,
36 | decoration: BoxDecoration(
37 | borderRadius: BorderRadius.circular(10),
38 | color: Theme.of(context).accentColor
39 | ),
40 | child: Row(
41 | mainAxisSize: MainAxisSize.min,
42 | children: [
43 | Container(
44 | margin: EdgeInsets.only(left: 16, right: 8),
45 | child: Text(
46 | Languages.of(context).labelAllow + " " +
47 | Languages.of(context).labelAccess,
48 | style: TextStyle(
49 | fontSize: 16,
50 | color: Colors.white,
51 | fontWeight: FontWeight.w600
52 | )
53 | ),
54 | ),
55 | Container(
56 | margin: EdgeInsets.only(right: 8),
57 | child: Icon(
58 | EvaIcons.radioButtonOnOutline,
59 | color: Colors.white,
60 | )
61 | )
62 | ],
63 | ),
64 | ),
65 | ),
66 | ],
67 | ),
68 | );
69 | }
70 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/internal/systemUi.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/services.dart';
3 | import 'package:provider/provider.dart';
4 | import 'package:songtube/provider/configurationProvider.dart';
5 | import 'package:songtube/provider/mediaProvider.dart';
6 | import 'package:songtube/provider/preferencesProvider.dart';
7 |
8 | void setSystemUiColor(BuildContext context) {
9 | ConfigurationProvider config = Provider.of(context, listen: false);
10 | MediaProvider mediaProvider = Provider.of(context, listen: false);
11 | PreferencesProvider prefs = Provider.of(context, listen: false);
12 | Brightness _systemBrightness = Theme.of(context).brightness;
13 | Brightness _statusBarBrightness = _systemBrightness == Brightness.light
14 | ? Brightness.dark
15 | : Brightness.light;
16 | if (!mediaProvider.fwController.isAttached) {
17 | SystemChrome.setSystemUIOverlayStyle(
18 | SystemUiOverlayStyle(
19 | statusBarColor: Colors.transparent,
20 | statusBarBrightness: _statusBarBrightness,
21 | statusBarIconBrightness: _statusBarBrightness,
22 | systemNavigationBarColor: Theme.of(context).cardColor,
23 | systemNavigationBarIconBrightness: _statusBarBrightness,
24 | ),
25 | );
26 | } else {
27 | double position = mediaProvider.fwController.panelPosition;
28 | int sdkInt = config.preferences.sdkInt;
29 | if (position > 0.95) {
30 | bool mediaBlurBackground = prefs.enablePlayerBlurBackground;
31 | SystemChrome.setSystemUIOverlayStyle(
32 | SystemUiOverlayStyle(
33 | statusBarIconBrightness: mediaBlurBackground ? mediaProvider.textColor == Colors.black
34 | ? Brightness.dark : Brightness.light : _statusBarBrightness,
35 | systemNavigationBarIconBrightness: mediaBlurBackground ? sdkInt >= 30 ? mediaProvider.textColor == Colors.black
36 | ? Brightness.dark : Brightness.light : null : _statusBarBrightness,
37 | ),
38 | );
39 | } else if (position < 0.95) {
40 | SystemChrome.setSystemUIOverlayStyle(
41 | SystemUiOverlayStyle(
42 | statusBarColor: Colors.transparent,
43 | statusBarBrightness: _statusBarBrightness,
44 | statusBarIconBrightness: _statusBarBrightness,
45 | systemNavigationBarColor: Theme.of(context).cardColor,
46 | systemNavigationBarIconBrightness: _statusBarBrightness,
47 | ),
48 | );
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/lib/screens/downloadScreen/tabs/cancelledTab.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:permission_handler/permission_handler.dart';
3 | import 'package:autolist/autolist.dart';
4 | import 'package:provider/provider.dart';
5 | import 'package:songtube/internal/download/downloadSet.dart';
6 | import 'package:songtube/provider/downloadsProvider.dart';
7 | import 'package:songtube/screens/downloadScreen/components/downloadTile.dart';
8 | import 'package:songtube/ui/components/emptyIndicator.dart';
9 |
10 | class DownloadsCancelledTab extends StatefulWidget {
11 | @override
12 | _DownloadsCancelledTabState createState() => _DownloadsCancelledTabState();
13 | }
14 |
15 | class _DownloadsCancelledTabState extends State {
16 | @override
17 | Widget build(BuildContext context) {
18 | return AnimatedSwitcher(
19 | duration: Duration(milliseconds: 200),
20 | child: downloadsBody(context)
21 | );
22 | }
23 |
24 | Widget downloadsBody(BuildContext context) {
25 | DownloadsProvider downloadsProvider = Provider.of(context);
26 | if (downloadsProvider.cancelledList.isNotEmpty) {
27 | return Padding(
28 | padding: EdgeInsets.only(top: 8),
29 | child: AutoList(
30 | items: downloadsProvider.cancelledList,
31 | duration: Duration(milliseconds: 400),
32 | itemBuilder: (context, infoset) {
33 | return Padding(
34 | padding: EdgeInsets.only(left: 16, right: 16, bottom: 8),
35 | child: DownloadTile(
36 | dataProgress: infoset.dataProgress.stream,
37 | progressBar: infoset.progressBar.stream,
38 | currentAction: infoset.currentAction.stream,
39 | metadata: infoset.downloadItem.tags,
40 | downloadType: infoset.downloadItem.downloadType,
41 | errorReason: infoset.errorReason,
42 | onDownloadCancel: () {
43 | Permission.storage.request().then((value) {
44 | if (value == PermissionStatus.granted) {
45 | downloadsProvider.retryDownload(infoset.downloadId);
46 | setState(() {});
47 | }
48 | });
49 | },
50 | cancelDownloadIcon: Icon(Icons.refresh, size: 18)
51 | )
52 | );
53 | },
54 | ),
55 | );
56 | } else {
57 | return Align(
58 | alignment: Alignment.topCenter,
59 | child: const EmptyIndicator()
60 | );
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/lib/pages/watchHistory.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:newpipeextractor_dart/models/infoItems/video.dart';
3 | import 'package:provider/provider.dart';
4 | import 'package:songtube/provider/preferencesProvider.dart';
5 | import 'package:songtube/provider/videoPageProvider.dart';
6 | import 'package:songtube/ui/layout/streamsListTile.dart';
7 |
8 | class WatchHistoryPage extends StatelessWidget {
9 | @override
10 | Widget build(BuildContext context) {
11 | PreferencesProvider prefs = Provider.of(context);
12 | VideoPageProvider pageProvider = Provider.of(context);
13 | List history = prefs.watchHistory;
14 | return Scaffold(
15 | backgroundColor: Theme.of(context).cardColor,
16 | appBar: AppBar(
17 | title: Text(
18 | "Watch History",
19 | style: TextStyle(
20 | fontFamily: 'Product Sans',
21 | fontWeight: FontWeight.w600,
22 | fontSize: 24,
23 | color: Theme.of(context).textTheme.bodyText1.color
24 | ),
25 | ),
26 | elevation: 0,
27 | backgroundColor: Theme.of(context).cardColor,
28 | leading: IconButton(
29 | icon: Icon(Icons.arrow_back_ios_new_rounded, color: Theme.of(context).iconTheme.color),
30 | onPressed: () {
31 | Navigator.pop(context);
32 | },
33 | ),
34 | ),
35 | body: Column(
36 | children: [
37 | Divider(
38 | height: 1,
39 | thickness: 1,
40 | color: Colors.grey[600].withOpacity(0.1),
41 | indent: 12,
42 | endIndent: 12
43 | ),
44 | Expanded(
45 | child: history.isNotEmpty
46 | ? StreamsListTileView(
47 | streams: history,
48 | onTap: (stream, index) {
49 | Navigator.of(context).pop();
50 | pageProvider.infoItem = stream;
51 | },
52 | onDelete: (item) => prefs.deleteFromWatchHistory(item as StreamInfoItem)
53 | )
54 | : Center(
55 | child: Text(
56 | "History is Empty!",
57 | style: TextStyle(
58 | fontWeight: FontWeight.w600,
59 | fontSize: 16
60 | ),
61 | ),
62 | ),
63 | ),
64 | Container(
65 | height: MediaQuery.of(context).padding.bottom,
66 | color: Theme.of(context).cardColor
67 | )
68 | ],
69 | )
70 | );
71 | }
72 | }
--------------------------------------------------------------------------------
/lib/players/components/videoPlayer/progressBar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:video_player/video_player.dart';
3 |
4 | class VideoPlayerProgressBar extends StatelessWidget {
5 | final VideoPlayerController controller;
6 | final Stream position;
7 | VideoPlayerProgressBar({
8 | @required this.controller,
9 | @required this.position
10 | });
11 | @override
12 | Widget build(BuildContext context) {
13 | return Container(
14 | margin: EdgeInsets.all(8),
15 | child: Row(
16 | mainAxisAlignment: MainAxisAlignment.center,
17 | crossAxisAlignment: CrossAxisAlignment.center,
18 | children: [
19 | StreamBuilder(
20 | stream: position,
21 | builder: (context, snapshot) {
22 | if (snapshot.hasData) {
23 | return Padding(
24 | padding: EdgeInsets.only(left: 16, right: 8, top: 4),
25 | child: Text(
26 | "${snapshot.data.inMinutes.toString().padLeft(2, '0')}:" +
27 | "${snapshot.data.inSeconds.remainder(60).toString().padLeft(2, '0')}",
28 | style: TextStyle(
29 | fontSize: 12,
30 | color: Colors.white
31 | ),
32 | )
33 | );
34 | } else {
35 | return Padding(
36 | padding: EdgeInsets.only(left: 16, right: 8, top: 4),
37 | child: Text(
38 | "00:00",
39 | style: TextStyle(
40 | fontSize: 12,
41 | color: Colors.white
42 | ),
43 | )
44 | );
45 | }
46 | }
47 | ),
48 | Expanded(
49 | child: VideoProgressIndicator(
50 | controller,
51 | allowScrubbing: true,
52 | colors: VideoProgressColors(
53 | playedColor: Theme.of(context).accentColor,
54 | bufferedColor: Colors.grey[500].withOpacity(0.6),
55 | backgroundColor: Colors.grey[600].withOpacity(0.6)
56 | ),
57 | ),
58 | ),
59 | Padding(
60 | padding: EdgeInsets.only(left: 8, right: 16, top: 4),
61 | child: Text(
62 | "${controller.value.duration.inMinutes.toString().padLeft(2, '0')}:" +
63 | "${controller.value.duration.inSeconds.remainder(60).toString().padLeft(2, '0')}",
64 | style: TextStyle(
65 | fontSize: 12,
66 | color: Colors.white
67 | ),
68 | )
69 | )
70 | ],
71 | ),
72 | );
73 | }
74 | }
--------------------------------------------------------------------------------
/lib/screens/downloadScreen/tabs/queueTab.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 | import 'package:songtube/internal/download/downloadSet.dart';
4 | import 'package:songtube/provider/downloadsProvider.dart';
5 | import 'package:songtube/screens/downloadScreen/components/downloadTile.dart';
6 | import 'package:songtube/ui/components/emptyIndicator.dart';
7 | import 'package:autolist/autolist.dart';
8 |
9 | class DownloadsQueueTab extends StatefulWidget {
10 | @override
11 | _DownloadsQueueTabState createState() => _DownloadsQueueTabState();
12 | }
13 |
14 | class _DownloadsQueueTabState extends State {
15 | @override
16 | Widget build(BuildContext context) {
17 | return AnimatedSwitcher(
18 | duration: Duration(milliseconds: 200),
19 | child: downloadsBody(context)
20 | );
21 | }
22 |
23 | Widget downloadsBody(BuildContext context) {
24 | DownloadsProvider downloadsProvider = Provider.of(context);
25 | if (downloadsProvider.downloadingList.isNotEmpty) {
26 | return AnimatedSwitcher(
27 | duration: Duration(milliseconds: 400),
28 | child: downloadsProvider.downloadingList.isNotEmpty ? Align(
29 | alignment: Alignment.topCenter,
30 | child: AutoList(
31 | shrinkWrap: true,
32 | physics: NeverScrollableScrollPhysics(),
33 | items: downloadsProvider.downloadingList,
34 | duration: Duration(milliseconds: 400),
35 | itemBuilder: (context, infoset) {
36 | return StreamBuilder