├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── flutter_utube │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets └── logo.png ├── dev ├── assets │ └── ico.png └── phoneScreenshots │ ├── 1.jpg │ ├── 10.jpg │ ├── 2.jpg │ ├── 3.jpg │ ├── 4.jpg │ ├── 5.jpg │ ├── 6.jpg │ ├── 7.jpg │ ├── 8.jpg │ └── 9.jpg ├── fonts └── Cairo-Regular.ttf ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── 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-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── lib ├── api │ ├── helpers │ │ ├── extract_json.dart │ │ └── helpers_extention.dart │ ├── retry.dart │ └── youtube_api.dart ├── constants.dart ├── helpers │ ├── data_search.dart │ ├── shared_helper.dart │ └── suggestion_history.dart ├── main.dart ├── models │ ├── channel.dart │ ├── channel_data.dart │ ├── my_video.dart │ ├── subscribed.dart │ └── video_data.dart ├── pages │ ├── channel │ │ ├── body.dart │ │ └── channel_page.dart │ ├── home │ │ ├── body.dart │ │ ├── home_page.dart │ │ └── subscribed_channels.dart │ ├── playlist_page.dart │ ├── search_page.dart │ └── video_detail_page.dart ├── theme │ └── colors.dart ├── utilities │ ├── categories.dart │ └── custom_app_bar.dart └── widgets │ ├── channel_widget.dart │ ├── loading.dart │ ├── playList_widget.dart │ └── video_widget.dart ├── names.txt ├── pubspec.lock ├── pubspec.yaml └── test └── widget_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Web related 36 | lib/generated_plugin_registrant.dart 37 | 38 | # Symbolication related 39 | app.*.symbols 40 | 41 | # Obfuscation related 42 | app.*.map.json 43 | 44 | # Android Studio will place build artifacts here 45 | /android/app/debug 46 | /android/app/profile 47 | /android/app/release 48 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 8 | channel: stable 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 17 | base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 18 | - platform: android 19 | create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 20 | base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 21 | - platform: ios 22 | create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 23 | base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 24 | 25 | # User provided section 26 | 27 | # List of Local paths (relative to this file) that should be 28 | # ignored by the migrate tool. 29 | # 30 | # Files that are not part of the templates will be ignored by default. 31 | unmanaged_files: 32 | - 'lib/main.dart' 33 | - 'ios/Runner.xcodeproj/project.pbxproj' 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # FlutterTube 3 | 4 | YouTube client app for streaming videos for Android and IOS. 5 | 6 | 7 |

8 | 9 | 10 | ## Screenshots 11 | 12 | ![App Screenshot](https://raw.githubusercontent.com/modwedar/FlutterTube/master/dev/phoneScreenshots/1.jpg) 13 | 14 | [](https://raw.githubusercontent.com/modwedar/FlutterTube/master/dev/phoneScreenshots/1.jpg) 15 | [](https://raw.githubusercontent.com/modwedar/FlutterTube/master/dev/phoneScreenshots/2.jpg) 16 | [](https://raw.githubusercontent.com/modwedar/FlutterTube/master/dev/phoneScreenshots/3.jpg) 17 | [](https://raw.githubusercontent.com/modwedar/FlutterTube/master/dev/phoneScreenshots/4.jpg) 18 | [](https://raw.githubusercontent.com/modwedar/FlutterTube/master/dev/phoneScreenshots/5.jpg) 19 | [](https://raw.githubusercontent.com/modwedar/FlutterTube/master/dev/phoneScreenshots/6.jpg) 20 | [](https://raw.githubusercontent.com/modwedar/FlutterTube/master/dev/phoneScreenshots/7.jpg) 21 | [](https://raw.githubusercontent.com/modwedar/FlutterTube/master/dev/phoneScreenshots/8.jpg) 22 | [](https://raw.githubusercontent.com/modwedar/FlutterTube/master/dev/phoneScreenshots/9.jpg) 23 | [](https://raw.githubusercontent.com/modwedar/FlutterTube/master/dev/phoneScreenshots/10.jpg) 24 | 25 | ## Description 26 | 27 | FlutterTube works by fetching the required data from Youtube. If the official API (YouTube) for our purposes, or is proprietary, the app parses the website or uses an internal API instead. This means that you don't need an account on google to use FlutterTube. 28 | 29 | The application fetches the data from YouTube, analyzes it, and then displays it. 30 | 31 | 32 | ## Features 33 | 34 | - Watch youtube videos without Ads 35 | - Watch the video in multiple quality 36 | - Get trending videos, music, gaming, and movies 37 | - Speed up and slow down videos 38 | - Next/Related videos 39 | - Search and watch playlists 40 | - Search videos, audios, channels, playlists and albums 41 | - Browse videos and audios within a channel 42 | - Fullscreen videos 43 | - Subscribe to channels (without logging into google account!) 44 | 45 | ### TODO List 46 | 47 | - [ ] Download as video or audio 48 | - [ ] Subscribe channel from video page 49 | - [ ] View and search watch history 50 | - [ ] Video minimizing 51 | - [ ] Get notifications about new videos from channels you're subscribed to 52 | - [ ] Show/hide comments 53 | - [ ] Downloads page 54 | - [ ] Display playsits in related videos 55 | - [ ] Next video playback feature 56 | - [ ] Popup mode (floating player, aka Picture-in-Picture) 57 | - [ ] Listen to audio in the background 58 | - [ ] Clear an item from search history 59 | 60 | ## License 61 | 62 | [MIT](https://opensource.org/licenses/MIT) 63 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion flutter.compileSdkVersion 30 | ndkVersion flutter.ndkVersion 31 | 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_8 34 | targetCompatibility JavaVersion.VERSION_1_8 35 | } 36 | 37 | kotlinOptions { 38 | jvmTarget = '1.8' 39 | } 40 | 41 | sourceSets { 42 | main.java.srcDirs += 'src/main/kotlin' 43 | } 44 | 45 | defaultConfig { 46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 47 | applicationId "com.example.flutter_utube" 48 | // You can update the following values to match your application needs. 49 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. 50 | minSdkVersion 24 51 | targetSdkVersion flutter.targetSdkVersion 52 | versionCode flutterVersionCode.toInteger() 53 | versionName flutterVersionName 54 | } 55 | 56 | buildTypes { 57 | release { 58 | // TODO: Add your own signing config for the release build. 59 | // Signing with the debug keys for now, so `flutter run --release` works. 60 | signingConfig signingConfigs.debug 61 | } 62 | } 63 | } 64 | 65 | flutter { 66 | source '../..' 67 | } 68 | 69 | dependencies { 70 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 71 | } 72 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 16 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/flutter_utube/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.flutter_utube 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.6.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.1.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 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 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /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-7.4-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/assets/logo.png -------------------------------------------------------------------------------- /dev/assets/ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/dev/assets/ico.png -------------------------------------------------------------------------------- /dev/phoneScreenshots/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/dev/phoneScreenshots/1.jpg -------------------------------------------------------------------------------- /dev/phoneScreenshots/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/dev/phoneScreenshots/10.jpg -------------------------------------------------------------------------------- /dev/phoneScreenshots/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/dev/phoneScreenshots/2.jpg -------------------------------------------------------------------------------- /dev/phoneScreenshots/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/dev/phoneScreenshots/3.jpg -------------------------------------------------------------------------------- /dev/phoneScreenshots/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/dev/phoneScreenshots/4.jpg -------------------------------------------------------------------------------- /dev/phoneScreenshots/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/dev/phoneScreenshots/5.jpg -------------------------------------------------------------------------------- /dev/phoneScreenshots/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/dev/phoneScreenshots/6.jpg -------------------------------------------------------------------------------- /dev/phoneScreenshots/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/dev/phoneScreenshots/7.jpg -------------------------------------------------------------------------------- /dev/phoneScreenshots/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/dev/phoneScreenshots/8.jpg -------------------------------------------------------------------------------- /dev/phoneScreenshots/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/dev/phoneScreenshots/9.jpg -------------------------------------------------------------------------------- /fonts/Cairo-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/fonts/Cairo-Regular.ttf -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXCopyFilesBuildPhase section */ 19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 20 | isa = PBXCopyFilesBuildPhase; 21 | buildActionMask = 2147483647; 22 | dstPath = ""; 23 | dstSubfolderSpec = 10; 24 | files = ( 25 | ); 26 | name = "Embed Frameworks"; 27 | runOnlyForDeploymentPostprocessing = 0; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 9740EEB11CF90186004384FC /* Flutter */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 65 | ); 66 | name = Flutter; 67 | sourceTree = ""; 68 | }; 69 | 97C146E51CF9000F007C117D = { 70 | isa = PBXGroup; 71 | children = ( 72 | 9740EEB11CF90186004384FC /* Flutter */, 73 | 97C146F01CF9000F007C117D /* Runner */, 74 | 97C146EF1CF9000F007C117D /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | 97C146EF1CF9000F007C117D /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 97C146EE1CF9000F007C117D /* Runner.app */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | 97C146F01CF9000F007C117D /* Runner */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 92 | 97C147021CF9000F007C117D /* Info.plist */, 93 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 94 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 95 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 96 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 97 | ); 98 | path = Runner; 99 | sourceTree = ""; 100 | }; 101 | /* End PBXGroup section */ 102 | 103 | /* Begin PBXNativeTarget section */ 104 | 97C146ED1CF9000F007C117D /* Runner */ = { 105 | isa = PBXNativeTarget; 106 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 107 | buildPhases = ( 108 | 9740EEB61CF901F6004384FC /* Run Script */, 109 | 97C146EA1CF9000F007C117D /* Sources */, 110 | 97C146EB1CF9000F007C117D /* Frameworks */, 111 | 97C146EC1CF9000F007C117D /* Resources */, 112 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 113 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 114 | ); 115 | buildRules = ( 116 | ); 117 | dependencies = ( 118 | ); 119 | name = Runner; 120 | productName = Runner; 121 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 122 | productType = "com.apple.product-type.application"; 123 | }; 124 | /* End PBXNativeTarget section */ 125 | 126 | /* Begin PBXProject section */ 127 | 97C146E61CF9000F007C117D /* Project object */ = { 128 | isa = PBXProject; 129 | attributes = { 130 | LastUpgradeCheck = 1300; 131 | ORGANIZATIONNAME = ""; 132 | TargetAttributes = { 133 | 97C146ED1CF9000F007C117D = { 134 | CreatedOnToolsVersion = 7.3.1; 135 | LastSwiftMigration = 1100; 136 | }; 137 | }; 138 | }; 139 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 140 | compatibilityVersion = "Xcode 9.3"; 141 | developmentRegion = en; 142 | hasScannedForEncodings = 0; 143 | knownRegions = ( 144 | en, 145 | Base, 146 | ); 147 | mainGroup = 97C146E51CF9000F007C117D; 148 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 149 | projectDirPath = ""; 150 | projectRoot = ""; 151 | targets = ( 152 | 97C146ED1CF9000F007C117D /* Runner */, 153 | ); 154 | }; 155 | /* End PBXProject section */ 156 | 157 | /* Begin PBXResourcesBuildPhase section */ 158 | 97C146EC1CF9000F007C117D /* Resources */ = { 159 | isa = PBXResourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 163 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 164 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 165 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXResourcesBuildPhase section */ 170 | 171 | /* Begin PBXShellScriptBuildPhase section */ 172 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 173 | isa = PBXShellScriptBuildPhase; 174 | buildActionMask = 2147483647; 175 | files = ( 176 | ); 177 | inputPaths = ( 178 | ); 179 | name = "Thin Binary"; 180 | outputPaths = ( 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | shellPath = /bin/sh; 184 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 185 | }; 186 | 9740EEB61CF901F6004384FC /* Run Script */ = { 187 | isa = PBXShellScriptBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | ); 191 | inputPaths = ( 192 | ); 193 | name = "Run Script"; 194 | outputPaths = ( 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | shellPath = /bin/sh; 198 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 199 | }; 200 | /* End PBXShellScriptBuildPhase section */ 201 | 202 | /* Begin PBXSourcesBuildPhase section */ 203 | 97C146EA1CF9000F007C117D /* Sources */ = { 204 | isa = PBXSourcesBuildPhase; 205 | buildActionMask = 2147483647; 206 | files = ( 207 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 208 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | }; 212 | /* End PBXSourcesBuildPhase section */ 213 | 214 | /* Begin PBXVariantGroup section */ 215 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 216 | isa = PBXVariantGroup; 217 | children = ( 218 | 97C146FB1CF9000F007C117D /* Base */, 219 | ); 220 | name = Main.storyboard; 221 | sourceTree = ""; 222 | }; 223 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 224 | isa = PBXVariantGroup; 225 | children = ( 226 | 97C147001CF9000F007C117D /* Base */, 227 | ); 228 | name = LaunchScreen.storyboard; 229 | sourceTree = ""; 230 | }; 231 | /* End PBXVariantGroup section */ 232 | 233 | /* Begin XCBuildConfiguration section */ 234 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 235 | isa = XCBuildConfiguration; 236 | buildSettings = { 237 | ALWAYS_SEARCH_USER_PATHS = NO; 238 | CLANG_ANALYZER_NONNULL = YES; 239 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 240 | CLANG_CXX_LIBRARY = "libc++"; 241 | CLANG_ENABLE_MODULES = YES; 242 | CLANG_ENABLE_OBJC_ARC = YES; 243 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 244 | CLANG_WARN_BOOL_CONVERSION = YES; 245 | CLANG_WARN_COMMA = YES; 246 | CLANG_WARN_CONSTANT_CONVERSION = YES; 247 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 248 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 249 | CLANG_WARN_EMPTY_BODY = YES; 250 | CLANG_WARN_ENUM_CONVERSION = YES; 251 | CLANG_WARN_INFINITE_RECURSION = YES; 252 | CLANG_WARN_INT_CONVERSION = YES; 253 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 254 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 255 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 256 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 257 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 258 | CLANG_WARN_STRICT_PROTOTYPES = YES; 259 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 260 | CLANG_WARN_UNREACHABLE_CODE = YES; 261 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 262 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 263 | COPY_PHASE_STRIP = NO; 264 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 265 | ENABLE_NS_ASSERTIONS = NO; 266 | ENABLE_STRICT_OBJC_MSGSEND = YES; 267 | GCC_C_LANGUAGE_STANDARD = gnu99; 268 | GCC_NO_COMMON_BLOCKS = YES; 269 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 270 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 271 | GCC_WARN_UNDECLARED_SELECTOR = YES; 272 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 273 | GCC_WARN_UNUSED_FUNCTION = YES; 274 | GCC_WARN_UNUSED_VARIABLE = YES; 275 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 276 | MTL_ENABLE_DEBUG_INFO = NO; 277 | SDKROOT = iphoneos; 278 | SUPPORTED_PLATFORMS = iphoneos; 279 | TARGETED_DEVICE_FAMILY = "1,2"; 280 | VALIDATE_PRODUCT = YES; 281 | }; 282 | name = Profile; 283 | }; 284 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 285 | isa = XCBuildConfiguration; 286 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 287 | buildSettings = { 288 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 289 | CLANG_ENABLE_MODULES = YES; 290 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 291 | ENABLE_BITCODE = NO; 292 | INFOPLIST_FILE = Runner/Info.plist; 293 | LD_RUNPATH_SEARCH_PATHS = ( 294 | "$(inherited)", 295 | "@executable_path/Frameworks", 296 | ); 297 | PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterUtube; 298 | PRODUCT_NAME = "$(TARGET_NAME)"; 299 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 300 | SWIFT_VERSION = 5.0; 301 | VERSIONING_SYSTEM = "apple-generic"; 302 | }; 303 | name = Profile; 304 | }; 305 | 97C147031CF9000F007C117D /* Debug */ = { 306 | isa = XCBuildConfiguration; 307 | buildSettings = { 308 | ALWAYS_SEARCH_USER_PATHS = NO; 309 | CLANG_ANALYZER_NONNULL = YES; 310 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 311 | CLANG_CXX_LIBRARY = "libc++"; 312 | CLANG_ENABLE_MODULES = YES; 313 | CLANG_ENABLE_OBJC_ARC = YES; 314 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 315 | CLANG_WARN_BOOL_CONVERSION = YES; 316 | CLANG_WARN_COMMA = YES; 317 | CLANG_WARN_CONSTANT_CONVERSION = YES; 318 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 319 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 320 | CLANG_WARN_EMPTY_BODY = YES; 321 | CLANG_WARN_ENUM_CONVERSION = YES; 322 | CLANG_WARN_INFINITE_RECURSION = YES; 323 | CLANG_WARN_INT_CONVERSION = YES; 324 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 325 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 326 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 327 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 328 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 329 | CLANG_WARN_STRICT_PROTOTYPES = YES; 330 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 331 | CLANG_WARN_UNREACHABLE_CODE = YES; 332 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 333 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 334 | COPY_PHASE_STRIP = NO; 335 | DEBUG_INFORMATION_FORMAT = dwarf; 336 | ENABLE_STRICT_OBJC_MSGSEND = YES; 337 | ENABLE_TESTABILITY = YES; 338 | GCC_C_LANGUAGE_STANDARD = gnu99; 339 | GCC_DYNAMIC_NO_PIC = NO; 340 | GCC_NO_COMMON_BLOCKS = YES; 341 | GCC_OPTIMIZATION_LEVEL = 0; 342 | GCC_PREPROCESSOR_DEFINITIONS = ( 343 | "DEBUG=1", 344 | "$(inherited)", 345 | ); 346 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 347 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 348 | GCC_WARN_UNDECLARED_SELECTOR = YES; 349 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 350 | GCC_WARN_UNUSED_FUNCTION = YES; 351 | GCC_WARN_UNUSED_VARIABLE = YES; 352 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 353 | MTL_ENABLE_DEBUG_INFO = YES; 354 | ONLY_ACTIVE_ARCH = YES; 355 | SDKROOT = iphoneos; 356 | TARGETED_DEVICE_FAMILY = "1,2"; 357 | }; 358 | name = Debug; 359 | }; 360 | 97C147041CF9000F007C117D /* Release */ = { 361 | isa = XCBuildConfiguration; 362 | buildSettings = { 363 | ALWAYS_SEARCH_USER_PATHS = NO; 364 | CLANG_ANALYZER_NONNULL = YES; 365 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 366 | CLANG_CXX_LIBRARY = "libc++"; 367 | CLANG_ENABLE_MODULES = YES; 368 | CLANG_ENABLE_OBJC_ARC = YES; 369 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 370 | CLANG_WARN_BOOL_CONVERSION = YES; 371 | CLANG_WARN_COMMA = YES; 372 | CLANG_WARN_CONSTANT_CONVERSION = YES; 373 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 374 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 375 | CLANG_WARN_EMPTY_BODY = YES; 376 | CLANG_WARN_ENUM_CONVERSION = YES; 377 | CLANG_WARN_INFINITE_RECURSION = YES; 378 | CLANG_WARN_INT_CONVERSION = YES; 379 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 380 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 381 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 382 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 383 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 384 | CLANG_WARN_STRICT_PROTOTYPES = YES; 385 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 386 | CLANG_WARN_UNREACHABLE_CODE = YES; 387 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 388 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 389 | COPY_PHASE_STRIP = NO; 390 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 391 | ENABLE_NS_ASSERTIONS = NO; 392 | ENABLE_STRICT_OBJC_MSGSEND = YES; 393 | GCC_C_LANGUAGE_STANDARD = gnu99; 394 | GCC_NO_COMMON_BLOCKS = YES; 395 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 396 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 397 | GCC_WARN_UNDECLARED_SELECTOR = YES; 398 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 399 | GCC_WARN_UNUSED_FUNCTION = YES; 400 | GCC_WARN_UNUSED_VARIABLE = YES; 401 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 402 | MTL_ENABLE_DEBUG_INFO = NO; 403 | SDKROOT = iphoneos; 404 | SUPPORTED_PLATFORMS = iphoneos; 405 | SWIFT_COMPILATION_MODE = wholemodule; 406 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 407 | TARGETED_DEVICE_FAMILY = "1,2"; 408 | VALIDATE_PRODUCT = YES; 409 | }; 410 | name = Release; 411 | }; 412 | 97C147061CF9000F007C117D /* Debug */ = { 413 | isa = XCBuildConfiguration; 414 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 415 | buildSettings = { 416 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 417 | CLANG_ENABLE_MODULES = YES; 418 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 419 | ENABLE_BITCODE = NO; 420 | INFOPLIST_FILE = Runner/Info.plist; 421 | LD_RUNPATH_SEARCH_PATHS = ( 422 | "$(inherited)", 423 | "@executable_path/Frameworks", 424 | ); 425 | PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterUtube; 426 | PRODUCT_NAME = "$(TARGET_NAME)"; 427 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 428 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 429 | SWIFT_VERSION = 5.0; 430 | VERSIONING_SYSTEM = "apple-generic"; 431 | }; 432 | name = Debug; 433 | }; 434 | 97C147071CF9000F007C117D /* Release */ = { 435 | isa = XCBuildConfiguration; 436 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 437 | buildSettings = { 438 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 439 | CLANG_ENABLE_MODULES = YES; 440 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 441 | ENABLE_BITCODE = NO; 442 | INFOPLIST_FILE = Runner/Info.plist; 443 | LD_RUNPATH_SEARCH_PATHS = ( 444 | "$(inherited)", 445 | "@executable_path/Frameworks", 446 | ); 447 | PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterUtube; 448 | PRODUCT_NAME = "$(TARGET_NAME)"; 449 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 450 | SWIFT_VERSION = 5.0; 451 | VERSIONING_SYSTEM = "apple-generic"; 452 | }; 453 | name = Release; 454 | }; 455 | /* End XCBuildConfiguration section */ 456 | 457 | /* Begin XCConfigurationList section */ 458 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 459 | isa = XCConfigurationList; 460 | buildConfigurations = ( 461 | 97C147031CF9000F007C117D /* Debug */, 462 | 97C147041CF9000F007C117D /* Release */, 463 | 249021D3217E4FDB00AE95B9 /* Profile */, 464 | ); 465 | defaultConfigurationIsVisible = 0; 466 | defaultConfigurationName = Release; 467 | }; 468 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 469 | isa = XCConfigurationList; 470 | buildConfigurations = ( 471 | 97C147061CF9000F007C117D /* Debug */, 472 | 97C147071CF9000F007C117D /* Release */, 473 | 249021D4217E4FDB00AE95B9 /* Profile */, 474 | ); 475 | defaultConfigurationIsVisible = 0; 476 | defaultConfigurationName = Release; 477 | }; 478 | /* End XCConfigurationList section */ 479 | }; 480 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 481 | } 482 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/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/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/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/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/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/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/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/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/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/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/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/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/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/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/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/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/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/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/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/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/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/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/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/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modwedar/FlutterTube/3536ebfa9c86969458670e687fd3cc2b5d2c2856/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | CFBundleDisplayName 8 | Flutter Utube 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | flutter_utube 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | 49 | NSAppTransportSecurity 50 | 51 | NSAllowsArbitraryLoads 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/api/helpers/extract_json.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | Map? extractJson(String s,[String separator = '']) { 4 | final index = s.indexOf(separator) + separator.length; 5 | if (index > s.length) { 6 | return null; 7 | } 8 | 9 | final str = s.substring(index); 10 | 11 | final startIdx = str.indexOf('{'); 12 | var endIdx = str.lastIndexOf('}'); 13 | 14 | while (true) { 15 | try { 16 | return json.decode(str.substring(startIdx, endIdx + 1)) 17 | as Map; 18 | } on FormatException { 19 | endIdx = str.lastIndexOf(str.substring(0, endIdx)); 20 | if (endIdx == 0) { 21 | return null; 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /lib/api/helpers/helpers_extention.dart: -------------------------------------------------------------------------------- 1 | extension GetOrNullMap on Map { 2 | /// Get a map inside a map 3 | Map? get(String key) { 4 | var v = this[key]; 5 | if (v == null) { 6 | return null; 7 | } 8 | return v; 9 | } 10 | 11 | /// Get a value inside a map. 12 | /// If it is null this returns null, if of another type this throws. 13 | T? getT(String key) { 14 | var v = this[key]; 15 | if (v == null) { 16 | return null; 17 | } 18 | if (v is! T) { 19 | throw Exception('Invalid type: ${v.runtimeType} should be $T'); 20 | } 21 | return v; 22 | } 23 | 24 | /// Get a List>> from a map. 25 | List>? getList(String key) { 26 | var v = this[key]; 27 | if (v == null) { 28 | return null; 29 | } 30 | if (v is! List) { 31 | throw Exception('Invalid type: ${v.runtimeType} should be of type List'); 32 | } 33 | 34 | return (v.toList()).cast>(); 35 | } 36 | 37 | } 38 | 39 | /// List Utility. 40 | extension ListUtil on Iterable { 41 | /// Same as [elementAt] but if the index is higher than the length returns 42 | /// null 43 | E? elementAtSafe(int index) { 44 | if (index >= length) { 45 | return null; 46 | } 47 | return elementAt(index); 48 | } 49 | } -------------------------------------------------------------------------------- /lib/api/retry.dart: -------------------------------------------------------------------------------- 1 | library _youtube_explode.retry; 2 | 3 | import 'dart:async'; 4 | 5 | 6 | 7 | /// Run the [function] each time an exception is thrown until the retryCount 8 | /// is 0. 9 | Future retry(FutureOr Function() function) async { 10 | var retryCount = 5; 11 | 12 | // ignore: literal_only_boolean_expressions 13 | while (true) { 14 | try { 15 | return await function(); 16 | // ignore: avoid_catches_without_on_clauses 17 | } on Exception catch (e) { 18 | 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /lib/api/youtube_api.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:flutter_utube/models/my_video.dart'; 3 | import 'package:flutter_utube/models/video_data.dart'; 4 | import 'package:xml2json/xml2json.dart'; 5 | import '/api/retry.dart'; 6 | import '/helpers/suggestion_history.dart'; 7 | import '/models/channel_data.dart'; 8 | import 'helpers/extract_json.dart'; 9 | import 'helpers/helpers_extention.dart'; 10 | import 'package:http/http.dart' as http; 11 | import 'package:html/parser.dart' as parser; 12 | import 'package:collection/collection.dart'; 13 | import 'dart:developer'; 14 | 15 | class YoutubeApi { 16 | String? _searchToken; 17 | String? _channelToken; 18 | String? _playListToken; 19 | String? lastQuery; 20 | 21 | Future fetchSearchVideo(String query) async { 22 | List list = []; 23 | var client = http.Client(); 24 | if (_searchToken != null && query == lastQuery) { 25 | var url = 26 | 'https://www.youtube.com/youtubei/v1/search?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8'; 27 | 28 | return retry(() async { 29 | var body = { 30 | 'context': const { 31 | 'client': { 32 | 'hl': 'en', 33 | 'clientName': 'WEB', 34 | 'clientVersion': '2.20200911.04.00' 35 | } 36 | }, 37 | 'continuation': _searchToken 38 | }; 39 | var raw = await client.post(Uri.parse(url), body: json.encode(body)); 40 | Map jsonMap = json.decode(raw.body); 41 | var contents = jsonMap 42 | .getList('onResponseReceivedCommands') 43 | ?.firstOrNull 44 | ?.get('appendContinuationItemsAction') 45 | ?.getList('continuationItems') 46 | ?.firstOrNull 47 | ?.get('itemSectionRenderer') 48 | ?.getList('contents'); 49 | list = contents!.toList(); 50 | _searchToken = _getContinuationToken(jsonMap); 51 | return list; 52 | }); 53 | } else { 54 | lastQuery = query; 55 | var response = await client.get( 56 | Uri.parse( 57 | 'https://www.youtube.com/results?search_query=$query', 58 | ), 59 | ); 60 | var jsonMap = _getJsonMap(response); 61 | if (jsonMap != null) { 62 | var contents = jsonMap 63 | .get('contents') 64 | ?.get('twoColumnSearchResultsRenderer') 65 | ?.get('primaryContents') 66 | ?.get('sectionListRenderer') 67 | ?.getList('contents') 68 | ?.firstOrNull 69 | ?.get('itemSectionRenderer') 70 | ?.getList('contents'); 71 | 72 | list = contents!.toList(); 73 | _searchToken = _getContinuationToken(jsonMap); 74 | } 75 | } 76 | return list; 77 | } 78 | 79 | Future fetchTrendingVideo() async { 80 | SuggestionHistory.init(); 81 | List list = []; 82 | var client = http.Client(); 83 | var response = await client.get( 84 | Uri.parse( 85 | 'https://www.youtube.com/feed/trending', 86 | ), 87 | ); 88 | var raw = response.body; 89 | var root = parser.parse(raw); 90 | final scriptText = root 91 | .querySelectorAll('script') 92 | .map((e) => e.text) 93 | .toList(growable: false); 94 | var initialData = 95 | scriptText.firstWhereOrNull((e) => e.contains('var ytInitialData = ')); 96 | initialData ??= scriptText 97 | .firstWhereOrNull((e) => e.contains('window["ytInitialData"] =')); 98 | var jsonMap = extractJson(initialData!); 99 | if (jsonMap != null) { 100 | var contents = jsonMap 101 | .get('contents') 102 | ?.get('twoColumnBrowseResultsRenderer') 103 | ?.getList('tabs') 104 | ?.firstOrNull 105 | ?.get('tabRenderer') 106 | ?.get('content') 107 | ?.get('sectionListRenderer') 108 | ?.getList('contents') 109 | ?.firstOrNull 110 | ?.get('itemSectionRenderer') 111 | ?.getList('contents') 112 | ?.firstOrNull 113 | ?.get('shelfRenderer') 114 | ?.get('content') 115 | ?.get('expandedShelfContentsRenderer') 116 | ?.getList('items'); 117 | var firstList = contents != null ? contents.toList() : []; 118 | var secondContents = jsonMap 119 | .get('contents') 120 | ?.get('twoColumnBrowseResultsRenderer') 121 | ?.getList('tabs') 122 | ?.firstOrNull 123 | ?.get('tabRenderer') 124 | ?.get('content') 125 | ?.get('sectionListRenderer') 126 | ?.getList('contents') 127 | ?.elementAtSafe(3) 128 | ?.get('itemSectionRenderer') 129 | ?.getList('contents') 130 | ?.firstOrNull 131 | ?.get('shelfRenderer') 132 | ?.get('content') 133 | ?.get('expandedShelfContentsRenderer') 134 | ?.getList('items'); 135 | var secondList = secondContents != null ? secondContents.toList() : []; 136 | list = [...firstList, ...secondList]; 137 | } 138 | return list; 139 | } 140 | 141 | Future fetchTrendingMusic() async { 142 | String params = "4gINGgt5dG1hX2NoYXJ0cw%3D%3D"; 143 | List list = []; 144 | var client = http.Client(); 145 | var response = await client.get( 146 | Uri.parse( 147 | 'https://www.youtube.com/feed/trending?bp=$params', 148 | ), 149 | ); 150 | var raw = response.body; 151 | var root = parser.parse(raw); 152 | final scriptText = root 153 | .querySelectorAll('script') 154 | .map((e) => e.text) 155 | .toList(growable: false); 156 | var initialData = 157 | scriptText.firstWhereOrNull((e) => e.contains('var ytInitialData = ')); 158 | initialData ??= scriptText 159 | .firstWhereOrNull((e) => e.contains('window["ytInitialData"] =')); 160 | var jsonMap = extractJson(initialData!); 161 | if (jsonMap != null) { 162 | var contents = jsonMap 163 | .get('contents') 164 | ?.get('twoColumnBrowseResultsRenderer') 165 | ?.getList('tabs') 166 | ?.elementAtSafe(1) 167 | ?.get('tabRenderer') 168 | ?.get('content') 169 | ?.get('sectionListRenderer') 170 | ?.getList('contents') 171 | ?.firstOrNull 172 | ?.get('itemSectionRenderer') 173 | ?.getList('contents') 174 | ?.firstOrNull 175 | ?.get('shelfRenderer') 176 | ?.get('content') 177 | ?.get('expandedShelfContentsRenderer') 178 | ?.getList('items'); 179 | list = contents != null ? contents.toList() : []; 180 | } 181 | return list; 182 | } 183 | 184 | Future fetchTrendingGaming() async { 185 | String params = "4gIcGhpnYW1pbmdfY29ycHVzX21vc3RfcG9wdWxhcg"; 186 | List list = []; 187 | var client = http.Client(); 188 | var response = await client.get( 189 | Uri.parse( 190 | 'https://www.youtube.com/feed/trending?bp=$params', 191 | ), 192 | ); 193 | var raw = response.body; 194 | var root = parser.parse(raw); 195 | final scriptText = root 196 | .querySelectorAll('script') 197 | .map((e) => e.text) 198 | .toList(growable: false); 199 | var initialData = 200 | scriptText.firstWhereOrNull((e) => e.contains('var ytInitialData = ')); 201 | initialData ??= scriptText 202 | .firstWhereOrNull((e) => e.contains('window["ytInitialData"] =')); 203 | var jsonMap = extractJson(initialData!); 204 | if (jsonMap != null) { 205 | var contents = jsonMap 206 | .get('contents') 207 | ?.get('twoColumnBrowseResultsRenderer') 208 | ?.getList('tabs') 209 | ?.elementAtSafe(2) 210 | ?.get('tabRenderer') 211 | ?.get('content') 212 | ?.get('sectionListRenderer') 213 | ?.getList('contents') 214 | ?.firstOrNull 215 | ?.get('itemSectionRenderer') 216 | ?.getList('contents') 217 | ?.firstOrNull 218 | ?.get('shelfRenderer') 219 | ?.get('content') 220 | ?.get('expandedShelfContentsRenderer') 221 | ?.getList('items'); 222 | list = contents != null ? contents.toList() : []; 223 | } 224 | return list; 225 | } 226 | 227 | Future fetchTrendingMovies() async { 228 | String params = "4gIKGgh0cmFpbGVycw%3D%3D"; 229 | List list = []; 230 | var client = http.Client(); 231 | var response = await client.get( 232 | Uri.parse( 233 | 'https://www.youtube.com/feed/trending?bp=$params', 234 | ), 235 | ); 236 | var raw = response.body; 237 | var root = parser.parse(raw); 238 | final scriptText = root 239 | .querySelectorAll('script') 240 | .map((e) => e.text) 241 | .toList(growable: false); 242 | var initialData = 243 | scriptText.firstWhereOrNull((e) => e.contains('var ytInitialData = ')); 244 | initialData ??= scriptText 245 | .firstWhereOrNull((e) => e.contains('window["ytInitialData"] =')); 246 | var jsonMap = extractJson(initialData!); 247 | if (jsonMap != null) { 248 | var contents = jsonMap 249 | .get('contents') 250 | ?.get('twoColumnBrowseResultsRenderer') 251 | ?.getList('tabs') 252 | ?.elementAtSafe(3) 253 | ?.get('tabRenderer') 254 | ?.get('content') 255 | ?.get('sectionListRenderer') 256 | ?.getList('contents') 257 | ?.firstOrNull 258 | ?.get('itemSectionRenderer') 259 | ?.getList('contents') 260 | ?.firstOrNull 261 | ?.get('shelfRenderer') 262 | ?.get('content') 263 | ?.get('expandedShelfContentsRenderer') 264 | ?.getList('items'); 265 | list = contents != null ? contents.toList() : []; 266 | } 267 | return list; 268 | } 269 | 270 | Future> fetchSuggestions(String query) async { 271 | List suggestions = []; 272 | String baseUrl = 273 | 'http://suggestqueries.google.com/complete/search?output=toolbar&ds=yt&q='; 274 | var client = http.Client(); 275 | final myTranformer = Xml2Json(); 276 | var response = await client.get(Uri.parse(baseUrl + query)); 277 | var body = response.body; 278 | myTranformer.parse(body); 279 | var json = myTranformer.toGData(); 280 | List suggestionsData = jsonDecode(json)['toplevel']['CompleteSuggestion']; 281 | suggestionsData.forEach((suggestion) { 282 | suggestions.add(suggestion['suggestion']['data'].toString()); 283 | }); 284 | return suggestions; 285 | } 286 | 287 | String? _getContinuationToken(Map? root) { 288 | if (root?['contents'] != null) { 289 | if (root?['contents']?['twoColumnBrowseResultsRenderer'] != null) { 290 | return root! 291 | .get('contents') 292 | ?.get('twoColumnBrowseResultsRenderer') 293 | ?.getList('tabs') 294 | ?.elementAtSafe(1) 295 | ?.get('tabRenderer') 296 | ?.get('content') 297 | ?.get('sectionListRenderer') 298 | ?.getList('contents') 299 | ?.firstOrNull 300 | ?.get('itemSectionRenderer') 301 | ?.getList('contents') 302 | ?.firstOrNull 303 | ?.get('gridRenderer') 304 | ?.getList('items') 305 | ?.elementAtSafe(30) 306 | ?.get('continuationItemRenderer') 307 | ?.get('continuationEndpoint') 308 | ?.get('continuationCommand') 309 | ?.getT('token'); 310 | } 311 | var contents = root! 312 | .get('contents') 313 | ?.get('twoColumnSearchResultsRenderer') 314 | ?.get('primaryContents') 315 | ?.get('sectionListRenderer') 316 | ?.getList('contents'); 317 | 318 | if (contents == null || contents.length <= 1) { 319 | return null; 320 | } 321 | return contents 322 | .elementAtSafe(1) 323 | ?.get('continuationItemRenderer') 324 | ?.get('continuationEndpoint') 325 | ?.get('continuationCommand') 326 | ?.getT('token'); 327 | } 328 | if (root?['onResponseReceivedCommands'] != null) { 329 | return root! 330 | .getList('onResponseReceivedCommands') 331 | ?.firstOrNull 332 | ?.get('appendContinuationItemsAction') 333 | ?.getList('continuationItems') 334 | ?.elementAtSafe(1) 335 | ?.get('continuationItemRenderer') 336 | ?.get('continuationEndpoint') 337 | ?.get('continuationCommand') 338 | ?.getT('token'); 339 | } 340 | return null; 341 | } 342 | 343 | Future fetchChannelData(String channelId) async { 344 | var client = http.Client(); 345 | var response = await client.get( 346 | Uri.parse( 347 | 'https://www.youtube.com/channel/$channelId/videos', 348 | ), 349 | ); 350 | var raw = response.body; 351 | var root = parser.parse(raw); 352 | final scriptText = root 353 | .querySelectorAll('script') 354 | .map((e) => e.text) 355 | .toList(growable: false); 356 | var initialData = 357 | scriptText.firstWhereOrNull((e) => e.contains('var ytInitialData = ')); 358 | initialData ??= scriptText 359 | .firstWhereOrNull((e) => e.contains('window["ytInitialData"] =')); 360 | var jsonMap = extractJson(initialData!); 361 | if (jsonMap != null) { 362 | ChannelData channelData = ChannelData.fromMap(jsonMap); 363 | channelData.checkIsSubscribed(channelId); 364 | _channelToken = _getContinuationToken(jsonMap); 365 | return channelData; 366 | } 367 | return null; 368 | } 369 | 370 | Future loadMoreInChannel() async { 371 | List? list; 372 | var client = http.Client(); 373 | var url = 374 | 'https://www.youtube.com/youtubei/v1/browse?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8'; 375 | var body = { 376 | 'context': const { 377 | 'client': { 378 | 'hl': 'en', 379 | 'clientName': 'WEB', 380 | 'clientVersion': '2.20200911.04.00' 381 | } 382 | }, 383 | 'continuation': _channelToken 384 | }; 385 | var raw = await client.post(Uri.parse(url), body: json.encode(body)); 386 | Map jsonMap = json.decode(raw.body); 387 | var contents = jsonMap 388 | .getList('onResponseReceivedActions') 389 | ?.firstOrNull 390 | ?.get('appendContinuationItemsAction') 391 | ?.getList('continuationItems'); 392 | if (contents != null) { 393 | list = contents.toList(); 394 | _channelToken = _getChannelContinuationToken(jsonMap); 395 | } 396 | return list; 397 | } 398 | 399 | Future loadMoreInPlayList() async { 400 | List? list; 401 | var client = http.Client(); 402 | var url = 403 | 'https://www.youtube.com/youtubei/v1/browse?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8'; 404 | var body = { 405 | 'context': const { 406 | 'client': { 407 | 'hl': 'en', 408 | 'clientName': 'WEB', 409 | 'clientVersion': '2.20200911.04.00' 410 | } 411 | }, 412 | 'continuation': _playListToken 413 | }; 414 | var raw = await client.post(Uri.parse(url), body: json.encode(body)); 415 | Map jsonMap = json.decode(raw.body); 416 | var contents = jsonMap 417 | .getList('onResponseReceivedActions') 418 | ?.firstOrNull 419 | ?.get('appendContinuationItemsAction') 420 | ?.getList('continuationItems'); 421 | if (contents != null) { 422 | list = contents.toList(); 423 | _playListToken = _getChannelContinuationToken(jsonMap); 424 | } 425 | return list; 426 | } 427 | 428 | String? _getChannelContinuationToken(Map? root) { 429 | return root! 430 | .getList('onResponseReceivedActions') 431 | ?.firstOrNull 432 | ?.get('appendContinuationItemsAction') 433 | ?.getList('continuationItems') 434 | ?.elementAtSafe(30) 435 | ?.get('continuationItemRenderer') 436 | ?.get('continuationEndpoint') 437 | ?.get('continuationCommand') 438 | ?.getT('token'); 439 | } 440 | 441 | String? _getPlayListContinuationToken(Map? root) { 442 | return root! 443 | .get('contents') 444 | ?.get('twoColumnBrowseResultsRenderer') 445 | ?.getList('tabs') 446 | ?.firstOrNull 447 | ?.get('tabRenderer') 448 | ?.get('content') 449 | ?.get('sectionListRenderer') 450 | ?.getList('contents') 451 | ?.firstOrNull 452 | ?.get('itemSectionRenderer') 453 | ?.getList('contents') 454 | ?.firstOrNull 455 | ?.get('playlistVideoListRenderer') 456 | ?.getList('contents') 457 | ?.elementAtSafe(100) 458 | ?.get('continuationItemRenderer') 459 | ?.get('continuationEndpoint') 460 | ?.get('continuationCommand') 461 | ?.getT('token'); 462 | } 463 | 464 | Future fetchPlayListVideos(String id, int loaded) async { 465 | List videos = []; 466 | var url = 'https://www.youtube.com/playlist?list=$id&hl=en&persist_hl=1'; 467 | var client = http.Client(); 468 | var response = await client.get( 469 | Uri.parse(url), 470 | ); 471 | var jsonMap = _getJsonMap(response); 472 | if (jsonMap != null) { 473 | var contents = jsonMap 474 | .get('contents') 475 | ?.get('twoColumnBrowseResultsRenderer') 476 | ?.getList('tabs') 477 | ?.firstOrNull 478 | ?.get('tabRenderer') 479 | ?.get('content') 480 | ?.get('sectionListRenderer') 481 | ?.getList('contents') 482 | ?.firstOrNull 483 | ?.get('itemSectionRenderer') 484 | ?.getList('contents') 485 | ?.firstOrNull 486 | ?.get('playlistVideoListRenderer') 487 | ?.getList('contents'); 488 | videos = contents!.toList(); 489 | _playListToken = _getPlayListContinuationToken(jsonMap); 490 | } 491 | return videos; 492 | } 493 | 494 | Future fetchVideoData(String videoId) async { 495 | VideoData? videoData; 496 | var client = http.Client(); 497 | var response = 498 | await client.get(Uri.parse('https://www.youtube.com/watch?v=$videoId')); 499 | var raw = response.body; 500 | var root = parser.parse(raw); 501 | final scriptText = root 502 | .querySelectorAll('script') 503 | .map((e) => e.text) 504 | .toList(growable: false); 505 | var initialData = 506 | scriptText.firstWhereOrNull((e) => e.contains('var ytInitialData = ')); 507 | initialData ??= scriptText 508 | .firstWhereOrNull((e) => e.contains('window["ytInitialData"] =')); 509 | var jsonMap = extractJson(initialData!); 510 | if (jsonMap != null) { 511 | var contents = jsonMap.get('contents')?.get('twoColumnWatchNextResults'); 512 | 513 | var videosList = contents 514 | ?.get('secondaryResults') 515 | ?.get('secondaryResults') 516 | ?.getList('results') 517 | ?.toList(); 518 | 519 | videoData = VideoData( 520 | video: MyVideo( 521 | videoId: videoId, 522 | title: contents!['results']['results']['contents'][0]['videoPrimaryInfoRenderer']['title']['runs'][0]['text'], 523 | username: contents['results']['results']['contents'][1]['videoSecondaryInfoRenderer']['owner']['videoOwnerRenderer']['title']['runs'][0]['text'], 524 | viewCount: contents['results']['results']['contents'][0]['videoPrimaryInfoRenderer']['viewCount']['videoViewCountRenderer']['shortViewCount']['simpleText'], 525 | subscribeCount: contents['results']?['results']?['contents']?[1]?['videoSecondaryInfoRenderer']?['owner']?['videoOwnerRenderer']?['subscriberCountText']?['simpleText'], 526 | likeCount: contents['results']['results']['contents'][0]['videoPrimaryInfoRenderer']['videoActions']['menuRenderer']['topLevelButtons'][0]['toggleButtonRenderer']['defaultText']['simpleText'], 527 | unlikeCount: '', 528 | date: contents['results']['results']['contents'][0]['videoPrimaryInfoRenderer']['dateText']['simpleText'], 529 | channelThumb: contents['results']['results']['contents'][1]['videoSecondaryInfoRenderer']['owner']['videoOwnerRenderer']['thumbnail']['thumbnails'][1]['url'], 530 | channelId: contents['results']['results']['contents'][1]['videoSecondaryInfoRenderer']['owner']['videoOwnerRenderer']['navigationEndpoint']['browseEndpoint']['browseId'] 531 | ), 532 | videosList: videosList); 533 | } 534 | return videoData; 535 | } 536 | 537 | Map? _getJsonMap(http.Response response) { 538 | var raw = response.body; 539 | var root = parser.parse(raw); 540 | final scriptText = root 541 | .querySelectorAll('script') 542 | .map((e) => e.text) 543 | .toList(growable: false); 544 | var initialData = 545 | scriptText.firstWhereOrNull((e) => e.contains('var ytInitialData = ')); 546 | initialData ??= scriptText 547 | .firstWhereOrNull((e) => e.contains('window["ytInitialData"] =')); 548 | var jsonMap = extractJson(initialData!); 549 | return jsonMap; 550 | } 551 | } 552 | -------------------------------------------------------------------------------- /lib/constants.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | const PrimaryColor = Color(0xff2d2d2d); 4 | const SecondaryColor = Color(0xff2d2d2d); 5 | //const SecondaryColor = Color(0xFF1b1c1e); 6 | -------------------------------------------------------------------------------- /lib/helpers/data_search.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_utube/constants.dart'; 3 | import 'dart:async'; 4 | 5 | import '/api/youtube_api.dart'; 6 | import '/helpers/suggestion_history.dart'; 7 | import '/pages/search_page.dart'; 8 | 9 | class DataSearch extends SearchDelegate { 10 | Timer? _timer; 11 | 12 | final YoutubeApi _youtubeApi = YoutubeApi(); 13 | 14 | final list = SuggestionHistory.suggestions; 15 | 16 | 17 | // @override 18 | // ThemeData appBarTheme(BuildContext context) { 19 | // return ThemeData( 20 | // appBarTheme: const AppBarTheme( 21 | // color: PrimaryColor, // affects AppBar's background color 22 | // ), 23 | // ); 24 | // } 25 | 26 | @override 27 | List buildActions(BuildContext context) { 28 | return [ 29 | IconButton( 30 | onPressed: () { 31 | query = ""; 32 | }, 33 | icon: Icon(Icons.clear)) 34 | ]; 35 | } 36 | 37 | @override 38 | Widget buildLeading(BuildContext context) { 39 | return IconButton( 40 | icon: AnimatedIcon( 41 | icon: AnimatedIcons.menu_arrow, 42 | progress: transitionAnimation, 43 | ), 44 | onPressed: () { 45 | close(context, ""); 46 | }, 47 | ); 48 | } 49 | 50 | @override 51 | Widget buildResults(BuildContext context) { 52 | return SearchPage(query); 53 | } 54 | 55 | @override 56 | Widget buildSuggestions(BuildContext context) { 57 | if(query.isEmpty){ 58 | List suggestions = list.reversed.toList(); 59 | return ListView.builder( 60 | itemBuilder: (BuildContext context, int index) { 61 | return GestureDetector( 62 | onTap: () { 63 | query = suggestions[index]; 64 | showResults(context); 65 | }, 66 | child: ListTile( 67 | leading: Icon(Icons.north_west), 68 | title: Text(suggestions[index]), 69 | trailing: Icon(Icons.history_outlined), 70 | ), 71 | ); 72 | }, 73 | itemCount: suggestions.length, 74 | ); 75 | } 76 | return FutureBuilder( 77 | future: _youtubeApi.fetchSuggestions(query), 78 | builder: (BuildContext context, AsyncSnapshot snapshot) { 79 | if (snapshot.hasData) { 80 | var snapshots = snapshot.data; 81 | List suggestions = query.isEmpty ? list : snapshots; 82 | return ListView.builder( 83 | itemBuilder: (BuildContext context, int index) { 84 | return GestureDetector( 85 | onTap: () { 86 | list.add(query); 87 | query = suggestions[index]; 88 | showResults(context); 89 | }, 90 | child: ListTile( 91 | leading: Icon(Icons.north_west), 92 | title: Text(suggestions[index]), 93 | ), 94 | ); 95 | }, 96 | itemCount: suggestions.length, 97 | ); 98 | } 99 | return Container(); 100 | }, 101 | ); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /lib/helpers/shared_helper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter_utube/models/subscribed.dart'; 4 | import 'package:shared_preferences/shared_preferences.dart'; 5 | 6 | class SharedHelper { 7 | SharedPreferences? sharedPreferences; 8 | 9 | Future> getSubscribedChannels() async { 10 | 11 | sharedPreferences = await SharedPreferences.getInstance(); 12 | List subscribedChannels = []; 13 | List? idsList = await _getSubscribedChannelsIds(); 14 | idsList?.forEach((id) async { 15 | Subscribed subscribed = await _getSubscribedChannel(id); 16 | subscribedChannels.add(subscribed); 17 | }); 18 | return subscribedChannels; 19 | } 20 | 21 | Future _getSubscribedChannel(String channelId) async { 22 | String? str = sharedPreferences!.getString(channelId); 23 | return Subscribed.fromJson(jsonDecode(str!)); 24 | } 25 | 26 | Future?> _getSubscribedChannelsIds() async { 27 | List? subscribedChannelsIds = sharedPreferences!.getStringList('subscribedChannelsIds'); 28 | return subscribedChannelsIds; 29 | } 30 | 31 | void _addSubscribedChannelsId(String channelId) async { 32 | List? list = await _getSubscribedChannelsIds(); 33 | list ??= []; 34 | list.add(channelId); 35 | sharedPreferences!.setStringList("subscribedChannelsIds",list); 36 | } 37 | 38 | void _removeSubscribedChannelsId(String channelId) async { 39 | List? list = await _getSubscribedChannelsIds(); 40 | if(list != null){ 41 | list.remove(channelId); 42 | sharedPreferences!.setStringList("subscribedChannelsIds",list); 43 | } 44 | } 45 | 46 | void subscribeChannel(String channelId, String user) async { 47 | sharedPreferences = await SharedPreferences.getInstance(); 48 | sharedPreferences!.setString(channelId, user); 49 | _addSubscribedChannelsId(channelId); 50 | } 51 | 52 | void unSubscribeChannel(String channelId)async{ 53 | sharedPreferences = await SharedPreferences.getInstance(); 54 | sharedPreferences!.remove(channelId); 55 | _removeSubscribedChannelsId(channelId); 56 | } 57 | } -------------------------------------------------------------------------------- /lib/helpers/suggestion_history.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | 3 | class SuggestionHistory{ 4 | static List suggestions = []; 5 | 6 | static void store(String query) async{ 7 | SharedPreferences prefs = await SharedPreferences.getInstance(); 8 | if(suggestions.length > 10){ 9 | suggestions.removeAt(0); 10 | } 11 | if(suggestions.contains(query)){ 12 | suggestions.removeWhere((suggestion) => suggestion == query); 13 | } 14 | suggestions.add(query); 15 | await prefs.setStringList('suggestions', suggestions); 16 | } 17 | 18 | static void init() async{ 19 | SharedPreferences prefs = await SharedPreferences.getInstance(); 20 | suggestions = prefs.getStringList('suggestions')!; 21 | } 22 | } -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'constants.dart'; 4 | import 'pages/home/home_page.dart'; 5 | 6 | void main(){ 7 | runApp(MyApp()); 8 | } 9 | 10 | class MyApp extends StatelessWidget { 11 | @override 12 | Widget build(BuildContext context) { 13 | return MaterialApp( 14 | debugShowCheckedModeBanner: false, 15 | home: HomePage(), 16 | theme: ThemeData( 17 | brightness: Brightness.dark, 18 | primaryColor: PrimaryColor, 19 | scaffoldBackgroundColor: PrimaryColor 20 | ), 21 | ); 22 | } 23 | } -------------------------------------------------------------------------------- /lib/models/channel.dart: -------------------------------------------------------------------------------- 1 | class Channel { 2 | String subscribers, avatar; 3 | String? banner; 4 | 5 | Channel( 6 | {required this.subscribers, required this.avatar, required this.banner}); 7 | } 8 | -------------------------------------------------------------------------------- /lib/models/channel_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | 3 | import '/models/channel.dart'; 4 | import '/api/helpers/helpers_extention.dart'; 5 | import 'package:collection/collection.dart'; 6 | 7 | 8 | class ChannelData { 9 | Channel channel; 10 | List videosList; 11 | bool isSubscribed = false; 12 | 13 | ChannelData({required this.channel, required this.videosList}); 14 | 15 | factory ChannelData.fromMap(Map map) { 16 | var headers = map.get('header'); 17 | String? subscribers = headers 18 | ?.get('c4TabbedHeaderRenderer') 19 | ?.get('subscriberCountText')?['simpleText']; 20 | var thumbnails = headers 21 | ?.get('c4TabbedHeaderRenderer') 22 | ?.get('avatar') 23 | ?.getList('thumbnails'); 24 | String avatar = thumbnails?.elementAtSafe(thumbnails.length - 1)?['url']; 25 | String? banner = headers 26 | ?.get('c4TabbedHeaderRenderer') 27 | ?.get('banner') 28 | ?.getList('thumbnails') 29 | ?.first['url']; 30 | var contents = map 31 | .get('contents') 32 | ?.get('twoColumnBrowseResultsRenderer') 33 | ?.getList('tabs')?[1] 34 | .get('tabRenderer') 35 | ?.get('content') 36 | ?.get('sectionListRenderer') 37 | ?.getList('contents') 38 | ?.firstOrNull 39 | ?.get('itemSectionRenderer') 40 | ?.getList('contents') 41 | ?.firstOrNull 42 | ?.get('gridRenderer') 43 | ?.getList('items'); 44 | List list = contents!.toList(); 45 | return ChannelData(videosList: list, channel: Channel(subscribers: (subscribers != null) ? subscribers : " ", avatar: avatar, banner: banner)); 46 | } 47 | 48 | void checkIsSubscribed(String channelId) async { 49 | SharedPreferences sharedPreferences = await SharedPreferences.getInstance(); 50 | String? s = sharedPreferences.getString(channelId); 51 | if(s == null){ 52 | isSubscribed = false; 53 | } else { 54 | isSubscribed = true; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/models/my_video.dart: -------------------------------------------------------------------------------- 1 | class MyVideo { 2 | String videoId, 3 | title, 4 | date, 5 | username, 6 | viewCount, 7 | likeCount, 8 | unlikeCount, 9 | channelThumb, 10 | channelId; 11 | String? subscribeCount; 12 | 13 | MyVideo( 14 | {required this.videoId, 15 | required this.title, 16 | required this.username, 17 | required this.viewCount, 18 | this.subscribeCount, 19 | required this.likeCount, 20 | required this.unlikeCount, 21 | required this.date, 22 | required this.channelThumb, 23 | required this.channelId}); 24 | } 25 | -------------------------------------------------------------------------------- /lib/models/subscribed.dart: -------------------------------------------------------------------------------- 1 | class Subscribed { 2 | String username, channelId, avatar, videosCount; 3 | 4 | Subscribed( 5 | {required this.username, 6 | required this.channelId, 7 | required this.avatar, 8 | required this.videosCount}); 9 | 10 | factory Subscribed.fromJson(Map parsedJson) { 11 | return Subscribed( 12 | username: parsedJson['username'] ?? "", 13 | channelId: parsedJson['channelId'] ?? "", 14 | avatar: parsedJson['avatar'] ?? "", 15 | videosCount: parsedJson['videosCount'] ?? "", 16 | ); 17 | } 18 | 19 | Map toJson() { 20 | return { 21 | "username": username, 22 | "channelId": channelId, 23 | "avatar": avatar, 24 | "videosCount": videosCount, 25 | }; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/models/video_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_utube/models/my_video.dart'; 2 | 3 | class VideoData { 4 | MyVideo video; 5 | List>? videosList; 6 | 7 | VideoData({required this.video, required this.videosList}); 8 | } -------------------------------------------------------------------------------- /lib/pages/channel/body.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_utube/helpers/shared_helper.dart'; 5 | import '../../models/subscribed.dart'; 6 | import '/api/youtube_api.dart'; 7 | import '/models/channel_data.dart'; 8 | import '/widgets/video_widget.dart'; 9 | 10 | class Body extends StatefulWidget { 11 | final title; 12 | final ChannelData channelData; 13 | YoutubeApi youtubeApi; 14 | String channelId; 15 | 16 | Body( 17 | {required this.channelData, 18 | required this.title, 19 | required this.youtubeApi, 20 | required this.channelId}); 21 | 22 | @override 23 | _BodyState createState() => _BodyState(); 24 | } 25 | 26 | class _BodyState extends State { 27 | late ScrollController controller; 28 | late List contentList; 29 | Subscribed? subscribed; 30 | bool videosEnd = false; 31 | bool isLoading = true; 32 | bool? isSubscribed; 33 | SharedHelper sharedHelper = SharedHelper(); 34 | List subscribedChannelsIds = []; 35 | 36 | @override 37 | void initState() { 38 | contentList = widget.channelData.videosList; 39 | controller = ScrollController()..addListener(_scrollListener); 40 | subscribed = Subscribed(username: widget.title, channelId: widget.channelId, avatar: widget.channelData.channel.avatar, videosCount: ""); 41 | isSubscribed = widget.channelData.isSubscribed; 42 | super.initState(); 43 | } 44 | 45 | @override 46 | void dispose() { 47 | controller.removeListener(_scrollListener); 48 | super.dispose(); 49 | } 50 | 51 | @override 52 | Widget build(BuildContext context) { 53 | return SingleChildScrollView( 54 | controller: controller, 55 | child: Stack( 56 | children: [ 57 | Column( 58 | children: [ 59 | widget.channelData.channel.banner!= null ? Container( 60 | height: 80, 61 | width: double.infinity, 62 | decoration: BoxDecoration( 63 | image: DecorationImage( 64 | image: Image.network(widget.channelData.channel.banner!) 65 | .image, 66 | fit: BoxFit.cover)), 67 | ): Container() , 68 | Container( 69 | color: Colors.black26, 70 | padding: const EdgeInsets.only( 71 | left: 100, right: 20, bottom: 10, top: 10), 72 | child: Row( 73 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 74 | children: [ 75 | SizedBox( 76 | width: 150, 77 | child: Column( 78 | children: [ 79 | Text( 80 | widget.title, 81 | style: const TextStyle( 82 | fontSize: 18, 83 | fontFamily: 'Cairo' 84 | ), 85 | ), 86 | Text( 87 | widget.channelData.channel.subscribers, 88 | style: const TextStyle( 89 | color: Colors.grey, 90 | fontSize: 12, 91 | fontFamily: 'Cairo' 92 | ), 93 | ) 94 | ], 95 | ), 96 | ), 97 | SizedBox( 98 | height: 35, 99 | width: 90, 100 | child: isSubscribed! 101 | ? TextButton( 102 | onPressed: () async { 103 | unSubscribe(); 104 | }, 105 | style: ButtonStyle( 106 | backgroundColor: 107 | MaterialStateProperty.all(Colors.redAccent), 108 | shape: 109 | MaterialStateProperty.all( 110 | RoundedRectangleBorder( 111 | borderRadius: BorderRadius.circular(2), 112 | ), 113 | ), 114 | textStyle: MaterialStateProperty.all( 115 | const TextStyle( 116 | fontSize: 11, 117 | fontFamily: 'Cairo' 118 | ), 119 | ), 120 | ), 121 | child: const Text( 122 | 'Subscribed', 123 | style: TextStyle( 124 | color: Colors.white, 125 | fontFamily: 'Cairo' 126 | ), 127 | ), 128 | ) 129 | : TextButton( 130 | onPressed: () async { 131 | subscribe(); 132 | }, 133 | style: ButtonStyle( 134 | backgroundColor: 135 | MaterialStateProperty.all(Colors.red), 136 | shape: 137 | MaterialStateProperty.all( 138 | RoundedRectangleBorder( 139 | borderRadius: BorderRadius.circular(2), 140 | ), 141 | ), 142 | textStyle: MaterialStateProperty.all( 143 | const TextStyle( 144 | fontSize: 11, 145 | fontFamily: 'Cairo' 146 | ), 147 | ), 148 | ), 149 | child: const Text( 150 | 'Subscribe', 151 | style: TextStyle( 152 | color: Colors.white, 153 | fontFamily: 'Cairo' 154 | ), 155 | ), 156 | ), 157 | ), 158 | ], 159 | ), 160 | ), 161 | Padding( 162 | padding: const EdgeInsets.only(bottom: 50), 163 | child: ListView.builder( 164 | shrinkWrap: true, 165 | physics: const NeverScrollableScrollPhysics(), 166 | itemCount: contentList.length, 167 | itemBuilder: (context, index) { 168 | if (contentList[index].containsKey('gridVideoRenderer')) { 169 | return video(index, contentList); 170 | } 171 | return Container(); 172 | }, 173 | ), 174 | ) 175 | ], 176 | ), 177 | Padding( 178 | padding: const EdgeInsets.only(left: 10, top: 50), 179 | child: Container( 180 | height: 60, 181 | width: 60, 182 | alignment: Alignment.center, 183 | decoration: BoxDecoration( 184 | shape: BoxShape.circle, 185 | color: Colors.white, 186 | ), 187 | child: Container( 188 | height: 55, 189 | width: 55, 190 | decoration: BoxDecoration( 191 | shape: BoxShape.circle, 192 | image: DecorationImage( 193 | image: 194 | Image.network(widget.channelData.channel.avatar) 195 | .image, 196 | fit: BoxFit.cover))), 197 | ), 198 | ), 199 | Visibility( 200 | visible: isLoading, 201 | child: Container( 202 | child: Positioned( 203 | bottom: 10, 204 | right: 180, 205 | child: CircularProgressIndicator(), 206 | ), 207 | ), 208 | ) 209 | ], 210 | ), 211 | ); 212 | } 213 | 214 | void _scrollListener() { 215 | if (controller.position.atEdge) { 216 | if (controller.position.pixels != 0 && videosEnd == false) { 217 | _loadMore(); 218 | } 219 | } 220 | } 221 | 222 | Widget video(int index, List contentList) { 223 | String? simpleText = contentList[index]['gridVideoRenderer'] 224 | ['shortViewCountText']?['simpleText']; 225 | return VideoWidget( 226 | videoId: contentList[index]['gridVideoRenderer']['videoId'], 227 | duration: contentList[index]['gridVideoRenderer']['thumbnailOverlays'][0] 228 | ['thumbnailOverlayTimeStatusRenderer']['text']['simpleText'], 229 | title: contentList[index]['gridVideoRenderer']['title']['runs'][0] 230 | ['text'], 231 | channelName: widget.title, 232 | views: (simpleText != null) ? simpleText : "???", 233 | ); 234 | } 235 | 236 | void subscribe () async { 237 | sharedHelper.subscribeChannel(widget.channelId, jsonEncode(subscribed!.toJson())); 238 | setState((){ 239 | isSubscribed = true; 240 | }); 241 | } 242 | 243 | void unSubscribe () async { 244 | sharedHelper.unSubscribeChannel(widget.channelId); 245 | setState((){ 246 | isSubscribed = false; 247 | }); 248 | } 249 | 250 | void _loadMore() async { 251 | List? list = await widget.youtubeApi.loadMoreInChannel(); 252 | if (list != null) { 253 | setState(() { 254 | contentList.addAll(list); 255 | }); 256 | } else { 257 | videosEnd = true; 258 | setState(() { 259 | isLoading = false; 260 | }); 261 | } 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /lib/pages/channel/channel_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:line_icons/line_icons.dart'; 3 | import '/api/youtube_api.dart'; 4 | import '/models/channel_data.dart'; 5 | import '/pages/channel/body.dart'; 6 | 7 | class ChannelPage extends StatefulWidget { 8 | final id; 9 | final title; 10 | 11 | const ChannelPage({Key? key, required this.id, required this.title}) 12 | : super(key: key); 13 | 14 | @override 15 | _ChannelPageState createState() => _ChannelPageState(); 16 | } 17 | 18 | class _ChannelPageState extends State { 19 | YoutubeApi youtubeApi = YoutubeApi(); 20 | ChannelData? channelData; 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return Scaffold( 25 | appBar: AppBar( 26 | title: Text(widget.title), 27 | actions: [ 28 | IconButton( 29 | onPressed: (){}, 30 | icon: Icon(LineIcons.rss, color: Colors.white), 31 | ), 32 | IconButton( 33 | onPressed: (){}, 34 | icon: Icon(LineIcons.share, color: Colors.white), 35 | ) 36 | ], 37 | ), 38 | body: body(), 39 | ); 40 | } 41 | 42 | Widget body() { 43 | return RefreshIndicator( 44 | onRefresh: _refresh, 45 | child: FutureBuilder( 46 | future: youtubeApi.fetchChannelData(widget.id), 47 | builder: (BuildContext context, AsyncSnapshot snapshot) { 48 | switch (snapshot.connectionState) { 49 | case ConnectionState.waiting: 50 | return _loading(); 51 | case ConnectionState.active: 52 | return _loading(); 53 | case ConnectionState.none: 54 | return Container(child: Text("Connection None")); 55 | case ConnectionState.done: 56 | if (snapshot.error != null) { 57 | return Center( 58 | child: Container(child: Text(snapshot.stackTrace.toString()))); 59 | } else { 60 | if (snapshot.hasData) { 61 | return Body(channelData: snapshot.data, title: widget.title, youtubeApi: youtubeApi, channelId: widget.id,); 62 | } else { 63 | return Center(child: Container(child: Text("No data"))); 64 | } 65 | } 66 | } 67 | }, 68 | ), 69 | ); 70 | } 71 | 72 | 73 | Widget _loading() { 74 | return Container( 75 | child: Center( 76 | child: Padding( 77 | padding: const EdgeInsets.only(top: 100), 78 | child: CircularProgressIndicator(), 79 | ), 80 | ), 81 | ); 82 | } 83 | 84 | Future _refresh() async { 85 | setState(() {}); 86 | return true; 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /lib/pages/home/body.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '/api/youtube_api.dart'; 3 | import '/widgets/video_widget.dart'; 4 | 5 | class Body extends StatefulWidget { 6 | List contentList; 7 | YoutubeApi youtubeApi; 8 | 9 | Body( 10 | {Key? key, 11 | required this.contentList, 12 | required this.youtubeApi,}) 13 | : super(key: key); 14 | 15 | @override 16 | _BodyState createState() => _BodyState(contentList); 17 | } 18 | 19 | class _BodyState extends State { 20 | List contentList; 21 | 22 | _BodyState(this.contentList); 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return SafeArea( 27 | child: ListView.builder( 28 | shrinkWrap: true, 29 | physics: const NeverScrollableScrollPhysics(), 30 | itemCount: contentList.length, 31 | itemBuilder: (context, index) { 32 | if (contentList[index].containsKey('videoRenderer')) { 33 | return video(index, contentList); 34 | } 35 | return Container(); 36 | }, 37 | ), 38 | ); 39 | } 40 | 41 | Widget video(int index, List contentList) { 42 | return VideoWidget( 43 | videoId: contentList[index]['videoRenderer']['videoId'], 44 | duration: contentList[index]['videoRenderer']['lengthText']['simpleText'], 45 | title: contentList[index]['videoRenderer']['title']['runs'][0]['text'], 46 | channelName: contentList[index]['videoRenderer']['longBylineText']['runs'] 47 | [0]['text'], 48 | views: contentList[index]['videoRenderer']['shortViewCountText'] 49 | ['simpleText'], 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/pages/home/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_utube/pages/home/subscribed_channels.dart'; 3 | import '../../constants.dart'; 4 | import '../../theme/colors.dart'; 5 | import '../../utilities/categories.dart'; 6 | import '/api/youtube_api.dart'; 7 | import '/pages/home/body.dart'; 8 | import '/utilities/custom_app_bar.dart'; 9 | import '/widgets/loading.dart'; 10 | 11 | class HomePage extends StatefulWidget { 12 | @override 13 | _HomePageState createState() => _HomePageState(); 14 | } 15 | 16 | class _HomePageState extends State { 17 | YoutubeApi youtubeApi = YoutubeApi(); 18 | List? contentList; 19 | int _selectedIndex = 0; 20 | late Future trending; 21 | int trendingIndex = 0; 22 | late double progressPosition; 23 | 24 | @override 25 | void initState() { 26 | super.initState(); 27 | trending = youtubeApi.fetchTrendingVideo(); 28 | contentList = []; 29 | } 30 | 31 | @override 32 | void dispose() { 33 | super.dispose(); 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | progressPosition = MediaQuery.of(context).size.height / 0.5; 39 | return Scaffold( 40 | backgroundColor: SecondaryColor, 41 | appBar: CustomAppBar(), 42 | body: body(), 43 | bottomNavigationBar: customBottomNavigationBar(), 44 | ); 45 | } 46 | 47 | Widget body() { 48 | switch (_selectedIndex) { 49 | case 1: 50 | return SubscribedChannels(); 51 | case 2: 52 | return Center( 53 | child: Text("TODO"), 54 | ); 55 | case 3: 56 | return Center( 57 | child: Text("TODO"), 58 | ); 59 | } 60 | return RefreshIndicator( 61 | onRefresh: _refresh, 62 | child: SingleChildScrollView( 63 | child: Column( 64 | children: [ 65 | Padding( 66 | padding: EdgeInsets.only(left: 10, right: 10, top: 18, bottom: 10), 67 | child: Categories( 68 | callback: changeTrendingState, 69 | trendingIndex: trendingIndex, 70 | ), 71 | ), 72 | FutureBuilder( 73 | future: trending, 74 | builder: (BuildContext context, AsyncSnapshot snapshot) { 75 | switch (snapshot.connectionState) { 76 | case ConnectionState.waiting: 77 | return Padding( 78 | padding: EdgeInsets.only(top: 300), 79 | child: loading(), 80 | ); 81 | case ConnectionState.active: 82 | return Padding( 83 | padding: EdgeInsets.only(top: 300), 84 | child: loading(), 85 | ); 86 | case ConnectionState.none: 87 | return const Text("Connection None"); 88 | case ConnectionState.done: 89 | if (snapshot.error != null) { 90 | return Container( 91 | child: Text(snapshot.stackTrace.toString())); 92 | } else { 93 | if (snapshot.hasData) { 94 | contentList = snapshot.data; 95 | return Body( 96 | contentList: contentList!, youtubeApi: youtubeApi); 97 | } else { 98 | return Center(child: Container(child: Text("No data"))); 99 | } 100 | } 101 | } 102 | }, 103 | ), 104 | ], 105 | ), 106 | ), 107 | ); 108 | } 109 | 110 | Widget customBottomNavigationBar() { 111 | return Container( 112 | decoration: const BoxDecoration( 113 | borderRadius: BorderRadius.only( 114 | topRight: Radius.circular(30), topLeft: Radius.circular(30)), 115 | boxShadow: [ 116 | BoxShadow(color: Colors.black12, spreadRadius: 0, blurRadius: 10), 117 | ], 118 | ), 119 | child: ClipRRect( 120 | borderRadius: const BorderRadius.only( 121 | topLeft: Radius.circular(30.0), 122 | topRight: Radius.circular(30.0), 123 | ), 124 | child: BottomNavigationBar( 125 | currentIndex: _selectedIndex, 126 | type: BottomNavigationBarType.fixed, 127 | showUnselectedLabels: true, 128 | onTap: _onItemTapped, 129 | backgroundColor: const Color(0xff424242), 130 | selectedItemColor: pink, 131 | selectedLabelStyle: TextStyle(fontFamily: 'Cairo'), 132 | unselectedLabelStyle: TextStyle(fontFamily: 'Cairo'), 133 | items: const [ 134 | BottomNavigationBarItem( 135 | icon: Icon(Icons.local_fire_department), 136 | label: 'Trending', 137 | ), 138 | BottomNavigationBarItem( 139 | icon: Icon(Icons.live_tv), label: 'Subscriptions'), 140 | BottomNavigationBarItem( 141 | icon: Icon(Icons.history), label: 'History'), 142 | BottomNavigationBarItem( 143 | icon: Icon(Icons.cloud_download), label: 'Downloads') 144 | ], 145 | ), 146 | )); 147 | } 148 | 149 | void _onItemTapped(int index) { 150 | setState(() { 151 | _selectedIndex = index; 152 | }); 153 | } 154 | 155 | void changeTrendingState(int index) { 156 | switch (index) { 157 | case 0: 158 | setState(() { 159 | trending = youtubeApi.fetchTrendingVideo(); 160 | }); 161 | break; 162 | case 1: 163 | setState(() { 164 | trending = youtubeApi.fetchTrendingMusic(); 165 | }); 166 | break; 167 | case 2: 168 | setState(() { 169 | trending = youtubeApi.fetchTrendingGaming(); 170 | }); 171 | break; 172 | case 3: 173 | setState(() { 174 | trending = youtubeApi.fetchTrendingMovies(); 175 | }); 176 | break; 177 | } 178 | trendingIndex = index; 179 | } 180 | 181 | Future _refresh() async { 182 | List newList = await youtubeApi.fetchTrendingVideo(); 183 | if(newList.isNotEmpty){ 184 | setState(() { 185 | contentList = newList; 186 | }); 187 | return true; 188 | } 189 | return false; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /lib/pages/home/subscribed_channels.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_utube/models/subscribed.dart'; 3 | import 'package:flutter_utube/widgets/channel_widget.dart'; 4 | 5 | import '../../helpers/shared_helper.dart'; 6 | import '../../widgets/loading.dart'; 7 | 8 | class SubscribedChannels extends StatefulWidget { 9 | const SubscribedChannels({Key? key}) : super(key: key); 10 | 11 | @override 12 | State createState() => _SubscribedChannelsState(); 13 | } 14 | 15 | class _SubscribedChannelsState extends State { 16 | SharedHelper sharedHelper = SharedHelper(); 17 | List? subscribedChannels; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return FutureBuilder( 22 | future: sharedHelper.getSubscribedChannels(), 23 | builder: (BuildContext context, AsyncSnapshot snapshot) { 24 | switch (snapshot.connectionState) { 25 | case ConnectionState.waiting: 26 | return loading(); 27 | case ConnectionState.active: 28 | return loading(); 29 | case ConnectionState.none: 30 | return const Text("Connection None"); 31 | case ConnectionState.done: 32 | if (snapshot.hasData) { 33 | subscribedChannels = snapshot.data; 34 | if(subscribedChannels!.isNotEmpty){ 35 | return ListView.builder( 36 | itemCount: subscribedChannels!.length, 37 | itemBuilder: (BuildContext context, int index) { 38 | return GestureDetector( 39 | onTap: () { 40 | 41 | }, 42 | child: channel(subscribedChannels![index]), 43 | ); 44 | }, 45 | ); 46 | } 47 | } 48 | return const Center( 49 | child: Text( 50 | "لست مشتركاً في أي قناة", 51 | style: TextStyle( 52 | fontSize: 20, 53 | fontFamily: 'Cairo' 54 | ), 55 | ), 56 | ); 57 | } 58 | }, 59 | ); 60 | } 61 | 62 | Widget channel(Subscribed subscribed) { 63 | return ChannelWidget( 64 | id: subscribed.channelId, 65 | thumbnail: subscribed.avatar, 66 | title: subscribed.username, 67 | videoCount: '', 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/pages/playlist_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:lazy_load_scrollview/lazy_load_scrollview.dart'; 3 | import 'package:line_icons/line_icons.dart'; 4 | import '/api/youtube_api.dart'; 5 | import '/widgets/video_widget.dart'; 6 | 7 | class PlayListPage extends StatefulWidget { 8 | final title, id; 9 | const PlayListPage({Key? key, required this.title, required this.id}) : super(key: key); 10 | 11 | @override 12 | _PlayListPageState createState() => _PlayListPageState(); 13 | } 14 | 15 | class _PlayListPageState extends State { 16 | 17 | YoutubeApi youtubeApi = YoutubeApi(); 18 | List contentList = []; 19 | bool isLoading = false; 20 | bool firstLoad = true; 21 | int loaded = 0; 22 | 23 | @override 24 | void initState() { 25 | _loadMore(); 26 | super.initState(); 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return Scaffold( 32 | appBar: AppBar( 33 | title: Text(widget.title), 34 | actions: [ 35 | IconButton( 36 | onPressed: (){}, 37 | icon: const Icon(LineIcons.rss, color: Colors.white), 38 | ), 39 | IconButton( 40 | onPressed: (){}, 41 | icon: const Icon(LineIcons.share, color: Colors.white), 42 | ) 43 | ], 44 | ), 45 | body: body(), 46 | ); 47 | } 48 | 49 | Widget body() { 50 | return SafeArea( 51 | child: Stack( 52 | children: [ 53 | Visibility( 54 | visible: firstLoad, 55 | child: const Center( 56 | child: CircularProgressIndicator(), 57 | ), 58 | ), 59 | LazyLoadScrollView( 60 | isLoading: true, 61 | onEndOfPage: () => _loadMore(), 62 | child: SafeArea( 63 | child: ListView.builder( 64 | itemCount: contentList.length, 65 | itemBuilder: (context, index) { 66 | if (contentList[index].containsKey('playlistVideoRenderer')) { 67 | return video(index, contentList); 68 | } 69 | return Container(); 70 | }, 71 | ), 72 | ), 73 | ) 74 | ], 75 | ), 76 | ); 77 | } 78 | 79 | Widget video(int index, List contentList) { 80 | return VideoWidget( 81 | videoId: contentList[index]['playlistVideoRenderer']['videoId'], 82 | duration: contentList[index]['playlistVideoRenderer']['lengthText']['simpleText'], 83 | title: contentList[index]['playlistVideoRenderer']['title']['runs'][0]['text'], 84 | channelName: contentList[index]['playlistVideoRenderer']['shortBylineText']['runs'][0]['text'], 85 | views: "", 86 | ); 87 | } 88 | 89 | Future _loadMore() async { 90 | 91 | setState(() { 92 | isLoading = true; 93 | }); 94 | if(loaded >= 1){ 95 | List? newList = await youtubeApi.loadMoreInPlayList(); 96 | if(newList != null){ 97 | contentList.addAll(newList); 98 | } 99 | } else { 100 | List newList = await youtubeApi.fetchPlayListVideos(widget.id, loaded); 101 | contentList.addAll(newList); 102 | } 103 | loaded++; 104 | setState(() { 105 | isLoading = false; 106 | firstLoad = false; 107 | }); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /lib/pages/search_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:lazy_load_scrollview/lazy_load_scrollview.dart'; 3 | import '/api/youtube_api.dart'; 4 | import '/helpers/suggestion_history.dart'; 5 | import '/widgets/channel_widget.dart'; 6 | import '/widgets/playList_widget.dart'; 7 | import '/widgets/video_widget.dart'; 8 | 9 | class SearchPage extends StatefulWidget { 10 | String query; 11 | 12 | SearchPage(this.query); 13 | 14 | @override 15 | _SearchPageState createState() => _SearchPageState(); 16 | } 17 | 18 | class _SearchPageState extends State { 19 | YoutubeApi _youtubeApi = YoutubeApi(); 20 | List? contentList; 21 | bool isLoading = false; 22 | bool firstLoad = true; 23 | 24 | @override 25 | void initState() { 26 | contentList = []; 27 | _loadMore(widget.query); 28 | SuggestionHistory.store(widget.query); 29 | super.initState(); 30 | } 31 | 32 | @override 33 | void dispose() { 34 | super.dispose(); 35 | } 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | return Scaffold( 40 | body: body(), 41 | ); 42 | } 43 | 44 | Widget body() { 45 | return SafeArea( 46 | child: Stack( 47 | children: [ 48 | Visibility( 49 | visible: firstLoad, 50 | child: Center( 51 | child: CircularProgressIndicator(), 52 | ), 53 | ), 54 | LazyLoadScrollView( 55 | isLoading: isLoading, 56 | onEndOfPage: () => _loadMore(widget.query), 57 | child: ListView.builder( 58 | itemCount: contentList!.length, 59 | itemBuilder: (context, index) { 60 | if (isLoading && index == contentList!.length - 1) { 61 | return Center(child: CircularProgressIndicator()); 62 | } else { 63 | if (contentList![index].containsKey('videoRenderer')) { 64 | return video(index, contentList!); 65 | } else if (contentList![index] 66 | .containsKey('channelRenderer')) { 67 | return channel(index, contentList!); 68 | } else if (contentList![index] 69 | .containsKey('playlistRenderer')) { 70 | return playList(index, contentList!); 71 | } 72 | return Container(); 73 | } 74 | }, 75 | ), 76 | ) 77 | ], 78 | ), 79 | ); 80 | } 81 | 82 | Widget video(int index, List contentList) { 83 | var lengthText = contentList[index]['videoRenderer']['lengthText']; 84 | var simpleText = contentList[index]['videoRenderer']['shortViewCountText']['simpleText']; 85 | return VideoWidget( 86 | videoId: contentList[index]['videoRenderer']['videoId'], 87 | duration: (lengthText == null) ? "Live" : lengthText['simpleText'], 88 | title: contentList[index]['videoRenderer']['title']['runs'][0]['text'], 89 | channelName: contentList[index]['videoRenderer']['longBylineText']['runs'] 90 | [0]['text'], 91 | views: (lengthText == null) ? "Views " + contentList[index]['videoRenderer']['viewCountText']['runs'][0]['text'] : simpleText , 92 | ); 93 | } 94 | 95 | Widget playList(int index, List contentList) { 96 | return PlayListWidget( 97 | id: contentList[index]['playlistRenderer']['playlistId'], 98 | thumbnails: contentList[index]['playlistRenderer']['thumbnails'][0]['thumbnails'], 99 | videoCount: contentList[index]['playlistRenderer']['videoCount'], 100 | title: contentList[index]['playlistRenderer']['title']['simpleText'], 101 | channelName: contentList[index]['playlistRenderer']['shortBylineText']['runs'][0]['text'], 102 | ); 103 | } 104 | 105 | 106 | Widget channel(int index, List contentList) { 107 | return ChannelWidget( 108 | id: contentList[index]['channelRenderer']['channelId'], 109 | thumbnail: contentList[index]['channelRenderer']['thumbnail'] 110 | ['thumbnails'][0]['url'], 111 | title: contentList[index]['channelRenderer']['title']['simpleText'], 112 | videoCount: contentList[index]['channelRenderer']['videoCountText'] 113 | ['runs'][0]['text'], 114 | ); 115 | } 116 | 117 | Future _loadMore(String query) async { 118 | setState(() { 119 | isLoading = true; 120 | }); 121 | List newList = await _youtubeApi.fetchSearchVideo(query); 122 | contentList!.addAll(newList); 123 | setState(() { 124 | isLoading = false; 125 | firstLoad = false; 126 | }); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /lib/pages/video_detail_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_utube/models/video_data.dart'; 3 | import 'package:line_icons/line_icons.dart'; 4 | import 'package:pod_player/pod_player.dart'; 5 | import '../api/youtube_api.dart'; 6 | import '../constants.dart'; 7 | import '../helpers/shared_helper.dart'; 8 | import '/theme/colors.dart'; 9 | import 'channel/channel_page.dart'; 10 | 11 | class VideoDetailPage extends StatefulWidget { 12 | String videoId; 13 | 14 | VideoDetailPage({required this.videoId}); 15 | 16 | @override 17 | _VideoDetailPageState createState() => _VideoDetailPageState(); 18 | } 19 | 20 | class _VideoDetailPageState extends State { 21 | bool isSwitched = true; 22 | late PodPlayerController _controller; 23 | 24 | // for video player 25 | late int _playBackTime; 26 | 27 | //The values that are passed when changing quality 28 | late Duration newCurrentPosition; 29 | 30 | YoutubeApi youtubeApi = YoutubeApi(); 31 | VideoData? videoData; 32 | double? progressPadding; 33 | final unknown = "Unknown"; 34 | bool? isSubscribed; 35 | SharedHelper sharedHelper = SharedHelper(); 36 | 37 | @override 38 | void initState() { 39 | super.initState(); 40 | _controller = PodPlayerController( 41 | playVideoFrom: 42 | PlayVideoFrom.youtube('https://youtu.be/${widget.videoId}'), 43 | )..initialise(); 44 | } 45 | 46 | @override 47 | void dispose() { 48 | super.dispose(); 49 | _controller.dispose(); 50 | } 51 | 52 | @override 53 | Widget build(BuildContext context) { 54 | progressPadding = MediaQuery.of(context).size.height * 0.3; 55 | return Scaffold( 56 | backgroundColor: SecondaryColor, 57 | body: getBody(), 58 | ); 59 | } 60 | 61 | Widget getBody() { 62 | var size = MediaQuery.of(context).size; 63 | return SafeArea( 64 | child: Column( 65 | children: [ 66 | PodVideoPlayer(controller: _controller), 67 | FutureBuilder( 68 | future: youtubeApi.fetchVideoData(widget.videoId), 69 | builder: (BuildContext context, AsyncSnapshot snapshot) { 70 | switch (snapshot.connectionState) { 71 | case ConnectionState.waiting: 72 | return Padding( 73 | padding: EdgeInsets.only(top: progressPadding!), 74 | child: const CircularProgressIndicator(), 75 | ); 76 | case ConnectionState.active: 77 | return Padding( 78 | padding: EdgeInsets.only(top: progressPadding!), 79 | child: const CircularProgressIndicator(), 80 | ); 81 | case ConnectionState.none: 82 | return const Text("Connection None"); 83 | case ConnectionState.done: 84 | if (snapshot.error != null) { 85 | return Center(child: Text(snapshot.stackTrace.toString())); 86 | } else { 87 | if (snapshot.hasData) { 88 | videoData = snapshot.data; 89 | return Expanded( 90 | child: SingleChildScrollView( 91 | child: Column( 92 | children: [ 93 | Padding( 94 | padding: const EdgeInsets.only( 95 | left: 20, right: 20, top: 10), 96 | child: Column( 97 | children: [ 98 | Row( 99 | crossAxisAlignment: 100 | CrossAxisAlignment.start, 101 | mainAxisAlignment: 102 | MainAxisAlignment.spaceBetween, 103 | children: [ 104 | SizedBox( 105 | width: size.width - 80, 106 | child: Text( 107 | videoData!.video.title, 108 | style: TextStyle( 109 | fontSize: 14, 110 | fontFamily: 'Cairo', 111 | color: white.withOpacity(0.8), 112 | fontWeight: FontWeight.w500, 113 | height: 1.3 114 | ), 115 | ), 116 | ), 117 | Padding( 118 | padding: const EdgeInsets.only(top: 3), 119 | child: GestureDetector( 120 | onTap: () { 121 | _controller.pause(); 122 | Navigator.pop(context); 123 | }, 124 | child: Icon( 125 | LineIcons.angleDown, 126 | color: white.withOpacity(0.7), 127 | size: 18, 128 | ), 129 | ), 130 | ) 131 | ], 132 | ), 133 | const SizedBox( 134 | height: 5, 135 | ), 136 | Row( 137 | children: [ 138 | Text( 139 | videoData!.video.date, 140 | style: TextStyle( 141 | color: white.withOpacity(0.4), 142 | fontSize: 13, 143 | fontFamily: 'Cairo' 144 | ), 145 | ), 146 | const SizedBox( 147 | width: 10, 148 | ), 149 | Text( 150 | videoData!.video.viewCount, 151 | style: TextStyle( 152 | color: white.withOpacity(0.4), 153 | fontSize: 13, 154 | fontFamily: 'Cairo' 155 | ), 156 | ) 157 | ], 158 | ), 159 | const SizedBox( 160 | height: 20, 161 | ), 162 | Padding( 163 | padding: const EdgeInsets.only( 164 | left: 10, right: 10), 165 | child: Row( 166 | mainAxisAlignment: 167 | MainAxisAlignment.spaceBetween, 168 | children: [ 169 | Column( 170 | children: [ 171 | Icon( 172 | LineIcons.thumbsUp, 173 | color: white.withOpacity(0.5), 174 | size: 26, 175 | ), 176 | const SizedBox( 177 | height: 2, 178 | ), 179 | Text( 180 | videoData!.video.likeCount, 181 | style: TextStyle( 182 | color: white.withOpacity(0.4), 183 | fontSize: 13, 184 | fontFamily: 'Cairo' 185 | ), 186 | ) 187 | ], 188 | ), 189 | Column( 190 | children: [ 191 | Icon( 192 | LineIcons.thumbsDown, 193 | color: white.withOpacity(0.5), 194 | size: 26, 195 | ), 196 | const SizedBox( 197 | height: 2, 198 | ), 199 | Text( 200 | 'Dislike', 201 | style: TextStyle( 202 | color: white.withOpacity(0.4), 203 | fontSize: 13, 204 | fontFamily: 'Cairo' 205 | ), 206 | ) 207 | ], 208 | ), 209 | Column( 210 | children: [ 211 | Icon( 212 | LineIcons.share, 213 | color: white.withOpacity(0.5), 214 | size: 26, 215 | ), 216 | const SizedBox( 217 | height: 2, 218 | ), 219 | Text( 220 | "Share", 221 | style: TextStyle( 222 | color: white.withOpacity(0.4), 223 | fontSize: 13, 224 | fontFamily: 'Cairo' 225 | ), 226 | ) 227 | ], 228 | ), 229 | Column( 230 | children: [ 231 | Icon( 232 | LineIcons.download, 233 | color: white.withOpacity(0.5), 234 | size: 26, 235 | ), 236 | const SizedBox( 237 | height: 2, 238 | ), 239 | Text( 240 | "Download", 241 | style: TextStyle( 242 | color: white.withOpacity(0.4), 243 | fontSize: 13, 244 | fontFamily: 'Cairo' 245 | ), 246 | ) 247 | ], 248 | ), 249 | Column( 250 | children: [ 251 | Icon( 252 | LineIcons.plus, 253 | color: white.withOpacity(0.5), 254 | size: 26, 255 | ), 256 | const SizedBox( 257 | height: 2, 258 | ), 259 | Text( 260 | "Save", 261 | style: TextStyle( 262 | color: white.withOpacity(0.4), 263 | fontSize: 13, 264 | fontFamily: 'Cairo' 265 | ), 266 | ) 267 | ], 268 | ) 269 | ], 270 | ), 271 | ), 272 | const SizedBox( 273 | height: 10, 274 | ), 275 | ], 276 | ), 277 | ), 278 | Divider( 279 | color: white.withOpacity(0.1), 280 | ), 281 | const SizedBox( 282 | height: 10, 283 | ), 284 | Padding( 285 | padding: 286 | const EdgeInsets.only(left: 20, right: 20), 287 | child: Row( 288 | mainAxisAlignment: 289 | MainAxisAlignment.spaceBetween, 290 | children: [ 291 | GestureDetector( 292 | onTap: () { 293 | _controller.pause(); 294 | Navigator.push( 295 | context, 296 | MaterialPageRoute( 297 | builder: (context) => ChannelPage( 298 | id: videoData!.video.channelId, 299 | title: 300 | videoData!.video.username)), 301 | ); 302 | }, 303 | child: Row( 304 | children: [ 305 | Container( 306 | width: 40, 307 | height: 40, 308 | decoration: BoxDecoration( 309 | borderRadius: 310 | BorderRadius.circular(8), 311 | image: DecorationImage( 312 | image: NetworkImage(videoData! 313 | .video.channelThumb), 314 | fit: BoxFit.cover)), 315 | ), 316 | const SizedBox( 317 | width: 10, 318 | ), 319 | SizedBox( 320 | width: (MediaQuery.of(context) 321 | .size 322 | .width - 323 | 180), 324 | child: Column( 325 | crossAxisAlignment: 326 | CrossAxisAlignment.start, 327 | children: [ 328 | Text( 329 | videoData!.video.username, 330 | style: TextStyle( 331 | color: white, 332 | fontFamily: 'Cairo', 333 | fontSize: 14, 334 | fontWeight: FontWeight.w500, 335 | height: 1.3 336 | ), 337 | ), 338 | const SizedBox( 339 | height: 5, 340 | ), 341 | Row( 342 | children: [ 343 | Text( 344 | videoData!.video 345 | .subscribeCount ?? 346 | '', 347 | style: TextStyle( 348 | color: white 349 | .withOpacity(0.4), 350 | fontSize: 13, 351 | fontWeight: 352 | FontWeight.w500, 353 | fontFamily: 'Cairo' 354 | ), 355 | ), 356 | ], 357 | ) 358 | ], 359 | ), 360 | ) 361 | ], 362 | ), 363 | ), 364 | GestureDetector( 365 | onTap: (){ 366 | 367 | }, 368 | child: Text( 369 | "SUBSCRIBE", 370 | style: TextStyle( 371 | color: red, 372 | fontWeight: FontWeight.bold, 373 | fontFamily: 'Cairo' 374 | ), 375 | ), 376 | ) 377 | ], 378 | ), 379 | ), 380 | const SizedBox( 381 | height: 10, 382 | ), 383 | Divider( 384 | color: white.withOpacity(0.1), 385 | ), 386 | Padding( 387 | padding: 388 | const EdgeInsets.only(right: 0, left: 20), 389 | child: Row( 390 | mainAxisAlignment: 391 | MainAxisAlignment.spaceBetween, 392 | children: [ 393 | Text( 394 | "Up next", 395 | style: TextStyle( 396 | fontSize: 14, 397 | color: white.withOpacity(0.4), 398 | fontWeight: FontWeight.w500, 399 | fontFamily: 'Cairo' 400 | ), 401 | ), 402 | Row( 403 | children: [ 404 | Text( 405 | "Autoplay", 406 | style: TextStyle( 407 | fontSize: 14, 408 | color: white.withOpacity(0.4), 409 | fontWeight: FontWeight.w500, 410 | fontFamily: 'Cairo' 411 | ), 412 | ), 413 | Switch( 414 | value: isSwitched, 415 | onChanged: (value) { 416 | setState(() { 417 | isSwitched = value; 418 | }); 419 | }) 420 | ], 421 | ) 422 | ], 423 | ), 424 | ), 425 | const SizedBox( 426 | height: 5, 427 | ), 428 | Padding( 429 | padding: 430 | const EdgeInsets.only(left: 20, right: 20), 431 | child: SingleChildScrollView( 432 | physics: const ScrollPhysics(), 433 | child: Column( 434 | children: [ 435 | ListView.builder( 436 | physics: 437 | const NeverScrollableScrollPhysics(), 438 | shrinkWrap: true, 439 | itemCount: videoData!.videosList!.length, 440 | itemBuilder: (context, index) { 441 | if (videoData!.videosList![index] 442 | .containsKey( 443 | 'compactVideoRenderer')) { 444 | String? image = videoData! 445 | .videosList![index] 446 | ['compactVideoRenderer'] 447 | ['thumbnail']['thumbnails'][0] 448 | ['url']; 449 | String? duration = videoData! 450 | .videosList![index] 451 | ['compactVideoRenderer'] 452 | ?['lengthText']?['simpleText']; 453 | String? title = 454 | videoData!.videosList![index] 455 | ['compactVideoRenderer'] 456 | ?['title']?['simpleText']; 457 | String? channel = videoData! 458 | .videosList![index] 459 | ['compactVideoRenderer'] 460 | ?['shortBylineText']?['runs'] 461 | ?[0]?['text']; 462 | String? views = videoData! 463 | .videosList![index] 464 | ['compactVideoRenderer'] 465 | ?['viewCountText']?['simpleText']; 466 | return GestureDetector( 467 | onTap: () { 468 | String videoId = videoData!.videosList![index]['compactVideoRenderer']['videoId']; 469 | _changeVideo(videoId); 470 | }, 471 | child: Padding( 472 | padding: const EdgeInsets.only( 473 | bottom: 20), 474 | child: Row( 475 | children: [ 476 | Container( 477 | width: 478 | (MediaQuery.of(context) 479 | .size 480 | .width - 481 | 50) / 482 | 2, 483 | height: 100, 484 | decoration: BoxDecoration( 485 | borderRadius: 486 | BorderRadius 487 | .circular(8), 488 | image: DecorationImage( 489 | image: 490 | Image.network( 491 | image!) 492 | .image, 493 | fit: BoxFit.cover)), 494 | child: Stack( 495 | children: [ 496 | Positioned( 497 | bottom: 10, 498 | right: 12, 499 | child: Container( 500 | decoration: BoxDecoration( 501 | color: Colors 502 | .black 503 | .withOpacity( 504 | 0.8), 505 | borderRadius: 506 | BorderRadius 507 | .circular( 508 | 3)), 509 | child: Padding( 510 | padding: 511 | const EdgeInsets 512 | .all(3.0), 513 | child: Text( 514 | duration ?? 515 | "00:00", 516 | style: TextStyle( 517 | fontSize: 518 | 12, 519 | color: white 520 | .withOpacity( 521 | 0.4), 522 | fontFamily: 'Cairo' 523 | ), 524 | ), 525 | ), 526 | ), 527 | ) 528 | ], 529 | ), 530 | ), 531 | const SizedBox( 532 | width: 20, 533 | ), 534 | Expanded( 535 | child: Row( 536 | crossAxisAlignment: 537 | CrossAxisAlignment 538 | .start, 539 | mainAxisAlignment: 540 | MainAxisAlignment 541 | .spaceBetween, 542 | children: [ 543 | SizedBox( 544 | width: (MediaQuery.of( 545 | context) 546 | .size 547 | .width - 548 | 130) / 549 | 2, 550 | child: Column( 551 | crossAxisAlignment: 552 | CrossAxisAlignment 553 | .start, 554 | children: [ 555 | Text( 556 | title ?? unknown, 557 | style: TextStyle( 558 | color: white 559 | .withOpacity( 560 | 0.9), 561 | fontWeight: 562 | FontWeight 563 | .w500, 564 | height: 1.3, 565 | fontSize: 14, 566 | fontFamily: 'Cairo' 567 | ), 568 | maxLines: 3, 569 | overflow: 570 | TextOverflow 571 | .ellipsis, 572 | ), 573 | Text( 574 | channel ?? 575 | unknown, 576 | style: TextStyle( 577 | color: white 578 | .withOpacity( 579 | 0.4), 580 | fontSize: 12, 581 | fontWeight: 582 | FontWeight 583 | .w500, 584 | fontFamily: 'Cairo' 585 | ), 586 | overflow: 587 | TextOverflow 588 | .ellipsis, 589 | ), 590 | Row( 591 | children: < 592 | Widget>[ 593 | Text( 594 | views ?? 595 | unknown, 596 | style: TextStyle( 597 | color: white.withOpacity( 598 | 0.4), 599 | fontSize: 600 | 12, 601 | fontWeight: 602 | FontWeight 603 | .w500, 604 | fontFamily: 'Cairo' 605 | ), 606 | overflow: 607 | TextOverflow 608 | .ellipsis, 609 | ), 610 | ], 611 | ) 612 | ], 613 | ), 614 | ), 615 | Icon( 616 | LineIcons 617 | .horizontalEllipsis, 618 | color: white 619 | .withOpacity(0.4), 620 | ) 621 | ], 622 | )) 623 | ], 624 | ), 625 | ), 626 | ); 627 | } else if(videoData!.videosList![index] 628 | .containsKey( 629 | 'compactPlaylistRenderer')) { 630 | //TODO: Add Playlist Widget 631 | return Container(); 632 | } 633 | return Container(); 634 | }, 635 | ) 636 | ], 637 | ), 638 | ), 639 | ) 640 | ], 641 | ), 642 | )); 643 | } else { 644 | return const Center(child: Text("No data")); 645 | } 646 | } 647 | } 648 | }, 649 | ) 650 | ], 651 | ), 652 | ); 653 | } 654 | void _changeVideo(String videoId) { 655 | _controller.changeVideo(playVideoFrom: PlayVideoFrom.youtube("https://youtu.be/$videoId")); 656 | setState((){ 657 | widget.videoId = videoId; 658 | }); 659 | } 660 | 661 | void subscribe () async { 662 | ///TODO: Subscribe channel from video page 663 | } 664 | 665 | void unSubscribe () async { 666 | ///TODO: Unsubscribe channel from video page 667 | } 668 | } 669 | -------------------------------------------------------------------------------- /lib/theme/colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | var white = Color(0xFFFFFFFF).withOpacity(0.9); 4 | var red = Colors.red; 5 | var grey = Color(0xff9e9e9e); 6 | var pink = Color(0xffffc491); -------------------------------------------------------------------------------- /lib/utilities/categories.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_utube/constants.dart'; 3 | import 'package:flutter_utube/theme/colors.dart'; 4 | 5 | class Categories extends StatefulWidget { 6 | void Function(int) callback; 7 | int trendingIndex; 8 | Categories({Key? key, required this.callback, required this.trendingIndex}) : super(key: key); 9 | 10 | @override 11 | State createState() => _CategoriesState(); 12 | } 13 | 14 | class _CategoriesState extends State { 15 | List categories = ["Trending", "Music", "Gaming", "Movies"]; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return SizedBox( 20 | height: 50, 21 | child: ListView.builder( 22 | scrollDirection: Axis.horizontal, 23 | itemCount: categories.length, 24 | itemBuilder: (context, index) => buildCategory(index), 25 | ), 26 | ); 27 | } 28 | 29 | Widget buildCategory(int index) { 30 | return GestureDetector( 31 | onTap: (){ 32 | widget.trendingIndex = index; 33 | widget.callback(widget.trendingIndex); 34 | }, 35 | child: widget.trendingIndex == index 36 | ? Align( 37 | child: Container( 38 | height: 38, 39 | width: 80, 40 | margin: const EdgeInsets.symmetric(horizontal: 5), 41 | decoration: BoxDecoration( 42 | borderRadius: BorderRadius.circular(20), 43 | color: pink 44 | ), 45 | child: Center( 46 | child: Text( 47 | categories[index], 48 | style: const TextStyle( 49 | color: PrimaryColor, 50 | fontSize: 16, 51 | fontWeight: FontWeight.w700, 52 | fontFamily: 'Cairo' 53 | ), 54 | ), 55 | ), 56 | ), 57 | ) 58 | : Container( 59 | width: 80, 60 | margin: const EdgeInsets.symmetric(horizontal: 5), 61 | child: Center( 62 | child: Text( 63 | categories[index], 64 | style: const TextStyle( 65 | color: Color(0xff9e9e9e), 66 | fontSize: 16, 67 | fontWeight: FontWeight.w700, 68 | fontFamily: 'Cairo' 69 | ), 70 | ), 71 | ), 72 | ), 73 | ); 74 | } 75 | } -------------------------------------------------------------------------------- /lib/utilities/custom_app_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import '/helpers/data_search.dart'; 4 | 5 | class CustomAppBar extends StatefulWidget implements PreferredSizeWidget { 6 | CustomAppBar({Key? key}) : preferredSize = const Size.fromHeight(kToolbarHeight), super(key: key); 7 | 8 | @override 9 | final Size preferredSize; // default is 56.0 10 | 11 | @override 12 | _CustomAppBarState createState() => _CustomAppBarState(); 13 | } 14 | 15 | class _CustomAppBarState extends State{ 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return AppBar( 20 | backgroundColor: Color(0xff2d2d2d), 21 | systemOverlayStyle: const SystemUiOverlayStyle( 22 | statusBarColor: Color(0xff2d2d2d), 23 | statusBarIconBrightness: Brightness.light, // For Android (dark icons) 24 | statusBarBrightness: Brightness.light, // For iOS (dark icons) 25 | ), 26 | elevation: 0.5, 27 | title: Container( 28 | padding: const EdgeInsets.only(left: 10), 29 | child: Image.asset("assets/logo.png", width: 200), 30 | ), 31 | actions: [ 32 | Container( 33 | padding: const EdgeInsets.only(right: 20), 34 | child: IconButton( 35 | onPressed: (){ 36 | showSearch(context: context, delegate: DataSearch()); 37 | }, 38 | icon: Icon( 39 | Icons.manage_search, 40 | size: 37, 41 | color: Colors.white, 42 | ), 43 | ), 44 | ) 45 | ], 46 | ); 47 | } 48 | } -------------------------------------------------------------------------------- /lib/widgets/channel_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '/pages/channel/channel_page.dart'; 3 | 4 | class ChannelWidget extends StatelessWidget { 5 | final String id, thumbnail, title, videoCount; 6 | ChannelWidget({ 7 | required this.id, 8 | required this.thumbnail, 9 | required this.title, 10 | required this.videoCount, 11 | }); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | String imgUrl = thumbnail; 16 | if (!imgUrl.startsWith("https")) { 17 | imgUrl = "https://" + imgUrl.substring(2); 18 | } 19 | return InkWell( 20 | onTap: (){ 21 | Navigator.push( 22 | context, 23 | MaterialPageRoute(builder: (context) => ChannelPage(id: id, title: title)), 24 | ); 25 | }, 26 | child: Container( 27 | padding: const EdgeInsets.only(bottom: 30, left: 30, top: 10), 28 | child: Row( 29 | children: [ 30 | Padding( 31 | padding: const EdgeInsets.only(left: 20, right: 20), 32 | child: InkWell( 33 | onTap: () {}, 34 | child: Container( 35 | height: 70, 36 | width: 70, 37 | decoration: BoxDecoration( 38 | shape: BoxShape.circle, 39 | image: DecorationImage( 40 | image: Image.network(imgUrl).image, fit: BoxFit.cover)), 41 | ), 42 | ), 43 | ), 44 | Expanded( 45 | child: Column( 46 | children: [ 47 | Text( 48 | title, 49 | style: const TextStyle( 50 | color: Colors.white, 51 | fontSize: 14, 52 | fontFamily: 'Cairo' 53 | ), 54 | ), 55 | SizedBox(height: 15), 56 | Text( 57 | videoCount + 58 | "فيديو" + 59 | "•", 60 | style: const TextStyle( 61 | color: Colors.white38, 62 | fontSize: 12, 63 | fontFamily: 'Cairo' 64 | ), 65 | ) 66 | ], 67 | ), 68 | ) 69 | ], 70 | ), 71 | ), 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/widgets/loading.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | Widget loading() { 4 | return const Center( 5 | child: CircularProgressIndicator(), 6 | ); 7 | } -------------------------------------------------------------------------------- /lib/widgets/playList_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../pages/playlist_page.dart'; 3 | 4 | class PlayListWidget extends StatelessWidget { 5 | final thumbnails; 6 | final String id, videoCount, title, channelName; 7 | 8 | PlayListWidget({ 9 | required this.id, 10 | required this.thumbnails, 11 | required this.videoCount, 12 | required this.title, 13 | required this.channelName, 14 | }); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return InkWell( 19 | onTap: () { 20 | _onTap(context); 21 | }, 22 | child: Padding( 23 | padding: const EdgeInsets.only(bottom: 30), 24 | child: Row( 25 | children: [ 26 | Padding( 27 | padding: const EdgeInsets.only(left: 20, right: 20), 28 | child: InkWell( 29 | onTap: () { 30 | _onTap(context); 31 | }, 32 | child: Stack( 33 | children: [ 34 | Container( 35 | height: 80, 36 | width: 140, 37 | decoration: BoxDecoration( 38 | borderRadius: BorderRadius.circular(8), 39 | image: DecorationImage( 40 | image: Image.network( 41 | thumbnails[thumbnails.length - 1]['url']) 42 | .image, 43 | fit: BoxFit.cover)), 44 | ), 45 | Positioned( 46 | bottom: 0.0, 47 | right: 0.0, 48 | child: Container( 49 | height: 80, 50 | width: 60, 51 | color: Colors.black54, 52 | child: Center( 53 | child: Padding( 54 | padding: const EdgeInsets.only(top: 20.0), 55 | child: Column( 56 | children: [ 57 | const Icon( 58 | Icons.menu_open, 59 | color: Colors.white, 60 | ), 61 | const SizedBox(height: 2), 62 | Text( 63 | videoCount, 64 | style: const TextStyle( 65 | color: Colors.white, 66 | fontSize: 13, 67 | fontFamily: 'Cairo'), 68 | ) 69 | ], 70 | ), 71 | ), 72 | ), 73 | ), 74 | ) 75 | ], 76 | ), 77 | ), 78 | ), 79 | Expanded( 80 | child: Column( 81 | children: [ 82 | Text( 83 | title, 84 | style: const TextStyle( 85 | color: Colors.white, 86 | fontSize: 14, 87 | fontFamily: 'Cairo' 88 | ), 89 | ), 90 | Text( 91 | channelName, 92 | textAlign: TextAlign.start, 93 | style: const TextStyle( 94 | color: Colors.white38, 95 | fontSize: 12, 96 | fontFamily: 'Cairo' 97 | ), 98 | ), 99 | ], 100 | ), 101 | ) 102 | ], 103 | ), 104 | ), 105 | ); 106 | } 107 | 108 | void _onTap(BuildContext context) { 109 | Navigator.push( 110 | context, 111 | MaterialPageRoute( 112 | builder: (context) => PlayListPage( 113 | title: title, 114 | id: id, 115 | )), 116 | ); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /lib/widgets/video_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '/api/youtube_api.dart'; 3 | import '/pages/video_detail_page.dart'; 4 | 5 | 6 | class VideoWidget extends StatelessWidget { 7 | final String videoId, duration, title, channelName, views; 8 | 9 | VideoWidget({ 10 | required this.videoId, 11 | required this.duration, 12 | required this.title, 13 | required this.channelName, 14 | required this.views, 15 | }); 16 | 17 | YoutubeApi youtubeApi = YoutubeApi(); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return InkWell( 22 | onTap: () async { 23 | navigateToPlayer(context); 24 | }, 25 | child: Padding( 26 | padding: const EdgeInsets.only(bottom: 20, top: 10), 27 | child: Row( 28 | children: [ 29 | Padding( 30 | padding: const EdgeInsets.only(left: 20, right: 10), 31 | child: InkWell( 32 | onTap: () { 33 | navigateToPlayer(context); 34 | }, 35 | child: Stack( 36 | children: [ 37 | Container( 38 | height: 80, 39 | width: 140, 40 | decoration: BoxDecoration( 41 | borderRadius: BorderRadius.circular(8), 42 | image: DecorationImage( 43 | image: Image.network( 44 | "https://i.ytimg.com/vi/$videoId/hqdefault.jpg") 45 | .image, 46 | fit: BoxFit.cover)), 47 | ), 48 | Positioned( 49 | bottom: 4.0, 50 | right: 4.0, 51 | child: Container( 52 | padding: const EdgeInsets.only( 53 | top: 1, bottom: 1, left: 4, right: 4), 54 | color: (duration == "Live") 55 | ? Colors.red.withOpacity(0.88) 56 | : Colors.black54, 57 | child: Text(duration, 58 | style: 59 | const TextStyle( 60 | color: Colors.white, 61 | fontSize: 11, 62 | fontFamily: 'Cairo' 63 | ), 64 | ), 65 | ), 66 | ), 67 | ], 68 | ), 69 | ), 70 | ), 71 | Expanded( 72 | child: Padding( 73 | padding: const EdgeInsets.only(right: 20, left: 15), 74 | child: Column( 75 | children: [ 76 | Text( 77 | title, 78 | textAlign: TextAlign.right, 79 | style: const TextStyle( 80 | color: Colors.white, 81 | fontSize: 14, 82 | fontFamily: 'Cairo' 83 | ), 84 | maxLines: 2, 85 | overflow: TextOverflow.ellipsis, 86 | ), //video title 87 | Text( 88 | channelName, 89 | textAlign: TextAlign.right, 90 | style: const TextStyle( 91 | color: Colors.white38, 92 | fontSize: 11, 93 | fontFamily: 'Cairo' 94 | ), 95 | ), //video channel 96 | const SizedBox( 97 | height: 5, 98 | ), 99 | Text( 100 | views, 101 | style: const TextStyle(color: Colors.white38, 102 | fontSize: 12, 103 | fontFamily: 'Cairo' 104 | ), 105 | ) 106 | ], 107 | ), 108 | ), 109 | ) 110 | ], 111 | ), 112 | ), 113 | ); 114 | } 115 | navigateToPlayer(BuildContext context){ 116 | Navigator.push( 117 | context, 118 | MaterialPageRoute( 119 | builder: (_) => VideoDetailPage( 120 | videoId: videoId))); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /names.txt: -------------------------------------------------------------------------------- 1 | 1- محمد محمود محمد عوض دويدار 2 | 2- محمد محمود محمد حسن 3 | 3- محمد سامي عبدالرحمن شرف -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | archive: 5 | dependency: transitive 6 | description: 7 | name: archive 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "3.3.0" 11 | async: 12 | dependency: transitive 13 | description: 14 | name: async 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.8.2" 18 | boolean_selector: 19 | dependency: transitive 20 | description: 21 | name: boolean_selector 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.1.0" 25 | characters: 26 | dependency: transitive 27 | description: 28 | name: characters 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.2.0" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.3.1" 39 | clock: 40 | dependency: transitive 41 | description: 42 | name: clock 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.1.0" 46 | collection: 47 | dependency: transitive 48 | description: 49 | name: collection 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.16.0" 53 | crypto: 54 | dependency: transitive 55 | description: 56 | name: crypto 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "3.0.2" 60 | csslib: 61 | dependency: transitive 62 | description: 63 | name: csslib 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "0.17.2" 67 | cupertino_icons: 68 | dependency: "direct main" 69 | description: 70 | name: cupertino_icons 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "1.0.5" 74 | fake_async: 75 | dependency: transitive 76 | description: 77 | name: fake_async 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "1.3.0" 81 | ffi: 82 | dependency: transitive 83 | description: 84 | name: ffi 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "2.0.1" 88 | file: 89 | dependency: transitive 90 | description: 91 | name: file 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "6.1.2" 95 | flutter: 96 | dependency: "direct main" 97 | description: flutter 98 | source: sdk 99 | version: "0.0.0" 100 | flutter_lints: 101 | dependency: "direct dev" 102 | description: 103 | name: flutter_lints 104 | url: "https://pub.dartlang.org" 105 | source: hosted 106 | version: "2.0.1" 107 | flutter_test: 108 | dependency: "direct dev" 109 | description: flutter 110 | source: sdk 111 | version: "0.0.0" 112 | flutter_web_plugins: 113 | dependency: transitive 114 | description: flutter 115 | source: sdk 116 | version: "0.0.0" 117 | freezed_annotation: 118 | dependency: transitive 119 | description: 120 | name: freezed_annotation 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "1.1.0" 124 | get: 125 | dependency: transitive 126 | description: 127 | name: get 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "4.6.5" 131 | html: 132 | dependency: "direct main" 133 | description: 134 | name: html 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "0.15.0" 138 | http: 139 | dependency: "direct main" 140 | description: 141 | name: http 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "0.13.4" 145 | http_parser: 146 | dependency: transitive 147 | description: 148 | name: http_parser 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "4.0.1" 152 | js: 153 | dependency: transitive 154 | description: 155 | name: js 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "0.6.4" 159 | json_annotation: 160 | dependency: transitive 161 | description: 162 | name: json_annotation 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "4.5.0" 166 | lazy_load_scrollview: 167 | dependency: "direct main" 168 | description: 169 | name: lazy_load_scrollview 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "1.3.0" 173 | line_icons: 174 | dependency: "direct main" 175 | description: 176 | name: line_icons 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "2.0.1" 180 | lints: 181 | dependency: transitive 182 | description: 183 | name: lints 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "2.0.0" 187 | lottie: 188 | dependency: transitive 189 | description: 190 | name: lottie 191 | url: "https://pub.dartlang.org" 192 | source: hosted 193 | version: "1.3.0" 194 | matcher: 195 | dependency: transitive 196 | description: 197 | name: matcher 198 | url: "https://pub.dartlang.org" 199 | source: hosted 200 | version: "0.12.11" 201 | material_color_utilities: 202 | dependency: transitive 203 | description: 204 | name: material_color_utilities 205 | url: "https://pub.dartlang.org" 206 | source: hosted 207 | version: "0.1.4" 208 | meta: 209 | dependency: transitive 210 | description: 211 | name: meta 212 | url: "https://pub.dartlang.org" 213 | source: hosted 214 | version: "1.7.0" 215 | path: 216 | dependency: transitive 217 | description: 218 | name: path 219 | url: "https://pub.dartlang.org" 220 | source: hosted 221 | version: "1.8.1" 222 | path_provider_linux: 223 | dependency: transitive 224 | description: 225 | name: path_provider_linux 226 | url: "https://pub.dartlang.org" 227 | source: hosted 228 | version: "2.1.7" 229 | path_provider_platform_interface: 230 | dependency: transitive 231 | description: 232 | name: path_provider_platform_interface 233 | url: "https://pub.dartlang.org" 234 | source: hosted 235 | version: "2.0.4" 236 | path_provider_windows: 237 | dependency: transitive 238 | description: 239 | name: path_provider_windows 240 | url: "https://pub.dartlang.org" 241 | source: hosted 242 | version: "2.1.0" 243 | petitparser: 244 | dependency: transitive 245 | description: 246 | name: petitparser 247 | url: "https://pub.dartlang.org" 248 | source: hosted 249 | version: "5.0.0" 250 | platform: 251 | dependency: transitive 252 | description: 253 | name: platform 254 | url: "https://pub.dartlang.org" 255 | source: hosted 256 | version: "3.1.0" 257 | plugin_platform_interface: 258 | dependency: transitive 259 | description: 260 | name: plugin_platform_interface 261 | url: "https://pub.dartlang.org" 262 | source: hosted 263 | version: "2.1.2" 264 | pod_player: 265 | dependency: "direct main" 266 | description: 267 | name: pod_player 268 | url: "https://pub.dartlang.org" 269 | source: hosted 270 | version: "0.0.6" 271 | process: 272 | dependency: transitive 273 | description: 274 | name: process 275 | url: "https://pub.dartlang.org" 276 | source: hosted 277 | version: "4.2.4" 278 | shared_preferences: 279 | dependency: "direct main" 280 | description: 281 | name: shared_preferences 282 | url: "https://pub.dartlang.org" 283 | source: hosted 284 | version: "2.0.15" 285 | shared_preferences_android: 286 | dependency: transitive 287 | description: 288 | name: shared_preferences_android 289 | url: "https://pub.dartlang.org" 290 | source: hosted 291 | version: "2.0.12" 292 | shared_preferences_ios: 293 | dependency: transitive 294 | description: 295 | name: shared_preferences_ios 296 | url: "https://pub.dartlang.org" 297 | source: hosted 298 | version: "2.1.1" 299 | shared_preferences_linux: 300 | dependency: transitive 301 | description: 302 | name: shared_preferences_linux 303 | url: "https://pub.dartlang.org" 304 | source: hosted 305 | version: "2.1.1" 306 | shared_preferences_macos: 307 | dependency: transitive 308 | description: 309 | name: shared_preferences_macos 310 | url: "https://pub.dartlang.org" 311 | source: hosted 312 | version: "2.0.4" 313 | shared_preferences_platform_interface: 314 | dependency: transitive 315 | description: 316 | name: shared_preferences_platform_interface 317 | url: "https://pub.dartlang.org" 318 | source: hosted 319 | version: "2.0.0" 320 | shared_preferences_web: 321 | dependency: transitive 322 | description: 323 | name: shared_preferences_web 324 | url: "https://pub.dartlang.org" 325 | source: hosted 326 | version: "2.0.4" 327 | shared_preferences_windows: 328 | dependency: transitive 329 | description: 330 | name: shared_preferences_windows 331 | url: "https://pub.dartlang.org" 332 | source: hosted 333 | version: "2.1.1" 334 | sky_engine: 335 | dependency: transitive 336 | description: flutter 337 | source: sdk 338 | version: "0.0.99" 339 | source_span: 340 | dependency: transitive 341 | description: 342 | name: source_span 343 | url: "https://pub.dartlang.org" 344 | source: hosted 345 | version: "1.8.2" 346 | stack_trace: 347 | dependency: transitive 348 | description: 349 | name: stack_trace 350 | url: "https://pub.dartlang.org" 351 | source: hosted 352 | version: "1.10.0" 353 | stream_channel: 354 | dependency: transitive 355 | description: 356 | name: stream_channel 357 | url: "https://pub.dartlang.org" 358 | source: hosted 359 | version: "2.1.0" 360 | string_scanner: 361 | dependency: transitive 362 | description: 363 | name: string_scanner 364 | url: "https://pub.dartlang.org" 365 | source: hosted 366 | version: "1.1.0" 367 | term_glyph: 368 | dependency: transitive 369 | description: 370 | name: term_glyph 371 | url: "https://pub.dartlang.org" 372 | source: hosted 373 | version: "1.2.0" 374 | test_api: 375 | dependency: transitive 376 | description: 377 | name: test_api 378 | url: "https://pub.dartlang.org" 379 | source: hosted 380 | version: "0.4.9" 381 | typed_data: 382 | dependency: transitive 383 | description: 384 | name: typed_data 385 | url: "https://pub.dartlang.org" 386 | source: hosted 387 | version: "1.3.1" 388 | universal_html: 389 | dependency: transitive 390 | description: 391 | name: universal_html 392 | url: "https://pub.dartlang.org" 393 | source: hosted 394 | version: "2.0.8" 395 | universal_io: 396 | dependency: transitive 397 | description: 398 | name: universal_io 399 | url: "https://pub.dartlang.org" 400 | source: hosted 401 | version: "2.0.4" 402 | vector_math: 403 | dependency: transitive 404 | description: 405 | name: vector_math 406 | url: "https://pub.dartlang.org" 407 | source: hosted 408 | version: "2.1.2" 409 | video_player: 410 | dependency: transitive 411 | description: 412 | name: video_player 413 | url: "https://pub.dartlang.org" 414 | source: hosted 415 | version: "2.4.5" 416 | video_player_android: 417 | dependency: transitive 418 | description: 419 | name: video_player_android 420 | url: "https://pub.dartlang.org" 421 | source: hosted 422 | version: "2.3.6" 423 | video_player_avfoundation: 424 | dependency: transitive 425 | description: 426 | name: video_player_avfoundation 427 | url: "https://pub.dartlang.org" 428 | source: hosted 429 | version: "2.3.5" 430 | video_player_platform_interface: 431 | dependency: transitive 432 | description: 433 | name: video_player_platform_interface 434 | url: "https://pub.dartlang.org" 435 | source: hosted 436 | version: "5.1.3" 437 | video_player_web: 438 | dependency: transitive 439 | description: 440 | name: video_player_web 441 | url: "https://pub.dartlang.org" 442 | source: hosted 443 | version: "2.0.10" 444 | wakelock: 445 | dependency: transitive 446 | description: 447 | name: wakelock 448 | url: "https://pub.dartlang.org" 449 | source: hosted 450 | version: "0.6.1+2" 451 | wakelock_macos: 452 | dependency: transitive 453 | description: 454 | name: wakelock_macos 455 | url: "https://pub.dartlang.org" 456 | source: hosted 457 | version: "0.4.0" 458 | wakelock_platform_interface: 459 | dependency: transitive 460 | description: 461 | name: wakelock_platform_interface 462 | url: "https://pub.dartlang.org" 463 | source: hosted 464 | version: "0.3.0" 465 | wakelock_web: 466 | dependency: transitive 467 | description: 468 | name: wakelock_web 469 | url: "https://pub.dartlang.org" 470 | source: hosted 471 | version: "0.4.0" 472 | wakelock_windows: 473 | dependency: transitive 474 | description: 475 | name: wakelock_windows 476 | url: "https://pub.dartlang.org" 477 | source: hosted 478 | version: "0.2.0" 479 | win32: 480 | dependency: transitive 481 | description: 482 | name: win32 483 | url: "https://pub.dartlang.org" 484 | source: hosted 485 | version: "2.7.0" 486 | xdg_directories: 487 | dependency: transitive 488 | description: 489 | name: xdg_directories 490 | url: "https://pub.dartlang.org" 491 | source: hosted 492 | version: "0.2.0+1" 493 | xml: 494 | dependency: transitive 495 | description: 496 | name: xml 497 | url: "https://pub.dartlang.org" 498 | source: hosted 499 | version: "5.4.1" 500 | xml2json: 501 | dependency: "direct main" 502 | description: 503 | name: xml2json 504 | url: "https://pub.dartlang.org" 505 | source: hosted 506 | version: "5.3.2" 507 | youtube_explode_dart: 508 | dependency: transitive 509 | description: 510 | name: youtube_explode_dart 511 | url: "https://pub.dartlang.org" 512 | source: hosted 513 | version: "1.11.0" 514 | sdks: 515 | dart: ">=2.17.1 <3.0.0" 516 | flutter: ">=3.0.0" 517 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_utube 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.17.1 <3.0.0" 22 | 23 | # Dependencies specify other packages that your package needs in order to work. 24 | # To automatically upgrade your package dependencies to the latest versions 25 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 26 | # dependencies can be manually updated by changing the version numbers below to 27 | # the latest version available on pub.dev. To see which dependencies have newer 28 | # versions available, run `flutter pub outdated`. 29 | dependencies: 30 | flutter: 31 | sdk: flutter 32 | 33 | 34 | # The following adds the Cupertino Icons font to your application. 35 | # Use with the CupertinoIcons class for iOS style icons. 36 | cupertino_icons: ^1.0.2 37 | line_icons: ^2.0.1 38 | http: ^0.13.4 39 | html: ^0.15.0 40 | lazy_load_scrollview: ^1.3.0 41 | xml2json: ^5.3.2 42 | shared_preferences: ^2.0.15 43 | pod_player: ^0.0.6 44 | 45 | dev_dependencies: 46 | flutter_test: 47 | sdk: flutter 48 | 49 | # The "flutter_lints" package below contains a set of recommended lints to 50 | # encourage good coding practices. The lint set provided by the package is 51 | # activated in the `analysis_options.yaml` file located at the root of your 52 | # package. See that file for information about deactivating specific lint 53 | # rules and activating additional ones. 54 | flutter_lints: ^2.0.0 55 | 56 | # For information on the generic Dart part of this file, see the 57 | # following page: https://dart.dev/tools/pub/pubspec 58 | 59 | # The following section is specific to Flutter packages. 60 | flutter: 61 | 62 | # The following line ensures that the Material Icons font is 63 | # included with your application, so that you can use the icons in 64 | # the material Icons class. 65 | uses-material-design: true 66 | 67 | # To add assets to your application, add an assets section, like this: 68 | assets: 69 | - assets/ 70 | 71 | fonts: 72 | - family: Cairo 73 | fonts: 74 | - asset: fonts/Cairo-Regular.ttf 75 | 76 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility in the flutter_test package. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:flutter_utube/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(const MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | --------------------------------------------------------------------------------