diff --git a/SportsFirst/.gitignore b/SportsFirst/.gitignore new file mode 100644 index 00000000..0fa6b675 --- /dev/null +++ b/SportsFirst/.gitignore @@ -0,0 +1,46 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/SportsFirst/.metadata b/SportsFirst/.metadata new file mode 100644 index 00000000..05f4d5d4 --- /dev/null +++ b/SportsFirst/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 097d3313d8e2c7f901932d63e537c1acefb87800 + channel: stable + +project_type: app diff --git a/SportsFirst/README.md b/SportsFirst/README.md new file mode 100644 index 00000000..a5039aec --- /dev/null +++ b/SportsFirst/README.md @@ -0,0 +1,29 @@ +# WatchParty + +Team Name - SportsFirst + +Track - BlockChain and Web 3.0 + +Brief Description - + +"Interactive Watch Party App for Cricket with Celebrity Host Featureā€ [with NFT and Tokenization]" +Cricket is not just a sport but a celebration globally, especially in India. With millions of fans watching live matches and events both in physical and digital setting Cricket is one of the most followed sport globally. The development of an interactive watch party app for Cricket can transform the way how people people watch and engage with the Sport. With Co-Watch and NFT Tokenization involved this app can be a game changer for attracting GenZ and millennial crowds. The app will also include a celebrity host feature to provide fans with an opportunity to interact with their favourite personalities. + +Objective: This App is designed for the sole objective of increasing the adoption of tech and providing the audience a unique way of watching and engaging with the sport. With features such as interactive audio and video channels, in addition to NFT features, community leader boards, in-app games this app aims to be a one-stop shop for viewer entertainment. + +Scope: The proposed watch party app will be designed to offer a social and interactive platform for cricket fans to connect and engage with each other while watching live matches. The app will include interactive audio and video channels, chat rooms for fans to connect and interact, as well as the celebrity host feature. Additionally, the app will provide a platform for fans to participate in various games and challenges related to the cricket matches they are watching. + +Smart contract link :- https://polygonscan.com/address/0x2953399124F0cBB46d2CbACD8A89cF0599974963#code + +ScreensShots :- + +![welecome_screen](https://user-images.githubusercontent.com/126079011/222400490-ada23b69-9071-4b00-9ddc-fbe7baf13b91.jpg) + +![home_screen](https://user-images.githubusercontent.com/126079011/222400572-5218864c-ae40-4480-9b8b-9696dabee1cf.jpg) + +![join_screen](https://user-images.githubusercontent.com/126079011/222400612-249b3184-1e94-42c0-98c2-56f0047fb255.jpg) + +![watch_party_screen](https://user-images.githubusercontent.com/126079011/222400627-ce288ca8-06ca-4409-80d8-423ee4fa3d1f.jpg) + + + diff --git a/SportsFirst/analysis_options.yaml b/SportsFirst/analysis_options.yaml new file mode 100644 index 00000000..61b6c4de --- /dev/null +++ b/SportsFirst/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/SportsFirst/android/.gitignore b/SportsFirst/android/.gitignore new file mode 100644 index 00000000..6f568019 --- /dev/null +++ b/SportsFirst/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/SportsFirst/android/app/build.gradle b/SportsFirst/android/app/build.gradle new file mode 100644 index 00000000..f2c6bd8b --- /dev/null +++ b/SportsFirst/android/app/build.gradle @@ -0,0 +1,94 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file('key.properties') +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} + +apply plugin: 'com.android.application' +apply plugin: 'com.google.gms.google-services' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + + +android { + + compileSdkVersion 33 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.app.agora_video" + minSdkVersion 21 + targetSdkVersion 30 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + signingConfigs { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null + storePassword keystoreProperties['storePassword'] + } + } + + buildTypes { + release { + signingConfig signingConfigs.release + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation platform('com.google.firebase:firebase-bom:29.3.1') + implementation 'com.google.firebase:firebase-analytics-ktx' + // required for all Android apps + implementation 'io.branch.sdk.android:library:5.+' + // required if your app is in the Google Play Store (tip: avoid using bundled play services libs) + implementation 'com.google.android.gms:play-services-ads-identifier:17.1.0+' + // optional + // Chrome Tab matching (enables 100% guaranteed matching based on cookies) + implementation 'androidx.browser:browser:1.0.0' + implementation 'com.google.android.material:material:1.6.1' +} diff --git a/SportsFirst/android/app/google-services.json b/SportsFirst/android/app/google-services.json new file mode 100644 index 00000000..f919150a --- /dev/null +++ b/SportsFirst/android/app/google-services.json @@ -0,0 +1,47 @@ +{ + "project_info": { + "project_number": "267152864189", + "project_id": "agora-video-705dc", + "storage_bucket": "agora-video-705dc.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:267152864189:android:a7f8616a7a93bec9b418d4", + "android_client_info": { + "package_name": "com.app.agora_video" + } + }, + "oauth_client": [ + { + "client_id": "267152864189-tgldlemlt92pp003i5td6eoglffqus75.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.app.agora_video", + "certificate_hash": "1ad8f2d523e292c54b4a3ccb54385e0dc894fc72" + } + }, + { + "client_id": "267152864189-se7r9108nq03cl2p4tptl1utjms7h8ht.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyDN_unttzrUJCyeXBT71Mdj5yrC5q5pzOk" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "267152864189-se7r9108nq03cl2p4tptl1utjms7h8ht.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/SportsFirst/android/app/src/debug/AndroidManifest.xml b/SportsFirst/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..eaeec695 --- /dev/null +++ b/SportsFirst/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/SportsFirst/android/app/src/main/AndroidManifest.xml b/SportsFirst/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..2a2a5d58 --- /dev/null +++ b/SportsFirst/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SportsFirst/android/app/src/main/kotlin/com/app/agora_video/agora_video/MainActivity.kt b/SportsFirst/android/app/src/main/kotlin/com/app/agora_video/agora_video/MainActivity.kt new file mode 100644 index 00000000..061efd62 --- /dev/null +++ b/SportsFirst/android/app/src/main/kotlin/com/app/agora_video/agora_video/MainActivity.kt @@ -0,0 +1,6 @@ +package com.app.agora_video.agora_video + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/SportsFirst/android/app/src/main/kotlin/com/example/agora_video/MainActivity.kt b/SportsFirst/android/app/src/main/kotlin/com/example/agora_video/MainActivity.kt new file mode 100644 index 00000000..f89ced35 --- /dev/null +++ b/SportsFirst/android/app/src/main/kotlin/com/example/agora_video/MainActivity.kt @@ -0,0 +1,6 @@ +package com.app.agora_video + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/SportsFirst/android/app/src/main/res/drawable-v21/launch_background.xml b/SportsFirst/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..f74085f3 --- /dev/null +++ b/SportsFirst/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/SportsFirst/android/app/src/main/res/drawable/launch_background.xml b/SportsFirst/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000..304732f8 --- /dev/null +++ b/SportsFirst/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/SportsFirst/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/SportsFirst/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..db77bb4b Binary files /dev/null and b/SportsFirst/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/SportsFirst/android/app/src/main/res/mipmap-hdpi/launcher_icon.png b/SportsFirst/android/app/src/main/res/mipmap-hdpi/launcher_icon.png new file mode 100644 index 00000000..4a50e4f5 Binary files /dev/null and b/SportsFirst/android/app/src/main/res/mipmap-hdpi/launcher_icon.png differ diff --git a/SportsFirst/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/SportsFirst/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..17987b79 Binary files /dev/null and b/SportsFirst/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/SportsFirst/android/app/src/main/res/mipmap-mdpi/launcher_icon.png b/SportsFirst/android/app/src/main/res/mipmap-mdpi/launcher_icon.png new file mode 100644 index 00000000..74a860c7 Binary files /dev/null and b/SportsFirst/android/app/src/main/res/mipmap-mdpi/launcher_icon.png differ diff --git a/SportsFirst/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/SportsFirst/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..09d43914 Binary files /dev/null and b/SportsFirst/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/SportsFirst/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png b/SportsFirst/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png new file mode 100644 index 00000000..55ab6b42 Binary files /dev/null and b/SportsFirst/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png differ diff --git a/SportsFirst/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/SportsFirst/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..d5f1c8d3 Binary files /dev/null and b/SportsFirst/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/SportsFirst/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png b/SportsFirst/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png new file mode 100644 index 00000000..b09005a2 Binary files /dev/null and b/SportsFirst/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png differ diff --git a/SportsFirst/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/SportsFirst/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..4d6372ee Binary files /dev/null and b/SportsFirst/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/SportsFirst/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png b/SportsFirst/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png new file mode 100644 index 00000000..9254b4d9 Binary files /dev/null and b/SportsFirst/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png differ diff --git a/SportsFirst/android/app/src/main/res/values-night/styles.xml b/SportsFirst/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000..e0fc71c1 --- /dev/null +++ b/SportsFirst/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/SportsFirst/android/app/src/main/res/values/styles.xml b/SportsFirst/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..46247bf2 --- /dev/null +++ b/SportsFirst/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/SportsFirst/android/app/src/profile/AndroidManifest.xml b/SportsFirst/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000..2a89f256 --- /dev/null +++ b/SportsFirst/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/SportsFirst/android/build.gradle b/SportsFirst/android/build.gradle new file mode 100644 index 00000000..89415efb --- /dev/null +++ b/SportsFirst/android/build.gradle @@ -0,0 +1,33 @@ +buildscript { + ext.kotlin_version = '1.6.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'com.google.gms:google-services:4.3.10' + } +} + +allprojects { + repositories { + google() + mavenCentral() + maven { url 'https://jitpack.io' } + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/SportsFirst/android/gradle.properties b/SportsFirst/android/gradle.properties new file mode 100644 index 00000000..94adc3a3 --- /dev/null +++ b/SportsFirst/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/SportsFirst/android/gradle/wrapper/gradle-wrapper.properties b/SportsFirst/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..bc6a58af --- /dev/null +++ b/SportsFirst/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip diff --git a/SportsFirst/android/settings.gradle b/SportsFirst/android/settings.gradle new file mode 100644 index 00000000..44e62bcf --- /dev/null +++ b/SportsFirst/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/SportsFirst/assets/mfvideo.png b/SportsFirst/assets/mfvideo.png new file mode 100644 index 00000000..567d6834 Binary files /dev/null and b/SportsFirst/assets/mfvideo.png differ diff --git a/SportsFirst/assets/token.json b/SportsFirst/assets/token.json new file mode 100644 index 00000000..ac5d9b4b --- /dev/null +++ b/SportsFirst/assets/token.json @@ -0,0 +1,478 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + } + ], + "name": "TransferBatch", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "TransferSingle", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "value", + "type": "string" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "URI", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "accounts", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + } + ], + "name": "balanceOfBatch", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "exists", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "mintBatch", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeBatchTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "newuri", + "type": "string" + } + ], + "name": "setURI", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "uri", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/SportsFirst/ios/.gitignore b/SportsFirst/ios/.gitignore new file mode 100644 index 00000000..7a7f9873 --- /dev/null +++ b/SportsFirst/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/SportsFirst/ios/Flutter/AppFrameworkInfo.plist b/SportsFirst/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000..8d4492f9 --- /dev/null +++ b/SportsFirst/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 9.0 + + diff --git a/SportsFirst/ios/Flutter/Debug.xcconfig b/SportsFirst/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000..592ceee8 --- /dev/null +++ b/SportsFirst/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/SportsFirst/ios/Flutter/Release.xcconfig b/SportsFirst/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000..592ceee8 --- /dev/null +++ b/SportsFirst/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/SportsFirst/ios/Runner.xcodeproj/project.pbxproj b/SportsFirst/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..4fe629c4 --- /dev/null +++ b/SportsFirst/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,481 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.agoraVideo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.agoraVideo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.agoraVideo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} \ No newline at end of file diff --git a/SportsFirst/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/SportsFirst/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/SportsFirst/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/SportsFirst/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SportsFirst/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/SportsFirst/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/SportsFirst/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/SportsFirst/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/SportsFirst/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/SportsFirst/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/SportsFirst/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..c87d15a3 --- /dev/null +++ b/SportsFirst/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SportsFirst/ios/Runner.xcworkspace/contents.xcworkspacedata b/SportsFirst/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..1d526a16 --- /dev/null +++ b/SportsFirst/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/SportsFirst/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SportsFirst/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/SportsFirst/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/SportsFirst/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/SportsFirst/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/SportsFirst/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/SportsFirst/ios/Runner/AppDelegate.swift b/SportsFirst/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000..70693e4a --- /dev/null +++ b/SportsFirst/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d36b1fab --- /dev/null +++ b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 00000000..f7d3ace1 Binary files /dev/null and b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 00000000..9bc09985 Binary files /dev/null and b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 00000000..307d3437 Binary files /dev/null and b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 00000000..106b2884 Binary files /dev/null and b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 00000000..a8cd0910 Binary files /dev/null and b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 00000000..fdf248e0 Binary files /dev/null and b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 00000000..c6417dbc Binary files /dev/null and b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 00000000..307d3437 Binary files /dev/null and b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 00000000..a683a27d Binary files /dev/null and b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 00000000..c11cf57c Binary files /dev/null and b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 00000000..c11cf57c Binary files /dev/null and b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 00000000..fc3b76ff Binary files /dev/null and b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 00000000..bc4f2912 Binary files /dev/null and b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 00000000..536d95a2 Binary files /dev/null and b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 00000000..d3010949 Binary files /dev/null and b/SportsFirst/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/SportsFirst/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/SportsFirst/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 00000000..0bedcf2f --- /dev/null +++ b/SportsFirst/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/SportsFirst/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/SportsFirst/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/SportsFirst/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/SportsFirst/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/SportsFirst/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/SportsFirst/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/SportsFirst/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/SportsFirst/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/SportsFirst/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/SportsFirst/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/SportsFirst/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000..89c2725b --- /dev/null +++ b/SportsFirst/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/SportsFirst/ios/Runner/Base.lproj/LaunchScreen.storyboard b/SportsFirst/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..f2e259c7 --- /dev/null +++ b/SportsFirst/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SportsFirst/ios/Runner/Base.lproj/Main.storyboard b/SportsFirst/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f3c28516 --- /dev/null +++ b/SportsFirst/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SportsFirst/ios/Runner/Info.plist b/SportsFirst/ios/Runner/Info.plist new file mode 100644 index 00000000..5d58ca5b --- /dev/null +++ b/SportsFirst/ios/Runner/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Agora Video + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + agora_video + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/SportsFirst/ios/Runner/Runner-Bridging-Header.h b/SportsFirst/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..308a2a56 --- /dev/null +++ b/SportsFirst/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/SportsFirst/lib/bloc/observer.dart b/SportsFirst/lib/bloc/observer.dart new file mode 100644 index 00000000..ef02611d --- /dev/null +++ b/SportsFirst/lib/bloc/observer.dart @@ -0,0 +1,54 @@ +import 'package:bloc/bloc.dart'; +import 'package:flutter/foundation.dart'; + +class AppObserver extends BlocObserver { + @override + void onEvent(Bloc bloc, Object? event) { + super.onEvent(bloc, event); + if (kDebugMode) { + print('OnEvent Fired--> Started'); + print(bloc.state); + print('OnEvent Fired--> Ended'); + } + } + + @override + void onChange(BlocBase bloc, Change change) { + super.onChange(bloc, change); + if (kDebugMode) { + print('OnChange Fired--> Started'); + print(change); + print('OnChange Fired--> Ended'); + } + } + + @override + void onCreate(BlocBase bloc) { + super.onCreate(bloc); + if (kDebugMode) { + print('onCreate Fired--> Started'); + print(bloc); + print('onCreate Fired--> Ended'); + } + } + + @override + void onTransition(Bloc bloc, Transition transition) { + super.onTransition(bloc, transition); + if (kDebugMode) { + print('OnTransition Fired--> Started'); + print(transition); + print('OnTransition Fired--> Ended'); + } + } + + @override + void onError(BlocBase bloc, Object error, StackTrace stackTrace) { + if (kDebugMode) { + print('OnError Fired--> Started'); + print(error); + print('OnError Fired--> Ended'); + } + super.onError(bloc, error, stackTrace); + } +} diff --git a/SportsFirst/lib/extensions/app_extensions.dart b/SportsFirst/lib/extensions/app_extensions.dart new file mode 100644 index 00000000..e9a000c4 --- /dev/null +++ b/SportsFirst/lib/extensions/app_extensions.dart @@ -0,0 +1,19 @@ +import 'package:intl/intl.dart'; + +extension DateTimeData on DateTime { + String toLocalDate() { + final DateFormat formatter = DateFormat('EEE, MM/dd'); + return formatter.format(toLocal()); + } + + String toLocalTime() { + final DateFormat formatter = DateFormat('hh:mm a'); + return formatter.format(toLocal()); + } + + String toLocalDay() { + final DateFormat formatter = DateFormat('EEE'); + return formatter.format(toLocal()); + } + +} diff --git a/SportsFirst/lib/main.dart b/SportsFirst/lib/main.dart new file mode 100644 index 00000000..fd42ede3 --- /dev/null +++ b/SportsFirst/lib/main.dart @@ -0,0 +1,97 @@ +// ignore_for_file: deprecated_member_use + +import 'package:agora_video/bloc/observer.dart'; +import 'package:agora_video/screens/home/user_home/bloc/verify_bloc.dart'; +import 'package:agora_video/screens/landing/bloc/landing_bloc.dart'; +import 'package:agora_video/screens/landing/landing_page.dart'; +import 'package:agora_video/utils/app_methods.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_branch_sdk/flutter_branch_sdk.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp(); + BlocOverrides.runZoned( + () { + runApp(const AgoraVideo()); + }, + blocObserver: AppObserver(), + ); +} + +class AgoraVideo extends StatefulWidget { + const AgoraVideo({Key? key}) : super(key: key); + + @override + State createState() => _AgoraVideoState(); +} + +class _AgoraVideoState extends State { + bool isLinkClicked = false; + DocumentSnapshot? doc; + + @override + void initState() { + initBranchSDK(); + FlutterBranchSdk.validateSDKIntegration(); + super.initState(); + } + + void initBranchSDK() { + FlutterBranchSdk.initSession().listen((data) { + if (data.containsKey("+clicked_branch_link") && + data["+clicked_branch_link"] == true) { + //Link clicked. Add logic to get link data + + if (data.containsKey("channel_name")) { + if (kDebugMode) { + print('Link channel name: ${data["channel_name"]}'); + print('link user id: ${data["user_id"]}'); + } + _openLiveStream(data); + } + } + }, onError: (error) { + PlatformException platformException = error as PlatformException; + if (kDebugMode) { + print( + 'InitSession error: ${platformException.code} - ${platformException.message}'); + } + }); + } + + Future _openLiveStream(Map data) async { + doc = await AppMethods.getStreamFromFirebase( + channelName: data["channel_name"], + userId: data["user_id"], + ); + isLinkClicked = true; + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => VerifyBloc(), + ), + BlocProvider( + create: (context) => LandingBloc(), + ), + ], + child: MaterialApp( + debugShowCheckedModeBanner: false, + home: LandingPage( + isLinkClicked: false, + doc: doc, + ), + ), + ); + } +} diff --git a/SportsFirst/lib/models/app_user.dart b/SportsFirst/lib/models/app_user.dart new file mode 100644 index 00000000..f374b2f2 --- /dev/null +++ b/SportsFirst/lib/models/app_user.dart @@ -0,0 +1,98 @@ +import 'package:agora_video/services/firebase_auth.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/foundation.dart'; + +class AppUser { + String? uid; + String? email; + String? photoUrl; + String? displayName; + bool? success; + Data? data; + String? id; + AppLogin? type; + + // factory AppUser.fromJson(Map json) => AppUser( + // success: json["success"], + // data: Data.fromJson(json["data"]), + // ); + + factory AppUser.fromJson(Map json) { + String? fbId = json['fbId']; + String? appleId = json['appleId']; + String? googleId = json['gId']; + String? id = fbId ?? googleId ?? appleId; + + return AppUser( + displayName: json['name'], + email: json['emailId'], + id: id, + photoUrl: json['profileImg'], + ); + } + + AppUser( + {this.data, + this.success, + this.uid, + this.email, + this.photoUrl, + this.displayName, + this.id, + this.type}); + + Map toJson() => { + "success": success, + "data": data?.toJson(), + //todo: there is fb id gid and name is also returning inside data + }; + +// response{success: true, message: Register Successfully, data: {userId: hdQJdKwc55kXaRvLq, authToken: lqghaN0om3dVSvoq7nwyucNhBVbaJiin6_ZmlL-h-cs, name: Riddhi Daftary, fbId: 105629627240340198785, gId: null}} + + factory AppUser.fromFirebase( + User? user, { + String? id, + AppLogin? logintype, + String? name, + String? email, + String? imageUrl, + bool useUpdatedName = false, + }) { + if (kDebugMode) { + print('uid in app user ' + user!.uid); + print('id in app user $id'); + print('email in app user ${user.email ?? email}'); + print('name in app user $name'); + print('useUpdatedName in app user $useUpdatedName'); + } + + return AppUser( + uid: user?.uid, + email: user?.email ?? email, + id: id, + type: logintype, + displayName: useUpdatedName ? name : user!.displayName ?? name, + photoUrl: user!.photoURL ?? imageUrl, + ); + } +} + +class Data { + Data({ + this.userId, + this.authToken, + }); + + String? userId; + String? authToken; + + factory Data.fromJson(Map json) => Data( + userId: json["userId"], + authToken: json["authToken"], + ); + + Map toJson() => { + "userId": userId, + "authToken": authToken, + }; +} diff --git a/SportsFirst/lib/screens/details_page/stream_details.dart b/SportsFirst/lib/screens/details_page/stream_details.dart new file mode 100644 index 00000000..49349629 --- /dev/null +++ b/SportsFirst/lib/screens/details_page/stream_details.dart @@ -0,0 +1,220 @@ +import 'package:agora_video/extensions/app_extensions.dart'; +import 'package:agora_video/screens/stream/stream_page.dart'; +import 'package:agora_video/utils/app_colors.dart'; +import 'package:agora_video/utils/app_methods.dart'; +import 'package:agora_video/utils/app_strings.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class StreamDetails extends StatelessWidget { + final String channelName; + final String description; + final String eventName; + final String guideName; + final bool isBroadcaster; + final String? imageLink; + final DateTime date; + const StreamDetails({ + Key? key, + required this.channelName, + required this.description, + required this.eventName, + required this.guideName, + required this.isBroadcaster, + required this.imageLink, + required this.date, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: CachedNetworkImage( + imageUrl: imageLink ?? AppStrings.defaultImageUrl, + errorWidget: (context, url, error) => const Icon(Icons.error), + placeholder: (context, url) { + return Center( + child: CircularProgressIndicator( + color: AppColors.primaryColor, + ), + ); + }, + imageBuilder: (context, imageProvider) { + return Stack( + children: [ + buildBackgroundImage(imageProvider), + buildOverlay(), + buildEventDetails(context), + ], + ); + }, + ), + ); + } + + Widget buildEventDetails(BuildContext context) { + return Positioned( + bottom: 22, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 22), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + buildEventName(context), + const SizedBox(height: 12), + buildEventDescription(context), + const SizedBox(height: 12), + buildDateTime(context), + const SizedBox(height: 12), + buildGuideName(context), + const SizedBox(height: 32), + buildJoinWidget(context), + ], + ), + ), + ); + } + + Widget buildOverlay() { + return Container( + height: double.infinity, + width: double.infinity, + decoration: const BoxDecoration( + backgroundBlendMode: BlendMode.darken, + color: Colors.black26, + ), + ); + } + + Widget buildBackgroundImage(ImageProvider imageProvider) { + return Container( + decoration: BoxDecoration( + image: DecorationImage( + image: imageProvider, + fit: BoxFit.cover, + ), + ), + ); + } + + Widget buildEventName(BuildContext context) { + return SizedBox( + width: MediaQuery.of(context).size.width * 0.8, + child: Text( + eventName, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 32, + color: Colors.white, + ), + ), + ); + } + + Widget buildEventDescription(BuildContext context) { + return SizedBox( + width: MediaQuery.of(context).size.width * 0.8, + child: Text( + description, + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 22, + color: Colors.white70, + ), + ), + ); + } + + Widget buildGuideName(BuildContext context) { + return SizedBox( + width: MediaQuery.of(context).size.width * 0.8, + child: Text( + "Hosted by: " + guideName, + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: Colors.white70, + ), + ), + ); + } + + Widget buildDateTime(BuildContext context) { + return SizedBox( + width: MediaQuery.of(context).size.width * 0.8, + child: Text( + "Event starts on: " + date.toLocalDate() + " @" + date.toLocalTime(), + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: Colors.white70, + ), + ), + ); + } + + Widget buildJoinWidget(BuildContext context) { + return AppMethods.calculateDifference(date) == 0 + ? AppMethods.isEventNow(date) + ? ElevatedButton( + onPressed: () { + Navigator.of(context).push( + CupertinoPageRoute( + builder: (context) => StreamPage( + channelName: channelName, + isBroadcaster: false, + guideName: guideName, + description: description, + eventName: eventName, + imageLink: imageLink ?? AppStrings.defaultImageUrl, + ), + ), + ); + }, + style: ElevatedButton.styleFrom( + primary: AppColors.primaryColor, + padding: + const EdgeInsets.symmetric(vertical: 8, horizontal: 22), + ), + child: Text( + AppStrings.joinButton, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 22, + ), + ), + ) + : buildStartSoonText() + : AppMethods.calculateDifference(date) >= 1 + ? Text( + AppStrings.eventNotStarted, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 16, + ), + ) + : Text( + AppStrings.eventCompleted, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 16, + ), + ); + } + + Widget buildStartSoonText() { + return Text( + AppStrings.eventSoon, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 22, + ), + ); + } +} diff --git a/SportsFirst/lib/screens/home/agent_home/agent_home.dart b/SportsFirst/lib/screens/home/agent_home/agent_home.dart new file mode 100644 index 00000000..f7ab4871 --- /dev/null +++ b/SportsFirst/lib/screens/home/agent_home/agent_home.dart @@ -0,0 +1,88 @@ +import 'package:agora_video/screens/home/agent_home/tabs/create_event_tab.dart'; +import 'package:agora_video/screens/home/agent_home/tabs/view_event_tab.dart'; +import 'package:agora_video/screens/stream/stream_page.dart'; +import 'package:agora_video/utils/app_methods.dart'; +import 'package:agora_video/utils/app_strings.dart'; +import 'package:agora_video/widgets/app_drawer.dart'; +import 'package:agora_video/widgets/appbar.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class AgentHomePage extends StatefulWidget { + final String? channelName; + final String? userId; + const AgentHomePage({ + Key? key, + this.channelName, + this.userId, + }) : super(key: key); + + @override + State createState() => _AgentHomePageState(); +} + +class _AgentHomePageState extends State { + @override + void initState() { + _checkEventFromLink(); + + super.initState(); + } + + @override + void didChangeDependencies() { + _checkEventFromLink(); + super.didChangeDependencies(); + } + + void _checkEventFromLink() { + if (widget.channelName != null) { + openLiveStream(widget.channelName!, widget.userId!); + } + } + + Future openLiveStream(String channelName, String userId) async { + DocumentSnapshot? doc = await AppMethods.getStreamFromFirebase( + channelName: channelName, + userId: userId, + ); + if (doc != null) { + Navigator.of(context).push( + CupertinoPageRoute( + builder: (context) => StreamPage( + channelName: doc['channel_name'], + description: doc['description'], + eventName: doc['event_name'], + guideName: doc['guide_name'], + imageLink: doc['image_link'] ?? AppStrings.defaultImageUrl, + isBroadcaster: false, + ), + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppStrings.channelNotFound), + ), + ); + } + } + + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: 2, + child: Scaffold( + appBar: HomeAppBar(), + drawer: const AppDrawer(), + body: const TabBarView( + children: [ + CreateEventTab(), + ViewEventsTab(), + ], + ), + ), + ); + } +} diff --git a/SportsFirst/lib/screens/home/agent_home/tabs/create_event_tab.dart b/SportsFirst/lib/screens/home/agent_home/tabs/create_event_tab.dart new file mode 100644 index 00000000..118b506e --- /dev/null +++ b/SportsFirst/lib/screens/home/agent_home/tabs/create_event_tab.dart @@ -0,0 +1,244 @@ +import 'dart:io'; + +import 'package:agora_video/extensions/app_extensions.dart'; +import 'package:agora_video/utils/app_extras.dart'; +import 'package:agora_video/utils/app_methods.dart'; +import 'package:agora_video/utils/app_strings.dart'; +import 'package:agora_video/widgets/custom_button.dart'; +import 'package:agora_video/widgets/custom_text_field.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_datetime_picker/flutter_datetime_picker.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class CreateEventTab extends StatefulWidget { + const CreateEventTab({ + Key? key, + }) : super(key: key); + + @override + State createState() => _CreateEventTabState(); +} + +class _CreateEventTabState extends State { + late DateTime selectedDate; + late TextEditingController _eventNameController; + late TextEditingController _dateController; + late TextEditingController _descriptionController; + late TextEditingController _guideNameController; + final ImagePicker _picker = ImagePicker(); + + final _formKey = GlobalKey(); + + XFile? image; + String? downloadUrl; + + @override + void initState() { + selectedDate = DateTime.now(); + _eventNameController = TextEditingController(); + _dateController = TextEditingController(); + _descriptionController = TextEditingController(); + _guideNameController = TextEditingController(); + super.initState(); + } + + @override + void dispose() { + _eventNameController.dispose(); + _dateController.dispose(); + _descriptionController.dispose(); + _guideNameController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 22), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 22), + buildAddEventText(), + const SizedBox(height: 22), + buildForm(context), + const SizedBox(height: 22), + buildAddEventButton(context), + const SizedBox(height: 22), + ], + ), + ), + ); + } + + Widget buildForm(BuildContext context) { + return Form( + key: _formKey, + child: Column( + children: [ + CustomTextField( + controller: _eventNameController, + hintText: AppStrings.textName, + validator: (value) { + if (value == null || value.isEmpty) { + return "This field is required"; + } + return null; + }, + ), + const SizedBox(height: 22), + CustomTextField( + controller: _dateController, + readOnly: true, + hintText: AppStrings.textDate, + validator: (value) { + if (value == null || value.isEmpty) { + return "This field is required"; + } + return null; + }, + onTap: () => _selectDate(context), + ), + const SizedBox(height: 22), + CustomTextField( + controller: _descriptionController, + hintText: AppStrings.textDescription, + minLines: 4, + validator: (value) { + if (value == null || value.isEmpty) { + return "This field is required"; + } + return null; + }, + ), + const SizedBox(height: 22), + CustomTextField( + controller: _guideNameController, + hintText: AppStrings.textGuideName, + validator: (value) { + if (value == null || value.isEmpty) { + return "This field is required"; + } + return null; + }, + ), + const SizedBox(height: 22), + CustomTextField( + hintText: AppStrings.textUploadPicture, + readOnly: true, + onTap: () => _pickImage(context), + ), + ], + ), + ); + } + + Widget buildAddEventButton(BuildContext context) { + return Row( + children: [ + Expanded( + child: CustomButton( + text: AppStrings.addEvent, + onPressed: () async { + if (_formKey.currentState!.validate()) { + AppExtras.showLoaderDialog( + context: context, + text: AppStrings.loaderAddingEvent, + ); + await _addEventInFirestore( + eventName: _eventNameController.text, + date: selectedDate, + description: _descriptionController.text, + guideName: _guideNameController.text, + ).then( + (value) => { + Navigator.of(context, rootNavigator: true).pop('dialog'), + AppExtras.showToast( + message: AppStrings.eventAddSuccess, + ), + }, + ); + } + }, + ), + ), + ], + ); + } + + Widget buildAddEventText() { + return Text( + AppStrings.addEvent, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 22, + ), + ); + } + + Future _addEventInFirestore({ + required String guideName, + required String eventName, + required DateTime date, + required String description, + }) async { + if (kDebugMode) { + print("Firestore called!"); + } + try { + final document = FirebaseFirestore.instance + .collection("broadcasts") + .doc(AppMethods.getUid()!) + .collection("events") + .doc(); + + if (image != null) { + downloadUrl = await AppMethods.uploadImageToFirebase( + File(image!.path), document.id); + } + + // String? eventLink = + // await AppMethods.getGameBranchLink(channelName: document.id); + // print(eventLink); + // print(eventLink); + await document.set({ + "user_id": AppMethods.getUid(), + "guide_name": guideName, + "channel_name": document.id, + "event_name": eventName, + "date_time": date, + "description": description, + "image_link": downloadUrl, + "event_link": "" + }); + } catch (e) { + print(e); + } + } + + Future _pickImage(BuildContext context) async { + if (await Permission.storage.isGranted) { + image = await _picker.pickImage(source: ImageSource.gallery); + } else { + await Permission.storage.request(); + } + } + + Future _selectDate(BuildContext context) async { + DatePicker.showDateTimePicker( + context, + minTime: DateTime.now(), + onConfirm: (date) { + setState(() { + selectedDate = date; + _dateController.text = selectedDate.toLocalDate(); + }); + }, + ); + } +} diff --git a/SportsFirst/lib/screens/home/agent_home/tabs/view_event_tab.dart b/SportsFirst/lib/screens/home/agent_home/tabs/view_event_tab.dart new file mode 100644 index 00000000..926baeb2 --- /dev/null +++ b/SportsFirst/lib/screens/home/agent_home/tabs/view_event_tab.dart @@ -0,0 +1,276 @@ +import 'package:agora_video/extensions/app_extensions.dart'; +import 'package:agora_video/screens/stream/stream_page.dart'; +import 'package:agora_video/utils/app_colors.dart'; +import 'package:agora_video/utils/app_extras.dart'; +import 'package:agora_video/utils/app_methods.dart'; +import 'package:agora_video/utils/app_strings.dart'; +import 'package:agora_video/widgets/network_image.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class ViewEventsTab extends StatefulWidget { + const ViewEventsTab({ + Key? key, + }) : super(key: key); + + @override + State createState() => _ViewEventsTabState(); +} + +class _ViewEventsTabState extends State { + Future _onPullRefresh() async { + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return buildEventCards(); + } + + Widget buildEventCards() { + return Container( + margin: const EdgeInsets.only(top: 12), + child: RefreshIndicator( + onRefresh: _onPullRefresh, + child: StreamBuilder( + stream: FirebaseFirestore.instance + .collection("broadcasts") + .doc(AppMethods.getUid()!) + .collection("events") + .orderBy("date_time") + .snapshots(), + builder: + (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasData) { + return ListView.builder( + itemCount: snapshot.data?.docs.length, + shrinkWrap: true, + itemBuilder: (context, index) { + return buildEventCard( + context: context, + data: snapshot.data?.docs[index], + ); + }, + ); + } else if (snapshot.hasError) { + return Center( + child: Text("Error: ${snapshot.error}"), + ); + } + return Center( + child: CircularProgressIndicator(color: AppColors.primaryColor), + ); + }, + ), + ), + ); + } + + Widget buildEventCard({ + required BuildContext context, + QueryDocumentSnapshot? data, + }) { + DateTime date = data?['date_time'].toDate(); + + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.white, + boxShadow: const [ + BoxShadow( + color: Colors.black54, + blurRadius: 2.0, + spreadRadius: 0.0, + offset: Offset(2.0, 2.0), + ), + ], + ), + margin: const EdgeInsets.symmetric(horizontal: 22, vertical: 12), + child: SizedBox( + height: 280, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildImage(data!), + buildEventDetails(data, date, context), + ], + ), + ), + ); + } + + Widget buildEventDetails( + QueryDocumentSnapshot data, + DateTime date, + BuildContext context, + ) { + return Expanded( + flex: 2, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildEventName(data), + const Divider(), + buildTimeAndButton(date, context, data), + ], + ), + ); + } + + Widget buildTimeAndButton( + DateTime date, + BuildContext context, + QueryDocumentSnapshot data, + ) { + return Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + buildDateAndTime(date), + Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + buildStarButton(context, date, data), + AppMethods.calculateDifference(date) < 0 + ? Container() + : buildShareButton(context, data['event_link'] ?? ""), + ], + ), + ], + ), + ), + ); + } + + Widget buildStarButton( + BuildContext context, + DateTime date, + QueryDocumentSnapshot data, + ) { + return buildStartButton(context, data); + } + + Widget buildStartButton( + BuildContext context, QueryDocumentSnapshot data) { + return ElevatedButton( + onPressed: () async { + if (await Permission.camera.isGranted && + await Permission.microphone.isGranted) { + Navigator.of(context).push( + CupertinoPageRoute( + builder: (context) => StreamPage( + channelName: "EKNjMxJOv9A3VhhAqFvZ", + isBroadcaster: true, + guideName: 'MobileFirst', + description: "Presentation for the app! ", + eventName: "Demo", + link: "https://vnml8.app.link/fI5FBaCjrrb", + imageLink: AppStrings.defaultImageUrl, + ), + ), + ); + } else { + await Permission.camera.request(); + await Permission.microphone.request(); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.primaryColor, + ), + child: Text( + AppStrings.startButton, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ); + } + + Widget buildShareButton(BuildContext context, String link) { + return IconButton( + onPressed: () => AppExtras.showLinkDialog(context, link), + icon: const Icon(Icons.share), + ); + } + + Widget buildCompletedText() { + return Text( + AppStrings.eventComplete, + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ); + } + + Widget buildUpcomingText() { + return Text( + AppStrings.eventUpcoming, + style: TextStyle( + color: AppColors.primaryColor, + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ); + } + + Widget buildStartSoonText() { + return Text( + AppStrings.eventSoon, + style: TextStyle( + color: AppColors.primaryColor, + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ); + } + + Widget buildDateAndTime(DateTime date) { + return Text( + "${date.toLocalDate()} @${date.toLocalTime()}", + style: const TextStyle( + color: Colors.black54, + fontWeight: FontWeight.w500, + fontSize: 12, + ), + ); + } + + Widget buildEventName(QueryDocumentSnapshot data) { + return Padding( + padding: const EdgeInsets.only(top: 8, left: 12, right: 12), + child: Text( + data['event_name'], + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 18, + ), + ), + ); + } + + Widget buildImage(QueryDocumentSnapshot data) { + return Expanded( + flex: 4, + child: ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(12), + topRight: Radius.circular(12), + ), + child: ImageFromNetwork( + imageLink: data['image_link'] ?? AppStrings.defaultImageUrl, + ), + ), + ); + } +} diff --git a/SportsFirst/lib/screens/home/home_page.dart b/SportsFirst/lib/screens/home/home_page.dart new file mode 100644 index 00000000..a288362d --- /dev/null +++ b/SportsFirst/lib/screens/home/home_page.dart @@ -0,0 +1,37 @@ +import 'package:agora_video/screens/home/agent_home/agent_home.dart'; +import 'package:agora_video/screens/home/user_home/user_home.dart'; +import 'package:agora_video/utils/app_methods.dart'; +import 'package:agora_video/utils/late_class.dart'; +import 'package:agora_video/widgets/loading.dart'; +import 'package:flutter/material.dart'; + +class HomePage extends StatefulWidget { + final bool isClick; + const HomePage({Key? key, required this.isClick}) : super(key: key); + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State { + Late isAgent = Late(); + + @override + void initState() { + _checkUserType(); + super.initState(); + } + + Future _checkUserType() async { + isAgent.val = await AppMethods.checkUserType(); + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return isAgent.isInitialized + ? isAgent.val == true + ? const AgentHomePage() + : const UserHomePage() + : const LoadingWidget(); + } +} diff --git a/SportsFirst/lib/screens/home/user_home/bloc/verify_bloc.dart b/SportsFirst/lib/screens/home/user_home/bloc/verify_bloc.dart new file mode 100644 index 00000000..0e77ce71 --- /dev/null +++ b/SportsFirst/lib/screens/home/user_home/bloc/verify_bloc.dart @@ -0,0 +1,156 @@ +import 'package:agora_video/utils/app_methods.dart'; +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/cupertino.dart'; + +// ignore: import_of_legacy_library_into_null_safe + +import 'package:http/http.dart'; +import 'package:walletconnect_dart/walletconnect_dart.dart'; +import 'package:web3dart/web3dart.dart'; + +import '../../../../utils/app_strings.dart'; + +part 'verify_event.dart'; + +part 'verify_state.dart'; + +class VerifyBloc extends Bloc { + SessionStatus? sessionStatus; + String? address; + int? chainId; + late bool isInstalled; + + late Client httpClient; + late Web3Client web3client; + + Map allTokens = {}; + Map unVerifiedTokens = {}; + + String? get walletAddress => address; + + VerifyBloc() : super(const MetamaskInitial()) { + on((event, emit) async { + if (event is ConnectWallet) { + emit(const WalletConnecting()); + isInstalled = true; + if (isInstalled) { + await connectWallet(); + emit(const WalletConnected()); + print(sessionStatus!.chainId); + if (sessionStatus!.chainId == 137) { + if (await verifyToken()) { + emit(const TokenVerified()); + } else { + emit(const TokenNotVerified()); + } + } else { + emit(const WrongNetwork()); + } + } else { + emit(const AppNotInstalled()); + } + } + }); + } + + Future connectWallet() async { + final connector = WalletConnect( + bridge: 'https://bridge.walletconnect.org', + clientMeta: const PeerMeta( + name: 'UnitasLink', + description: 'UnitasLink Developer App', + url: 'https://unitaslink.com/', + icons: [ + 'https://unitaslink.com/wp-content/uploads/2022/05/SiteLogo.png' + ], + ), + ); + + // Subscribe to events + connector.on('connect', (session) { + debugPrint("connect: $session"); + + address = sessionStatus?.accounts[0]; + chainId = sessionStatus?.chainId; + + debugPrint("Address: ${address!}"); + debugPrint("Chain Id: $chainId"); + }); + + connector.on('session_request', (payload) { + debugPrint("session request: $payload"); + }); + + connector.on('disconnect', (session) { + debugPrint("disconnect: $session"); + }); + + // Create a new session + if (!connector.connected) { + sessionStatus = await connector.createSession( + chainId: 137, + onDisplayUri: (uri) { + AppMethods.openUrl(uri); + }, + ); + } + } + + Future verifyToken() async { + bool flag = true; + httpClient = Client(); + web3client = Web3Client(AppStrings.endPointURL, httpClient); + + EthereumAddress ethereumAddress = + EthereumAddress.fromHex(sessionStatus!.accounts[0]); + + var token = BigInt.parse( + '107949229547521970898568846621117350688224786224499613327583753547784491468448'); + + var result = await AppMethods.query( + web3client: web3client, + functionName: "balanceOf", + args: [ethereumAddress, token], + contractJson: "assets/token.json", + contractName: "Token", + contractAddress: AppStrings.verifyContractAddress, + ); + + if (result[0].toString() == "0") { + flag = false; + } + print(flag); + return flag; + } + +// Future isAppInstalled() async { +// if (Platform.isAndroid) { +// try { +// var installedApp = +// await AppAvailability.checkAvailability("io.metamask"); +// if (installedApp.containsValue("io.metamask")) { +// return true; +// } +// } catch (e) { +// return false; +// } +// } else if (Platform.isIOS) { +// return true; +// /*try { +// //https://apps.apple.com/in/app/metamask-blockchain-wallet/id1438144202 +// var installedApp = +// await AppAvailability.checkAvailability("io.metamask://"); +// print("APP AVAIBILITY -- ${installedApp}"); +// if (installedApp.containsValue("io.metamask://")) { +// return true; +// } +// return true; +// } catch (e) { + +// return false; +// }*/ +// } +// return false; +// } +} diff --git a/SportsFirst/lib/screens/home/user_home/bloc/verify_event.dart b/SportsFirst/lib/screens/home/user_home/bloc/verify_event.dart new file mode 100644 index 00000000..7d92ee4e --- /dev/null +++ b/SportsFirst/lib/screens/home/user_home/bloc/verify_event.dart @@ -0,0 +1,15 @@ +part of 'verify_bloc.dart'; + +abstract class VerifyEvent extends Equatable { + const VerifyEvent(); + + @override + List get props => []; +} + +class ConnectWallet extends VerifyEvent { + const ConnectWallet(); + + @override + List get props => []; +} diff --git a/SportsFirst/lib/screens/home/user_home/bloc/verify_state.dart b/SportsFirst/lib/screens/home/user_home/bloc/verify_state.dart new file mode 100644 index 00000000..0bc247f0 --- /dev/null +++ b/SportsFirst/lib/screens/home/user_home/bloc/verify_state.dart @@ -0,0 +1,64 @@ +part of 'verify_bloc.dart'; + +abstract class VerifyState extends Equatable { + const VerifyState(); + + @override + List get props => []; +} + +class MetamaskInitial extends VerifyState { + const MetamaskInitial(); + + @override + List get props => []; +} + +class WalletConnecting extends VerifyState { + const WalletConnecting(); + + @override + List get props => []; +} + +class WalletConnected extends VerifyState { + const WalletConnected(); + + @override + List get props => []; +} + +// class WalletNotConnected extends VerifyState { +// const WalletNotConnected(); + +// @override +// List get props => []; +// } + +class TokenVerified extends VerifyState { + const TokenVerified(); + + @override + List get props => []; +} + +class TokenNotVerified extends VerifyState { + const TokenNotVerified(); + + @override + List get props => []; +} + +class WrongNetwork extends VerifyState { + const WrongNetwork(); + + @override + List get props => []; +} + +class AppNotInstalled extends VerifyState { + const AppNotInstalled(); + + @override + List get props => []; +} diff --git a/SportsFirst/lib/screens/home/user_home/user_home.dart b/SportsFirst/lib/screens/home/user_home/user_home.dart new file mode 100644 index 00000000..db6dafb6 --- /dev/null +++ b/SportsFirst/lib/screens/home/user_home/user_home.dart @@ -0,0 +1,344 @@ +import 'dart:io'; + +import 'package:agora_video/screens/home/user_home/bloc/verify_bloc.dart'; +import 'package:agora_video/utils/app_colors.dart'; +import 'package:agora_video/utils/app_methods.dart'; +import 'package:agora_video/utils/app_strings.dart'; +import 'package:agora_video/widgets/app_drawer.dart'; +import 'package:agora_video/widgets/appbar.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../../../widgets/custom_button.dart'; +import '../../details_page/stream_details.dart'; + +class UserHomePage extends StatelessWidget { + const UserHomePage({ + Key? key, + }) : super(key: key); + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: HomeAppBar(hasTabs: false), + drawer: const AppDrawer(), + body: SafeArea( + child: Scaffold( + body: Padding( + padding: const EdgeInsets.only(top: 42, left: 22, right: 22), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildTitleText(), + buildUsernameText(), + const SizedBox(height: 32), + buildExperienceText(), + const SizedBox(height: 32), + const ConnectButton(), + ], + ), + ), + ), + ), + ); + } + + Widget buildExperienceText() { + return Text( + AppStrings.linkText, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 24, + ), + ); + } + + Widget buildTitleText() { + return Text( + AppStrings.welcome, + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 32, + ), + ); + } + + Widget buildUsernameText() { + return Text( + AppMethods.getUsername()!, + style: TextStyle( + color: AppColors.primaryColor, + fontWeight: FontWeight.bold, + fontSize: 36, + ), + ); + } +} + +class ConnectButton extends StatelessWidget { + const ConnectButton({ + Key? key, + }) : super(key: key); + Future _launchUrl() async { + if (!await launchUrl(Uri.parse( + "https://opensea.io/assets/matic/0x2953399124f0cbb46d2cbacd8a89cf0599974963/107949229547521970898568846621117350688224786224499613327583753547784491468448"))) { + throw Exception( + 'Could not launch https://opensea.io/assets/matic/0x2953399124f0cbb46d2cbacd8a89cf0599974963/107949229547521970898568846621117350688224786224499613327583753547784491468448'); + } + } + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listener: (context, state) { + if (state is TokenVerified) { + Navigator.of(context).push( + CupertinoPageRoute( + builder: (context) => StreamDetails( + date: DateTime.now(), + channelName: "EKNjMxJOv9A3VhhAqFvZ", + isBroadcaster: false, + guideName: 'Toni Greg', + description: + "Commentary in Hindi, with lot of humour and insights", + eventName: "India vs Australia Third Test", + imageLink: AppStrings.defaultImageUrl, + ), + ), + ); + } + }, + builder: (context, state) { + if (state is WalletConnecting || state is WalletConnected) { + return const Center(child: CircularProgressIndicator()); + } + if (state is WalletConnected) { + // return Container( + // child: const Text("Token Verified"), + // ); + debugPrint("Connected-------------------------=>"); + } + if (state is AppNotInstalled) { + return const WalletNotInstalledWidget(); + } + if (state is WrongNetwork) { + return const WrongNetworkWidget(); + } + if (state is TokenVerified) { + return Container( + child: const Text("Token Verified"), + ); + } + if (state is TokenNotVerified) { + return Container( + child: Column( + children: [ + Wrap( + children: [ + const Text( + "You don’t have the watch party NFT. In order to attend the watch party buy the NFT from here:"), + TextButton( + onPressed: () { + _launchUrl(); + }, + child: const Text("Click here")), + ], + ), + const SizedBox( + height: 10, + ), + CustomButton( + text: AppStrings.tryAgain, + onPressed: () { + BlocProvider.of(context) + .add(const ConnectWallet()); + }, + ) + ], + ), + ); + } + return Center( + child: CustomButton( + text: AppStrings.conectButton, + onPressed: () { + BlocProvider.of(context).add(const ConnectWallet()); + }, + ), + ); + }, + ); + } +} + +class WalletNotInstalledWidget extends StatelessWidget { + const WalletNotInstalledWidget({ + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + AppStrings.walletNotInstalled, + style: const TextStyle( + color: Colors.white70, + fontWeight: FontWeight.w600, + fontSize: 20, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 12), + InkWell( + onTap: () { + if (Platform.isAndroid) { + AppMethods.openUrl(AppStrings.metaMaskDownloadURLAndroid); + } else if (Platform.isIOS) { + AppMethods.openUrl(AppStrings.metaMaskDownloadURLiOS); + } + }, + child: Text( + AppStrings.installWallet, + style: TextStyle( + color: AppColors.secondaryColor, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + ), + ], + ); + } +} + +class WrongNetworkWidget extends StatelessWidget { + const WrongNetworkWidget({ + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + AppStrings.wrongNetwork, + style: const TextStyle( + color: Colors.white70, + fontWeight: FontWeight.w600, + fontSize: 20, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 12), + CustomButton( + text: AppStrings.tryAgain, + onPressed: () { + BlocProvider.of(context).add(const ConnectWallet()); + }, + ), + ], + ); + } +} + +class TokenVerifiedText extends StatelessWidget { + const TokenVerifiedText({ + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Text( + AppStrings.verifiedText, + style: const TextStyle( + color: Colors.white70, + fontWeight: FontWeight.w600, + fontSize: 20, + ), + textAlign: TextAlign.center, + ); + } +} + +class TokenNotVerifiedWidget extends StatelessWidget { + final Map unVerifiedTokens; + const TokenNotVerifiedWidget({ + Key? key, + required this.unVerifiedTokens, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + List unVerifiedList = unVerifiedTokens.keys.toList(); + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + AppStrings.notVerifiedText, + style: const TextStyle( + color: Colors.white70, + fontWeight: FontWeight.w600, + fontSize: 20, + ), + textAlign: TextAlign.center, + ), + NoNFTList(unVerifiedList: unVerifiedList), + ], + ); + } +} + +class NoNFTList extends StatelessWidget { + const NoNFTList({ + Key? key, + required this.unVerifiedList, + }) : super(key: key); + + final List unVerifiedList; + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 12), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColors.textFieldColor, + borderRadius: BorderRadius.circular(12), + ), + child: ListView.builder( + shrinkWrap: true, + padding: EdgeInsets.zero, + itemCount: unVerifiedList.length, + itemBuilder: (context, index) { + return Row( + children: [ + Icon( + Icons.close_rounded, + color: AppColors.secondaryColor, + ), + const SizedBox(width: 12), + Text( + unVerifiedList[index], + style: const TextStyle( + color: Colors.white70, + fontWeight: FontWeight.w600, + fontSize: 20, + ), + textAlign: TextAlign.left, + ), + ], + ); + }, + ), + ); + } +} diff --git a/SportsFirst/lib/screens/landing/bloc/landing_bloc.dart b/SportsFirst/lib/screens/landing/bloc/landing_bloc.dart new file mode 100644 index 00000000..df404e84 --- /dev/null +++ b/SportsFirst/lib/screens/landing/bloc/landing_bloc.dart @@ -0,0 +1,16 @@ +import 'package:bloc/bloc.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:equatable/equatable.dart'; + +part 'landing_event.dart'; +part 'landing_state.dart'; + +class LandingBloc extends Bloc { + LandingBloc() : super(const LandingInitial()) { + on((event, emit) { + if (event is LinkClickEvent) { + emit(LinkClickState(event.doc)); + } + }); + } +} diff --git a/SportsFirst/lib/screens/landing/bloc/landing_event.dart b/SportsFirst/lib/screens/landing/bloc/landing_event.dart new file mode 100644 index 00000000..2fe3da77 --- /dev/null +++ b/SportsFirst/lib/screens/landing/bloc/landing_event.dart @@ -0,0 +1,17 @@ +part of 'landing_bloc.dart'; + +abstract class LandingEvent extends Equatable { + const LandingEvent(); + + @override + List get props => []; +} + +class LinkClickEvent extends LandingEvent { + final DocumentSnapshot doc; + + const LinkClickEvent(this.doc); + + @override + List get props => [doc]; +} diff --git a/SportsFirst/lib/screens/landing/bloc/landing_state.dart b/SportsFirst/lib/screens/landing/bloc/landing_state.dart new file mode 100644 index 00000000..8d706e83 --- /dev/null +++ b/SportsFirst/lib/screens/landing/bloc/landing_state.dart @@ -0,0 +1,20 @@ +part of 'landing_bloc.dart'; + +abstract class LandingState extends Equatable { + const LandingState(); + + @override + List get props => []; +} + +class LandingInitial extends LandingState { + const LandingInitial(); +} + +class LinkClickState extends LandingState { + final DocumentSnapshot doc; + const LinkClickState(this.doc); + + @override + List get props => [doc]; +} diff --git a/SportsFirst/lib/screens/landing/landing_page.dart b/SportsFirst/lib/screens/landing/landing_page.dart new file mode 100644 index 00000000..3cc0d30b --- /dev/null +++ b/SportsFirst/lib/screens/landing/landing_page.dart @@ -0,0 +1,86 @@ +import 'package:agora_video/screens/details_page/stream_details.dart'; +import 'package:agora_video/screens/home/home_page.dart'; +import 'package:agora_video/screens/landing/bloc/landing_bloc.dart'; +import 'package:agora_video/screens/login/login_page.dart'; +import 'package:agora_video/utils/app_methods.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class LandingPage extends StatefulWidget { + final bool isLinkClicked; + final DocumentSnapshot? doc; + + const LandingPage({Key? key, this.isLinkClicked = false, this.doc}) + : super(key: key); + + @override + State createState() => _LandingPageState(); +} + +class _LandingPageState extends State { + @override + void initState() { + print(widget.isLinkClicked); + print(widget.doc); + _checkLinkClick(); + super.initState(); + } + + @override + void didUpdateWidget(covariant LandingPage oldWidget) { + _checkLinkClick(); + super.didUpdateWidget(oldWidget); + } + + void _checkLinkClick() { + if (widget.isLinkClicked && AppMethods.getUid() != null) { + if (kDebugMode) { + print("Called link event"); + } + BlocProvider.of(context).add(LinkClickEvent(widget.doc!)); + } + } + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: FirebaseAuth.instance.authStateChanges(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasData) { + return BlocConsumer( + listener: (context, state) { + if (state is LinkClickState) { + Navigator.of(context).push( + CupertinoPageRoute( + builder: (context) => StreamDetails( + channelName: state.doc['channel_name'], + isBroadcaster: false, + guideName: state.doc['guide_name'], + description: state.doc['description'], + eventName: state.doc['event_name'], + date: state.doc['date_time'].toDate(), + imageLink: state.doc['image_link'], + ), + ), + ); + } + }, + buildWhen: (previous, current) { + if (current is LinkClickState) { + return false; + } + return true; + }, + builder: (context, state) { + return HomePage(isClick: widget.isLinkClicked,); + }, + ); + } + return const LoginPage(); + }, + ); + } +} diff --git a/SportsFirst/lib/screens/landing/new_landing_page.dart b/SportsFirst/lib/screens/landing/new_landing_page.dart new file mode 100644 index 00000000..7de49869 --- /dev/null +++ b/SportsFirst/lib/screens/landing/new_landing_page.dart @@ -0,0 +1,96 @@ +// import 'package:agora_video/screens/details_page/stream_details.dart'; +// import 'package:agora_video/screens/home/home_page.dart'; +// import 'package:agora_video/screens/landing/bloc/landing_bloc.dart'; +// import 'package:agora_video/screens/login/login_page.dart'; +// import 'package:agora_video/utils/app_methods.dart'; +// import 'package:cloud_firestore/cloud_firestore.dart'; +// import 'package:firebase_auth/firebase_auth.dart'; +// import 'package:flutter/cupertino.dart'; +// import 'package:flutter/foundation.dart'; +// import 'package:flutter_bloc/flutter_bloc.dart'; + +// import '../home/user_home/bloc/verify_bloc.dart'; + +// class LandingPage extends StatefulWidget { +// final bool isLinkClicked; +// final DocumentSnapshot? doc; + +// const LandingPage({Key? key, this.isLinkClicked = true, this.doc}) +// : super(key: key); + +// @override +// State createState() => _LandingPageState(); +// } + +// class _LandingPageState extends State { +// @override +// void initState() { +// _checkLinkClick(); +// super.initState(); +// } + +// @override +// void didUpdateWidget(covariant LandingPage oldWidget) { +// _checkLinkClick(); +// super.didUpdateWidget(oldWidget); +// } + +// void _checkLinkClick() { +// if (widget.isLinkClicked && AppMethods.getUid() != null) { +// if (kDebugMode) { +// print("Called link event"); +// } +// BlocProvider.of(context).add(const ConnectWallet()); +// } +// } + +// @override +// Widget build(BuildContext context) { +// return StreamBuilder( +// stream: FirebaseAuth.instance.authStateChanges(), +// builder: (BuildContext context, AsyncSnapshot snapshot) { +// if (snapshot.hasData) { +// return BlocConsumer( +// listener: (context, state) { +// if (state is TokenVerified) { +// BlocProvider.of(context) +// .add(LinkClickEvent(widget.doc!)); +// } +// }, +// builder: (context, state) { +// return BlocConsumer( +// listener: (context, state) { +// if (state is LinkClickState) { +// Navigator.of(context).push( +// CupertinoPageRoute( +// builder: (context) => StreamDetails( +// channelName: state.doc['channel_name'], +// isBroadcaster: false, +// guideName: state.doc['guide_name'], +// description: state.doc['description'], +// eventName: state.doc['event_name'], +// date: state.doc['date_time'].toDate(), +// imageLink: state.doc['image_link'], +// ), +// ), +// ); +// } +// }, +// buildWhen: (previous, current) { +// if (current is LinkClickState) { +// return false; +// } +// return true; +// }, +// builder: (context, state) { +// return const HomePage(); +// }, +// ); +// }, +// ); +// } +// return const LoginPage(); +// }, +// ); +// } +// } diff --git a/SportsFirst/lib/screens/login/login_page.dart b/SportsFirst/lib/screens/login/login_page.dart new file mode 100644 index 00000000..993e2ff1 --- /dev/null +++ b/SportsFirst/lib/screens/login/login_page.dart @@ -0,0 +1,259 @@ +import 'package:agora_video/main.dart'; +import 'package:agora_video/services/firebase_auth.dart'; +import 'package:agora_video/utils/app_colors.dart'; +import 'package:agora_video/utils/app_extras.dart'; +import 'package:agora_video/utils/app_methods.dart'; +import 'package:agora_video/utils/app_strings.dart'; +import 'package:agora_video/widgets/custom_button.dart'; +import 'package:agora_video/widgets/custom_text_field.dart'; +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class LoginPage extends StatefulWidget { + const LoginPage({Key? key}) : super(key: key); + + @override + State createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + final FirebaseAuthService _service = FirebaseAuthService(); + + final TextEditingController _emailController = TextEditingController(); + final TextEditingController _passwordController = TextEditingController(); + + late SharedPreferences _prefs; + + @override + void initState() { + super.initState(); + _initializeSharedPrefs(); + } + + Future _initializeSharedPrefs() async { + _prefs = await SharedPreferences.getInstance(); + } + + Future _handleAgentSignIn(BuildContext context) async { + if (_emailController.text.isNotEmpty && + _passwordController.text.isNotEmpty) { + AppExtras.showLoaderDialog( + context: context, text: AppStrings.loaderLoggingIn); + await _service + .signIn( + email: _emailController.text, password: _passwordController.text) + .then((value) async { + if (value == "Success") { + await AppMethods.setUserTypePrefs(prefs: _prefs, isAgent: true); + Navigator.of(context).pop(); + Navigator.of(context).pop(); + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const AgoraVideo(), + ), + ); + } else { + Navigator.of(context).pop(); + AppExtras.showLoaderDialog( + context: context, + text: value, + isLoading: false, + isBarrierDismissible: true, + ); + } + }); + } + } + + Future _handleUserSignIn(BuildContext context) async { + try { + await _service.googleLogin().then((value) async { + await AppMethods.setUserTypePrefs(prefs: _prefs, isAgent: false); + await AppMethods.addUserToFirebase(value); + }); + } catch (e) { + AppExtras.showLoaderDialog( + context: context, + text: e.toString(), + isLoading: false, + isBarrierDismissible: true, + ); + } + } + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + body: Padding( + padding: const EdgeInsets.only(top: 42, left: 22, right: 22), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildTitleText(), + const SizedBox(height: 32), + buildExperienceText(), + buildBottomSection(context), + ], + ), + ), + ), + ); + } + + Widget buildBottomSection(BuildContext context) { + return Expanded( + child: Align( + alignment: Alignment.bottomLeft, + child: Padding( + padding: const EdgeInsets.only(bottom: 22), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + buildLetsGoText(), + const SizedBox(height: 12), + buildGetStartedButton(context), + buildAgentButton(context), + ], + ), + ), + ), + ); + } + + Widget buildAgentButton(BuildContext context) { + return Container( + alignment: Alignment.center, + child: TextButton( + onPressed: () => showAgentLoginSheet(context), + child: Text( + AppStrings.textAgent, + textAlign: TextAlign.center, + style: const TextStyle( + fontWeight: FontWeight.bold, + color: Colors.black54, + fontSize: 16, + ), + ), + ), + ); + } + + Widget buildGetStartedButton(BuildContext context) { + return SizedBox( + width: double.infinity, + child: CustomButton( + onPressed: () => _handleUserSignIn(context), + text: AppStrings.getStartedButton, + ), + ); + } + + Widget buildLetsGoText() { + return Text( + AppStrings.letsGo, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 22, + ), + ); + } + + Widget buildExperienceText() { + return Text( + AppStrings.startExperience, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 28, + ), + ); + } + + Widget buildTitleText() { + return Text( + AppStrings.mFVideo, + style: TextStyle( + color: AppColors.primaryColor, + fontWeight: FontWeight.bold, + fontSize: 36, + ), + ); + } + + void showAgentLoginSheet(BuildContext context) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12), + topRight: Radius.circular(12), + ), + ), + builder: (context) { + return buildContent(context); + }, + ); + } + + Padding buildContent(BuildContext context) { + return Padding( + padding: MediaQuery.of(context).viewInsets, + child: Container( + padding: const EdgeInsets.all(22), + height: MediaQuery.of(context).size.height * 0.7, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + buildLoginText(), + buildEmailField(), + buildPasswordField(), + buildLoginButton(context), + ], + ), + ), + ); + } + + SizedBox buildLoginButton(BuildContext context) { + return SizedBox( + width: double.infinity, + child: CustomButton( + text: AppStrings.loginButton, + onPressed: () => _handleAgentSignIn(context), + ), + ); + } + + Widget buildPasswordField() { + return CustomTextField( + hintText: AppStrings.textPassword, + obscureText: true, + autoCorrect: false, + enableSuggestions: false, + controller: _passwordController, + minLines: 1, + maxLines: 1, + ); + } + + Widget buildEmailField() { + return CustomTextField( + hintText: AppStrings.textEmail, + controller: _emailController, + ); + } + + Widget buildLoginText() { + return Text( + AppStrings.textLogin, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 22, + ), + ); + } +} diff --git a/SportsFirst/lib/screens/stream/stream_page.dart b/SportsFirst/lib/screens/stream/stream_page.dart new file mode 100644 index 00000000..4fcdcbcc --- /dev/null +++ b/SportsFirst/lib/screens/stream/stream_page.dart @@ -0,0 +1,864 @@ +import 'dart:io'; +import 'package:agora_rtc_engine/rtc_engine.dart'; +import 'package:agora_rtc_engine/rtc_local_view.dart' as rtc_local; +import 'package:agora_rtc_engine/rtc_remote_view.dart' as rtc_remote; +import 'package:agora_video/extensions/app_extensions.dart'; +import 'package:agora_video/utils/app_extras.dart'; +import 'package:agora_video/utils/app_strings.dart'; +import 'package:agora_video/widgets/custom_text_field.dart'; +import 'package:agora_video/widgets/icon_button.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:emoji_picker_flutter/emoji_picker_flutter.dart' as emoji; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:agora_video/utils/app_colors.dart'; +import 'package:agora_video/utils/app_id.dart'; +import 'package:agora_video/utils/app_methods.dart'; +import 'package:wakelock/wakelock.dart'; + +class StreamPage extends StatefulWidget { + final String channelName; + final String eventName; + final String guideName; + final String description; + final bool isBroadcaster; + final String? link; + final String imageLink; + + const StreamPage({ + Key? key, + required this.channelName, + required this.guideName, + required this.isBroadcaster, + required this.eventName, + required this.description, + this.link, + required this.imageLink, + }) : super(key: key); + + @override + State createState() => _StreamPageState(); +} + +class _StreamPageState extends State { + late RtcEngine _engine; + bool muted = false; + bool isChatShown = false; + bool isCameraOff = false; + late int streamId; + bool isHidden = false; + int? userId; + bool isLoading = true; + bool isLiveEnded = false; + bool isFullScreen = false; + + late String firstHalf; + late String secondHalf; + + bool descriptionShowing = true; + + final TextEditingController _controller = TextEditingController(); + bool emojiShowing = false; + final FocusNode _focus = FocusNode(); + + _onEmojiSelected(emoji.Emoji emoji) { + _controller + ..text += emoji.emoji + ..selection = TextSelection.fromPosition( + TextPosition(offset: _controller.text.length)); + } + + _onBackspacePressed() { + _controller + ..text = _controller.text.characters.skipLast(1).toString() + ..selection = TextSelection.fromPosition( + TextPosition(offset: _controller.text.length)); + } + + @override + void initState() { + initializeAgora(); + + _focus.addListener(_onFocusChange); + + if (widget.description.length > 50) { + firstHalf = widget.description.substring(0, 50); + secondHalf = widget.description.substring(50, widget.description.length); + } else { + firstHalf = widget.description; + secondHalf = ""; + } + + Wakelock.enable(); + + super.initState(); + } + + void _onFocusChange() { + if (_focus.hasFocus) { + setState(() { + emojiShowing = false; + descriptionShowing = true; + }); + } + } + + @override + void dispose() { + _engine.destroy(); + _controller.dispose(); + _focus.removeListener(_onFocusChange); + _focus.dispose(); + Wakelock.disable(); + super.dispose(); + } + + Future initializeAgora() async { + await _initAgoraRtcEngine(); + + if (widget.isBroadcaster) { + streamId = (await _engine.createDataStream(false, false))!; + } + + await _engine.joinChannel(null, widget.channelName, null, 0); + } + + Future _initAgoraRtcEngine() async { + _engine = await RtcEngine.createWithContext(RtcEngineContext(AppId.appId)); + await _engine.enableVideo(); + + await _engine.setChannelProfile(ChannelProfile.LiveBroadcasting); + if (widget.isBroadcaster) { + await _engine.setClientRole(ClientRole.Broadcaster); + } else { + await _engine.setClientRole(ClientRole.Audience); + } + + _engine.setEventHandler( + RtcEngineEventHandler( + error: (e) { + if (kDebugMode) { + print("CheckAgora Error: " + e.toString()); + } + }, + joinChannelSuccess: (channelName, uid, elapsed) async { + if (kDebugMode) { + print("ChekcAgora joinChannelSuccess Uid" + uid.toString()); + } + }, + userJoined: (uid, elapsed) { + setState(() { + userId = uid; + isLoading = false; + isLiveEnded = false; + }); + }, + userOffline: (uid, reason) { + setState(() { + isLiveEnded = true; + }); + }, + ), + ); + } + + @override + Widget build(BuildContext context) { + return WillPopScope( + onWillPop: () async { + if (emojiShowing) { + setState(() { + emojiShowing = false; + }); + return false; + } + return true; + }, + child: OrientationBuilder( + builder: (context, orientation) { + if (orientation == Orientation.landscape && !isFullScreen) { + if (!widget.isBroadcaster && userId != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + isFullScreen = true; + }); + }); + } else { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + isFullScreen = true; + }); + }); + } + } + + if (orientation == Orientation.portrait && isFullScreen) { + if (!widget.isBroadcaster && userId != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + isChatShown = false; + }); + }); + } else { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + isChatShown = false; + }); + }); + } + } + + return Scaffold( + body: isFullScreen + ? isLiveEnded && !widget.isBroadcaster + ? liveEndedWidget(context, true) + : buildStreamPlayer(context, true) + : Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + isLoading && !widget.isBroadcaster + ? Container( + height: MediaQuery.of(context).size.height * 0.3, + color: Colors.black, + child: Center( + child: + buildNoStream(AppStrings.streamNotStarted), + ), + ) + : isLiveEnded && !widget.isBroadcaster + ? liveEndedWidget(context, false) + : buildStreamPlayer(context, false), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + buildTopRow(context), + buildChatList(), + buildMessageRow(), + ], + ), + ), + ], + ), + ); + }, + ), + ); + } + + Widget buildNoStream(String text) { + return CachedNetworkImage( + imageUrl: widget.imageLink, + errorWidget: (context, url, error) => const Icon(Icons.error), + imageBuilder: (context, imageProvider) { + return Stack( + children: [ + buildBackgroundImage(imageProvider), + buildOverlay(), + buildText(text), + ], + ); + }, + ); + } + + Widget buildText(String text) { + return Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Text( + text, + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w500, + fontSize: 22, + ), + ), + ), + ); + } + + Widget buildOverlay() { + return Container( + height: double.infinity, + width: double.infinity, + decoration: const BoxDecoration( + backgroundBlendMode: BlendMode.darken, + color: Colors.black45, + ), + ); + } + + Widget buildBackgroundImage(ImageProvider imageProvider) { + return Container( + width: double.infinity, + decoration: BoxDecoration( + image: DecorationImage( + image: imageProvider, + fit: BoxFit.cover, + ), + ), + ); + } + + Widget liveEndedWidget(BuildContext context, bool flag) { + return Container( + height: flag ? null : MediaQuery.of(context).size.height * 0.3, + color: Colors.black, + child: Stack( + children: [ + buildNoStream(AppStrings.disconnected), + flag + ? Positioned( + bottom: 12, + right: 12, + child: buildFullScreenButton(), + ) + : Container(), + ], + ), + ); + } + + Widget buildStreamPlayer(BuildContext context, isFullScreen) { + return SizedBox( + height: isFullScreen + ? MediaQuery.of(context).size.height + : MediaQuery.of(context).size.height * 0.3, + child: Stack( + children: [ + buildStreamView(), + if (!isHidden) buildControlButtons(), + buildStreamChatView(), + ], + ), + ); + } + + Widget buildStreamView() { + return GestureDetector( + onTap: () => setState(() { + isHidden = !isHidden; + }), + onDoubleTap: _onSwitchCamera, + child: widget.isBroadcaster + ? const rtc_local.SurfaceView() + : rtc_remote.SurfaceView( + channelId: widget.channelName, + uid: userId!, + ), + ); + } + + Widget buildTopRow(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildEventNameText(), + buildGuideNameText(), + const SizedBox(height: 4), + buildDescriptionText(), + buildShowDescriptionButton(), + ], + ), + ); + } + + Widget buildEventNameText() { + return SizedBox( + width: MediaQuery.of(context).size.width * 0.95, + child: Text( + widget.eventName, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 26, + ), + ), + ); + } + + Widget buildShareButton(BuildContext context) { + return widget.isBroadcaster + ? IconButton( + onPressed: () => AppExtras.showLinkDialog(context, widget.link!), + icon: const Icon(Icons.share), + ) + : Container(); + } + + Widget buildGuideNameText() { + return Text( + "Host: " + widget.guideName, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 18, + color: Colors.black54, + ), + ); + } + + Widget buildDescriptionText() { + return Text( + descriptionShowing ? (firstHalf + "...") : (firstHalf + secondHalf), + ); + } + + Widget buildShowDescriptionButton() { + return InkWell( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + descriptionShowing ? "show more" : "show less", + style: TextStyle(color: AppColors.primaryColor), + ), + ], + ), + onTap: () { + setState(() { + descriptionShowing = !descriptionShowing; + }); + }, + ); + } + + Widget buildChatList() { + return Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: buildChatBuilder(), + ), + ); + } + + Widget buildChatBuilder() { + return StreamBuilder( + stream: FirebaseFirestore.instance + .collection("chats") + .doc(widget.channelName) + .collection("chats") + .orderBy('timestamp', descending: true) + .snapshots(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasData) { + return buildChatView(snapshot); + } + if (snapshot.hasError) { + return Center( + child: Text("Error: " + snapshot.error.toString()), + ); + } + return Center( + child: CircularProgressIndicator(color: AppColors.primaryColor), + ); + }, + ); + } + + Widget buildChatView(AsyncSnapshot> snapshot) { + return ListView.builder( + itemCount: snapshot.data?.docs.length, + shrinkWrap: true, + reverse: true, + itemBuilder: (context, index) { + bool isSame = + snapshot.data?.docs[index]['sender_id'] == AppMethods.getUid(); + return Align( + alignment: isSame ? Alignment.centerRight : Alignment.centerLeft, + child: Row( + mainAxisAlignment: isSame || isFullScreen + ? MainAxisAlignment.end + : MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + isSame + ? Container() + : isFullScreen + ? Container() + : buildChatProfileImage( + snapshot.data?.docs[index]['sender_profile']), + buildChatBubble(context, isSame, snapshot, index), + ], + ), + ); + }, + ); + } + + Widget buildChatBubble( + BuildContext context, + bool isSame, + AsyncSnapshot> snapshot, + int index, + ) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8), + margin: const EdgeInsets.only(bottom: 8), + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width * 0.7, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: isFullScreen + ? Colors.black54 + : isSame + ? AppColors.primaryColor + : Colors.black54, + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: + isSame ? MainAxisAlignment.end : MainAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: + isSame ? CrossAxisAlignment.end : CrossAxisAlignment.start, + children: [ + isSame + ? Container() + : buildUsernameText( + snapshot.data?.docs[index]['send_by'] + ":", + ), + const SizedBox(height: 4), + buildMessageText( + context, + snapshot.data?.docs[index]['message'], + ), + ], + ), + ], + ), + ), + ); + } + + Widget buildMessageTime(DateTime date) { + return Text( + date.toLocalTime(), + style: const TextStyle( + color: Colors.white70, + fontSize: 10, + ), + ); + } + + Widget buildMessageText(BuildContext context, String message) { + return Container( + constraints: BoxConstraints( + maxWidth: isFullScreen + ? MediaQuery.of(context).size.width * 0.23 + : MediaQuery.of(context).size.width * 0.63, + ), + child: Text( + message, + style: const TextStyle( + color: Colors.white, + ), + ), + ); + } + + Widget buildUsernameText(String username) { + return Text( + username, + textAlign: TextAlign.left, + style: const TextStyle( + color: Colors.white70, + ), + ); + } + + Widget buildChatProfileImage(String profileUrl) { + return Padding( + padding: const EdgeInsets.only(right: 6), + child: CircleAvatar( + backgroundImage: NetworkImage(profileUrl), + backgroundColor: Colors.transparent, + radius: 22, + ), + ); + } + + Widget buildMessageRow() { + return Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + buildMessageField(), + buildEmojiButton(), + buildSendButton() + ], + ), + ), + buildOffstage(), + ], + ); + } + + Widget buildMessageField() { + return Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: CustomTextField( + controller: _controller, + focus: _focus, + hintText: AppStrings.typeMessage, + ), + ), + ); + } + + Widget buildEmojiButton() { + return Material( + color: Colors.transparent, + child: IconButton( + onPressed: () { + FocusManager.instance.primaryFocus?.unfocus(); + setState(() { + emojiShowing = !emojiShowing; + }); + }, + icon: Icon( + emojiShowing ? Icons.emoji_emotions : Icons.emoji_emotions_outlined, + color: AppColors.primaryColor, + ), + ), + ); + } + + Widget buildSendButton() { + return Material( + color: Colors.transparent, + child: IconButton( + padding: EdgeInsets.zero, + onPressed: sendMessage, + icon: Icon( + Icons.send, + color: AppColors.primaryColor, + ), + ), + ); + } + + Widget buildOffstage() { + return Offstage( + offstage: !emojiShowing, + child: SizedBox( + height: 250, + child: emoji.EmojiPicker( + onEmojiSelected: (emoji.Category? category, emoji.Emoji emoji) { + _onEmojiSelected(emoji); + }, + onBackspacePressed: _onBackspacePressed, + config: emoji.Config( + columns: 7, + // Issue: https://github.com/flutter/flutter/issues/28894 + emojiSizeMax: 32 * (Platform.isIOS ? 1.30 : 1.0), + verticalSpacing: 0, + horizontalSpacing: 0, + initCategory: emoji.Category.RECENT, + bgColor: const Color(0xFFF2F2F2), + indicatorColor: AppColors.primaryColor, + iconColor: Colors.grey, + iconColorSelected: AppColors.primaryColor, + backspaceColor: AppColors.primaryColor, + skinToneDialogBgColor: Colors.white, + skinToneIndicatorColor: Colors.grey, + enableSkinTones: true, + showRecentsTab: true, + recentsLimit: 28, + noRecents: const Text( + "No Recents", + style: TextStyle(fontSize: 20, color: Colors.black26), + ), + + tabIndicatorAnimDuration: kTabScrollDuration, + categoryIcons: const emoji.CategoryIcons(), + buttonMode: emoji.ButtonMode.MATERIAL, + ), + ), + ), + ); + } + + Widget buildControlButtons() { + return widget.isBroadcaster + ? Positioned( + bottom: 18, + left: isChatShown ? 12 : 0, + right: isChatShown ? null : 0, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + buildMuteButton(), + buildEndButton(), + buildSwitchButton(), + //buildCameraButton(), + isFullScreen && + MediaQuery.of(context).orientation == + Orientation.landscape + ? buildChatToggleButton() + : Container(), + MediaQuery.of(context).orientation == Orientation.portrait + ? buildFullScreenButton() + : Container(), + ], + ), + ) + : Positioned( + bottom: 18, + right: 0, + child: Row( + children: [ + buildEndButton(), + isFullScreen && + MediaQuery.of(context).orientation == + Orientation.landscape + ? Container() + : buildFullScreenButton(), + ], + ), + ); + } + + Widget buildMuteButton() { + return IconRawButton( + onPressed: _onToggleMute, + icon: muted ? Icons.mic_off : Icons.mic, + iconColor: muted ? Colors.white : AppColors.primaryColor, + fillColor: muted ? AppColors.primaryColor : Colors.white, + ); + } + + Widget buildCameraButton() { + return IconRawButton( + onPressed: _onCameraShut, + icon: isCameraOff + ? Icons.cancel_presentation_rounded + : Icons.cancel_presentation_rounded, + iconColor: isCameraOff ? Colors.white : AppColors.primaryColor, + fillColor: isCameraOff ? AppColors.primaryColor : Colors.white, + ); + } + + Widget buildEndButton() { + return IconRawButton( + onPressed: () => _onCallEnd(context), + icon: Icons.call_end, + iconColor: Colors.white, + fillColor: Colors.redAccent, + size: 36, + ); + } + + Widget buildSwitchButton() { + return IconRawButton( + onPressed: _onSwitchCamera, + icon: Icons.switch_camera, + ); + } + + Widget buildFullScreenButton() { + return IconRawButton( + onPressed: () { + setState(() { + isFullScreen = !isFullScreen; + }); + }, + icon: Icons.fullscreen, + ); + } + + Widget buildChatToggleButton() { + return IconRawButton( + onPressed: _onToggleChat, + icon: Icons.chat, + iconColor: isChatShown ? Colors.white : AppColors.primaryColor, + fillColor: isChatShown ? AppColors.primaryColor : Colors.white, + ); + } + + Widget buildStreamChatView() { + return Positioned( + right: 12, + bottom: 0, + top: 0, + child: isChatShown + ? Container( + width: MediaQuery.of(context).size.width * 0.3, + decoration: const BoxDecoration( + color: Colors.transparent, + ), + child: buildChatBuilder(), + ) + : Container(), + ); + } + + void _onCallEnd(BuildContext context) { + Navigator.pop(context); + } + + void _onToggleMute() { + setState(() { + muted = !muted; + }); + _engine.muteLocalAudioStream(muted); + } + + void _onToggleChat() { + setState(() { + isChatShown = !isChatShown; + }); + } + + void _onCameraShut() { + setState(() { + isCameraOff = !isCameraOff; + }); + _engine.muteLocalVideoStream(isCameraOff); + } + + void _onSwitchCamera() { + //if (streamId != null) _engine.sendStreamMessage(streamId, "mute user blet"); + _engine.switchCamera(); + } + + void sendMessage() { + if (_controller.text.isNotEmpty) { + String message = _controller.text.trim(); + _controller.clear(); + + FirebaseFirestore.instance + .collection("chats") + .doc(widget.channelName) + .collection("chats") + .doc() + .set({ + "message": message, + "sender_id": AppMethods.getUid() ?? "0", + "send_by": + widget.isBroadcaster ? widget.guideName : AppMethods.getUsername(), + "sender_profile": widget.isBroadcaster + ? AppStrings.defaultProfileUrl + : AppMethods.getProfileUrl(), + "timestamp": FieldValue.serverTimestamp(), + }); + } + } +} diff --git a/SportsFirst/lib/services/auth_service.dart b/SportsFirst/lib/services/auth_service.dart new file mode 100644 index 00000000..8599ad85 --- /dev/null +++ b/SportsFirst/lib/services/auth_service.dart @@ -0,0 +1,18 @@ +import 'package:agora_video/models/app_user.dart'; + +abstract class AuthService{ + Future currentUser(); + + Future signOut(); + + void setAppUser(AppUser user); + + AppUser? getAppUser(); + + Stream get onAuthStateChanged; + + void dispose(); + + Future googleLogin(); + +} \ No newline at end of file diff --git a/SportsFirst/lib/services/firebase_auth.dart b/SportsFirst/lib/services/firebase_auth.dart new file mode 100644 index 00000000..ead41aab --- /dev/null +++ b/SportsFirst/lib/services/firebase_auth.dart @@ -0,0 +1,150 @@ +// ignore_for_file: constant_identifier_names + +import 'package:agora_video/models/app_user.dart'; +import 'package:agora_video/services/auth_service.dart'; +import 'package:agora_video/utils/app_strings.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/foundation.dart'; + +import 'package:google_sign_in/google_sign_in.dart'; + +enum AppLogin { FACEBOOK, GOOGLE, EMAIL, APPLE } + +class FirebaseAuthService implements AuthService { + final FirebaseAuth _firebaseAuth = FirebaseAuth.instance; + AppUser? appUser; + UserCredential? _credential; + static final FirebaseAuthService _singleton = FirebaseAuthService._internal(); + + factory FirebaseAuthService() { + return _singleton; + } + + FirebaseAuthService._internal(); + + GoogleSignIn googleSignIn = GoogleSignIn( + signInOption: SignInOption.standard, + scopes: ['email'], + ); + + @override + Future currentUser() async { + return AppUser.fromFirebase(_firebaseAuth.currentUser!); + } + + @override + AppUser? getAppUser() { + return appUser; + } + + @override + Stream get onAuthStateChanged => _firebaseAuth + .authStateChanges() + .map((event) => AppUser.fromFirebase(event!)); + + @override + void setAppUser(AppUser user) { + appUser = user; + } + + @override + Future signOut() async { + await _firebaseAuth.signOut(); + await googleSignIn.signOut(); + if (googleSignIn.currentUser != null) { + await googleSignIn.disconnect(); + } + } + + Future linkAccounts( + String socialId, String email, OAuthCredential credentialToLink) async { + _credential = await _credential!.user!.linkWithCredential(credentialToLink); + + if (kDebugMode) { + print('uid is ' + _credential!.user!.uid); + } + return AppUser.fromFirebase( + _credential!.user!, + id: socialId, + logintype: AppLogin.GOOGLE, + ); + } + + bool isUserLoggedIn() { + return FirebaseAuthService().appUser!.id != null; + } + + @override + void dispose() {} + + @override + Future googleLogin({ + String? email, + OAuthCredential? credentialToLink, + }) async { + try { + final account = await googleSignIn.signIn(); + if (account != null) { + final googleAuth = await account.authentication; + final googleCredential = GoogleAuthProvider.credential( + idToken: googleAuth.idToken, accessToken: googleAuth.accessToken); + _credential = + await _firebaseAuth.signInWithCredential(googleCredential); + + if (kDebugMode) { + print(googleAuth.idToken); + print('id in firebase auth service' + account.id); + } + + try { + await _credential!.user?.updateEmail(account.email); + } catch (e) { + if (kDebugMode) { + print(e); + } + } + + try { + if (_credential!.user?.displayName != account.displayName) { + await _credential?.user!.updateDisplayName(account.displayName); + } + } catch (e) { + if (kDebugMode) { + print(e); + } + } + + return account.email; + + // return AppUser.fromFirebase( + // _credential!.user, + // id: account.id, + // logintype: AppLogin.GOOGLE, + // email: account.email, + // name: account.displayName, + // useUpdatedName: isNameUpdated, + // ); + } else { + throw Exception(AppStrings.loginError); + } + } catch (e, stackTrace) { + if (kDebugMode) { + print(stackTrace); + print('google login--> ${e.toString()}'); + } + rethrow; + } + } + + //SIGN IN Using Email and Password + Future signIn( + {required String email, required String password}) async { + try { + await _firebaseAuth.signInWithEmailAndPassword( + email: email, password: password); + return "Success"; + } on FirebaseAuthException catch (e) { + return e.message.toString(); + } + } +} diff --git a/SportsFirst/lib/utils/app_colors.dart b/SportsFirst/lib/utils/app_colors.dart new file mode 100644 index 00000000..c4175b80 --- /dev/null +++ b/SportsFirst/lib/utils/app_colors.dart @@ -0,0 +1,7 @@ +import 'package:flutter/material.dart'; + +class AppColors { + static Color primaryColor = Colors.deepPurple; + static Color secondaryColor = Colors.deepPurpleAccent; + static Color textFieldColor = Colors.white54; +} \ No newline at end of file diff --git a/SportsFirst/lib/utils/app_extras.dart b/SportsFirst/lib/utils/app_extras.dart new file mode 100644 index 00000000..0e6b5771 --- /dev/null +++ b/SportsFirst/lib/utils/app_extras.dart @@ -0,0 +1,92 @@ +import 'package:agora_video/utils/app_colors.dart'; +import 'package:agora_video/utils/app_methods.dart'; +import 'package:agora_video/utils/app_strings.dart'; +import 'package:agora_video/widgets/custom_text_field.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; + +class AppExtras { + static showLoaderDialog({ + required BuildContext context, + String? text, + bool isLoading = true, + bool isBarrierDismissible = false, + }) { + AlertDialog alert = AlertDialog( + content: Row( + children: [ + if (isLoading) + CircularProgressIndicator(color: AppColors.primaryColor), + text != null + ? Container( + margin: const EdgeInsets.only(left: 12), + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.5, + child: Text( + text, + style: const TextStyle( + fontWeight: FontWeight.w600, + ), + ), + ), + ) + : Container(), + ], + ), + ); + showDialog( + barrierDismissible: isBarrierDismissible, + context: context, + builder: (BuildContext context) { + return alert; + }, + ); + } + + static void showToast({required String message}) { + Fluttertoast.showToast( + msg: message, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.BOTTOM, + timeInSecForIosWeb: 1, + textColor: Colors.white, + fontSize: 16.0, + ); + } + + static void showLinkDialog(BuildContext context, String link) async { + Widget copyButton = ElevatedButton( + style: ElevatedButton.styleFrom( + primary: AppColors.primaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: Text(AppStrings.shareButton), + onPressed: () { + AppMethods.shareLink(link); + }, + ); + + AlertDialog alert = AlertDialog( + title: Text(AppStrings.invite), + content: CustomTextField( + hintText: link, + readOnly: true, + minLines: 2, + maxLines: 3, + ), + actions: [ + copyButton, + ], + ); + + await Future.delayed(const Duration(milliseconds: 50)); + showDialog( + context: context, + builder: (BuildContext context) { + return alert; + }, + ); + } +} diff --git a/SportsFirst/lib/utils/app_id.dart b/SportsFirst/lib/utils/app_id.dart new file mode 100644 index 00000000..5b6b1bf0 --- /dev/null +++ b/SportsFirst/lib/utils/app_id.dart @@ -0,0 +1,3 @@ +class AppId { + static String appId = "a6d86dde56c346d3a23d002745c2bb08"; +} \ No newline at end of file diff --git a/SportsFirst/lib/utils/app_methods.dart b/SportsFirst/lib/utils/app_methods.dart new file mode 100644 index 00000000..16a1869b --- /dev/null +++ b/SportsFirst/lib/utils/app_methods.dart @@ -0,0 +1,236 @@ +import 'dart:io'; +import 'dart:math'; + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:firebase_storage/firebase_storage.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_branch_sdk/flutter_branch_sdk.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:url_launcher/url_launcher_string.dart'; +import 'package:web3dart/web3dart.dart'; + +class AppMethods { + static String? getUsername() { + return FirebaseAuth.instance.currentUser?.displayName; + } + + static String? getUid() { + return FirebaseAuth.instance.currentUser?.uid; + } + + static String? getProfileUrl() { + return FirebaseAuth.instance.currentUser?.photoURL; + } + + static String? getEmail() { + return FirebaseAuth.instance.currentUser?.email; + } + + static const String _chars = + 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890'; + + static final Random _rnd = Random(); + + static String getRandomString(int length) { + return String.fromCharCodes( + Iterable.generate( + length, + (_) => _chars.codeUnitAt( + _rnd.nextInt(_chars.length), + ), + ), + ); + } + + static String randomPictureUrl() { + final randInt = _rnd.nextInt(1000); + return 'http://picsum.photos/seed/$randInt/300/300'; + } + + static Future>?> getStreamFromFirebase({ + required String channelName, + required String userId, + }) async { + try { + var doc = await FirebaseFirestore.instance + .collection("broadcasts") + .doc(userId) + .collection("events") + .doc(channelName) + .get(); + if (doc.exists) { + return doc; + } else { + return null; + } + } catch (e) { + if (kDebugMode) { + print("Error in fetching stream data $e"); + return null; + } + } + return null; + } + + static Future uploadImageToFirebase( + File image, String channelName) async { + try { + TaskSnapshot task = await FirebaseStorage.instance + .ref() + .child("images/") + .child("$channelName/") + .putFile(image); + return await task.ref.getDownloadURL(); + } catch (e) { + if (kDebugMode) { + print("Upload error $e"); + } + return null; + } + } + + static Future getGameBranchLink({ + required String channelName, + }) async { + BranchUniversalObject branchUniversalObject = BranchUniversalObject( + canonicalIdentifier: 'flutter/branch', + contentMetadata: BranchContentMetaData() + ..addCustomMetadata("channel_name", channelName) + ..addCustomMetadata("user_id", AppMethods.getUid()!), + ); + BranchLinkProperties linkProperties = BranchLinkProperties(); + linkProperties.addControlParam('url', 'http://www.google.com'); + + BranchResponse response = await FlutterBranchSdk.getShortUrl( + buo: branchUniversalObject, + linkProperties: linkProperties, + ); + + if (response.success) { + if (kDebugMode) { + print(response.result); + } + return response.result; + } else { + if (kDebugMode) { + print( + 'Error in getting shorturl: ${response.errorCode} - ${response.errorMessage}'); + } + return null; + } + } + + static int calculateDifference(DateTime date) { + DateTime now = DateTime.now(); + return DateTime(date.year, date.month, date.day) + .difference(DateTime(now.year, now.month, now.day)) + .inDays; + } + + static bool isEventNow(DateTime date) { + DateTime now = DateTime.now(); + if (now.isAfter(date)) { + return true; + } + return false; + } + + static Future setUserTypePrefs( + {required SharedPreferences prefs, required bool isAgent}) async { + await prefs.setBool("IS_AGENT", isAgent); + } + + static bool getUserTypePrefs({required SharedPreferences prefs}) { + return prefs.getBool("IS_AGENT")!; + } + + static Future removeUserTypePrefs( + {required SharedPreferences prefs}) async { + await prefs.remove("IS_AGENT"); + } + + static Future addUserToFirebase(String email) async { + try { + await FirebaseFirestore.instance + .collection("users") + .doc(email) + .set({'email': email}).then((value) => true); + } catch (e) { + if (kDebugMode) { + print("Error in adding user: $e"); + } + return false; + } + return false; + } + + static Future checkUserType() async { + try { + var doc = await FirebaseFirestore.instance + .collection("users") + .doc(AppMethods.getEmail()) + .get(); + if (!doc.exists) { + return true; + } + return false; + } catch (e) { + if (kDebugMode) { + print("Error checking user type: $e"); + } + return false; + } + } + + static shareLink(String link) { + Share.share(link); + } + + static Future openUrl(String url) async { + Uri uri = Uri.parse(url); + if (!await launchUrl(uri, mode: LaunchMode.externalApplication)) { + throw 'Could not launch $uri'; + } + } + + static Future query({ + required Web3Client web3client, + required String functionName, + required List args, + required String contractJson, + required String contractName, + required String contractAddress, + }) async { + final contract = await loadContract( + contractJson: contractJson, + contractName: contractName, + contractAddress: contractAddress, + ); + final ethFunction = contract.function(functionName); + final result = await web3client.call( + contract: contract, + function: ethFunction, + params: args, + ); + + return result; + } + + static Future loadContract({ + required String contractJson, + required String contractName, + required String contractAddress, + }) async { + String abi = await rootBundle.loadString(contractJson); + + final contract = DeployedContract( + ContractAbi.fromJson(abi, contractName), + EthereumAddress.fromHex(contractAddress), + ); + return contract; + } +} diff --git a/SportsFirst/lib/utils/app_strings.dart b/SportsFirst/lib/utils/app_strings.dart new file mode 100644 index 00000000..bbad7c5f --- /dev/null +++ b/SportsFirst/lib/utils/app_strings.dart @@ -0,0 +1,95 @@ +class AppStrings { + //Default App Urls + static String defaultImageUrl = + "https://www.digitalphotomentor.com/photography/2019/11/MOUNTAIN-PHOTOGRAPHY-HALF-DOME-LAST-LIGHT-683x1024.jpg"; + static String defaultProfileUrl = + "https://t3.ftcdn.net/jpg/03/46/83/96/360_F_346839683_6nAPzbhpSkIpb8pmAwufkC7c5eD7wYws.jpg"; + +//Stream Details Page + static String joinButton = "Join"; + static String eventNotStarted = "Event has not started yet"; + static String eventCompleted = "Event is completed"; + + //Agent Home Page + static String channelNotFound = "Please check the channel name and try again"; + static String addEvent = "Add New Event"; + static String textName = "Enter name of your event"; + static String textDate = "Event date"; + static String textDescription = "Enter description"; + static String textGuideName = "Enter guide name"; + static String textUploadPicture = "Click to upload a picture"; + static String loaderAddingEvent = "Adding event..."; + static String eventAddSuccess = + "Event added! Please check in the \"View Events Tab\""; + static String loaderUploadingImage = "Uploading"; + static String eventUpcoming = "Upcoming"; + static String eventSoon = "Event starting soon"; + static String eventComplete = "Completed"; + static String startButton = "Start"; + + //User Home Page + static String welcome = "Welcome"; + static String linkText = "Click on a link you received and join an event!"; + + //Login Page + static String loaderLoggingIn = "Logging in"; + static String mFVideo = "WatchParty"; + static String startExperience = "Start The Experience From Here"; + static String letsGo = "Let's go"; + static String getStartedButton = "Get Started"; + static String textAgent = "Are you a Host?"; + static String textLogin = "Please Login To Continue"; + static String textEmail = "Enter your Email"; + static String textPassword = "Enter your password"; + static String loginButton = "Login"; + + //Stream Page + static String typeMessage = "Type a message"; + static String disconnected = "Host has left the event"; + static String invite = "Invite others"; + static String copyClipboard = "Copied to clipboard"; + static String shareButton = "Share"; + static String streamNotStarted = + "Please wait for the host to start the Watch Party"; + + //Firebase Auth Page + static String loginError = + "Login with google is unsuccessful, Please try again and allow the app to access your basic info from google account"; + +//Widgets + static String logout = "Logout"; + static String createTab = "Create Event"; + static String viewTab = "View Events"; + static String defaultButtonText = "No Text"; + static String defaultTextFieldHint = "Enter text"; + + //wallet + static String metaMaskDownloadURLAndroid = + "https://play.google.com/store/apps/details?id=io.metamask"; + static String metaMaskDownloadURLiOS = + "https://apps.apple.com/bm/app/metamask-blockchain-wallet/id1438144202"; + static String walletNotInstalled = "MetaMask wallet is not installed"; + + static String installWallet = "Click here to install MetaMask Wallet"; + static String tryAgain = "Try Again"; + + static String verifyFirst = "Verify First"; + static String content = + "For accessing further content please connect your wallet and verify that you have our NFT."; + static String conectButton = "Connect Wallet"; + static String noToken = "Don't have test token?"; + static String buyToken = "Buy test token"; + static String requestToken = "Request test token"; + static String verifyingText = "Please wait. Verifying token"; + static String verifiedText = + "Welcome user, now you can access the contents of the app."; + static String notVerifiedText = "Sorry, You do not have our NFT(s)"; + static String walletConnected = + "Wallet connected. Checking if you have our NFT"; + static String wrongNetwork = + "You are on wrong network. Please switch to Polygon Mainnet"; + static String verifyContractAddress = + "0x2953399124F0cBB46d2CbACD8A89cF0599974963"; + static String endPointURL = + "https://rpc-mainnet.maticvigil.com/v1/1c227778443a509a15e31163fd76b0eafcb7f896"; +} diff --git a/SportsFirst/lib/utils/late_class.dart b/SportsFirst/lib/utils/late_class.dart new file mode 100644 index 00000000..f2171055 --- /dev/null +++ b/SportsFirst/lib/utils/late_class.dart @@ -0,0 +1,38 @@ +import 'dart:async'; +import 'package:flutter/foundation.dart'; + +class Late { + final ValueNotifier _initialization = ValueNotifier(false); + late T _val; + + Late([T? value]) { + if (value != null) { + val = value; + } + } + + get isInitialized { + return _initialization.value; + } + + T get val => _val; + + set val(T val) => this + .._initialization.value = true + .._val = val; +} + +extension LateExtension on T { + Late get late => Late(); +} + +extension ExtLate on Late { + Future get wait { + Completer completer = Completer(); + _initialization.addListener(() async { + completer.complete(_initialization.value); + }); + + return completer.future; + } +} \ No newline at end of file diff --git a/SportsFirst/lib/widgets/app_drawer.dart b/SportsFirst/lib/widgets/app_drawer.dart new file mode 100644 index 00000000..93255046 --- /dev/null +++ b/SportsFirst/lib/widgets/app_drawer.dart @@ -0,0 +1,85 @@ +import 'package:agora_video/services/firebase_auth.dart'; +import 'package:agora_video/utils/app_colors.dart'; +import 'package:agora_video/utils/app_methods.dart'; +import 'package:agora_video/utils/app_strings.dart'; +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class AppDrawer extends StatefulWidget { + const AppDrawer({Key? key}) : super(key: key); + + @override + State createState() => _AppDrawerState(); +} + +class _AppDrawerState extends State { + final FirebaseAuthService _service = FirebaseAuthService(); + late SharedPreferences _prefs; + + @override + void initState() { + _initializeSharedPrefs(); + super.initState(); + } + + Future _initializeSharedPrefs() async { + _prefs = await SharedPreferences.getInstance(); + } + + @override + Widget build(BuildContext context) { + return Drawer( + child: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 22, vertical: 32), + child: Text( + AppStrings.mFVideo, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 32, + color: AppColors.primaryColor, + ), + ), + ), + const Divider(), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 22, vertical: 12), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GestureDetector( + onTap: () async { + await _prefs.remove("IS_AGENT"); + await _service.signOut(); + }, + child: Text( + AppStrings.logout, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 32, + color: AppColors.primaryColor, + ), + ), + ), + Text( + AppMethods.getEmail()!, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 12, + color: Colors.black, + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/SportsFirst/lib/widgets/appbar.dart b/SportsFirst/lib/widgets/appbar.dart new file mode 100644 index 00000000..63cb0f68 --- /dev/null +++ b/SportsFirst/lib/widgets/appbar.dart @@ -0,0 +1,96 @@ +import 'package:agora_video/utils/app_colors.dart'; +import 'package:agora_video/utils/app_methods.dart'; +import 'package:agora_video/utils/app_strings.dart'; +import 'package:flutter/material.dart'; + +class HomeAppBar extends StatelessWidget with PreferredSizeWidget { + final bool hasTabs; + + HomeAppBar({Key? key, this.hasTabs = true}) : super(key: key); + + @override + Widget build(BuildContext context) { + return AppBar( + title: Text( + AppStrings.mFVideo, + style: TextStyle( + color: AppColors.primaryColor, + fontWeight: FontWeight.bold, + fontSize: 26, + ), + ), + centerTitle: false, + automaticallyImplyLeading: false, + actions: [ + Padding( + padding: const EdgeInsets.only(right: 12), + child: GestureDetector( + onTap: () => Scaffold.of(context).openDrawer(), + child: CircleAvatar( + backgroundImage: NetworkImage( + AppMethods.getProfileUrl() ?? AppStrings.defaultProfileUrl), + backgroundColor: Colors.transparent, + radius: 16, + ), + ), + ), + ], + elevation: 0, + backgroundColor: Colors.transparent, + bottom: hasTabs + ? TabBar( + indicatorColor: AppColors.primaryColor, + tabs: [ + Tab( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.calendar_month_rounded, + color: AppColors.primaryColor, + ), + const SizedBox(width: 8), + Text( + AppStrings.createTab, + style: TextStyle( + color: AppColors.primaryColor, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + Tab( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.calendar_view_day_rounded, + color: AppColors.primaryColor, + ), + const SizedBox(width: 8), + Text( + AppStrings.viewTab, + style: TextStyle( + color: AppColors.primaryColor, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ], + ) + : null, + ); + } + + @override + Size get preferredSize => hasTabs + ? const Size.fromHeight(kToolbarHeight) * 2 + : const Size.fromHeight(kToolbarHeight); +} diff --git a/SportsFirst/lib/widgets/custom_button.dart b/SportsFirst/lib/widgets/custom_button.dart new file mode 100644 index 00000000..cd80a4d3 --- /dev/null +++ b/SportsFirst/lib/widgets/custom_button.dart @@ -0,0 +1,37 @@ +import 'package:agora_video/utils/app_colors.dart'; +import 'package:agora_video/utils/app_strings.dart'; +import 'package:flutter/material.dart'; + +class CustomButton extends StatelessWidget { + final String? text; + final VoidCallback? onPressed; + final double? fontSize; + const CustomButton({Key? key, this.text, this.onPressed, this.fontSize}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: onPressed, + style: ElevatedButton.styleFrom( + elevation: 8, + backgroundColor: AppColors.primaryColor, + padding: const EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + text ?? AppStrings.defaultButtonText, + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: fontSize ?? 22, + color: Colors.white, + ), + ), + ), + ); + } +} diff --git a/SportsFirst/lib/widgets/custom_text_field.dart b/SportsFirst/lib/widgets/custom_text_field.dart new file mode 100644 index 00000000..71bf5774 --- /dev/null +++ b/SportsFirst/lib/widgets/custom_text_field.dart @@ -0,0 +1,70 @@ +import 'package:agora_video/utils/app_strings.dart'; +import 'package:flutter/material.dart'; + +import '../utils/app_colors.dart'; + +class CustomTextField extends StatelessWidget { + final TextEditingController? controller; + final String? hintText; + final bool? readOnly; + final VoidCallback? onTap; + final int? maxLines; + final int? minLines; + final FocusNode? focus; + final bool? obscureText; + final bool? autoCorrect; + final bool? enableSuggestions; + final FormFieldValidator? validator; + + const CustomTextField({ + Key? key, + this.controller, + this.hintText, + this.readOnly = false, + this.onTap, + this.minLines, + this.maxLines, + this.focus, + this.obscureText, + this.autoCorrect, + this.enableSuggestions, + this.validator, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Material( + elevation: 2, + borderRadius: BorderRadius.circular(12), + child: Container( + decoration: BoxDecoration( + color: AppColors.textFieldColor, + borderRadius: BorderRadius.circular(12), + ), + child: TextFormField( + controller: controller, + readOnly: readOnly!, + onTap: onTap, + minLines: minLines, + maxLines: maxLines, + focusNode: focus, + obscureText: obscureText ?? false, + enableSuggestions: enableSuggestions ?? true, + autocorrect: autoCorrect ?? false, + cursorColor: AppColors.primaryColor, + textInputAction: TextInputAction.next, + validator: validator, + decoration: InputDecoration( + contentPadding: const EdgeInsets.all(12), + border: InputBorder.none, + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: AppColors.primaryColor), + ), + hintText: hintText ?? AppStrings.defaultTextFieldHint, + ), + ), + ), + ); + } +} diff --git a/SportsFirst/lib/widgets/icon_button.dart b/SportsFirst/lib/widgets/icon_button.dart new file mode 100644 index 00000000..d211d98f --- /dev/null +++ b/SportsFirst/lib/widgets/icon_button.dart @@ -0,0 +1,34 @@ +import 'package:agora_video/utils/app_colors.dart'; +import 'package:flutter/material.dart'; + +class IconRawButton extends StatelessWidget { + final VoidCallback onPressed; + final IconData icon; + final Color? fillColor; + final Color? iconColor; + final double? size; + const IconRawButton({ + Key? key, + required this.onPressed, + required this.icon, + this.fillColor = Colors.white, + this.iconColor, + this.size = 20.0, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return RawMaterialButton( + onPressed: onPressed, + child: Icon( + icon, + color: iconColor ?? AppColors.primaryColor, + size: size, + ), + shape: const CircleBorder(), + elevation: 2.0, + fillColor: fillColor, + padding: const EdgeInsets.all(12.0), + ); + } +} diff --git a/SportsFirst/lib/widgets/loading.dart b/SportsFirst/lib/widgets/loading.dart new file mode 100644 index 00000000..a0e71ce6 --- /dev/null +++ b/SportsFirst/lib/widgets/loading.dart @@ -0,0 +1,17 @@ +import 'package:agora_video/utils/app_colors.dart'; +import 'package:flutter/material.dart'; + +class LoadingWidget extends StatelessWidget { + const LoadingWidget({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: CircularProgressIndicator( + color: AppColors.primaryColor, + ), + ), + ); + } +} diff --git a/SportsFirst/lib/widgets/network_image.dart b/SportsFirst/lib/widgets/network_image.dart new file mode 100644 index 00000000..0d6fa6b8 --- /dev/null +++ b/SportsFirst/lib/widgets/network_image.dart @@ -0,0 +1,25 @@ +import 'package:agora_video/utils/app_colors.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; + +class ImageFromNetwork extends StatelessWidget { + final String imageLink; + const ImageFromNetwork({Key? key, required this.imageLink}) : super(key: key); + + @override + Widget build(BuildContext context) { + return CachedNetworkImage( + imageUrl: imageLink, + width: double.infinity, + height: double.infinity, + fit: BoxFit.cover, + progressIndicatorBuilder: (context, url, downloadProgress) => Center( + child: CircularProgressIndicator( + value: downloadProgress.progress, + color: AppColors.primaryColor, + ), + ), + errorWidget: (context, url, error) => const Icon(Icons.error), + ); + } +} diff --git a/SportsFirst/pubspec.lock b/SportsFirst/pubspec.lock new file mode 100644 index 00000000..94faa698 --- /dev/null +++ b/SportsFirst/pubspec.lock @@ -0,0 +1,985 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.16" + agora_rtc_engine: + dependency: "direct main" + description: + name: agora_rtc_engine + url: "https://pub.dartlang.org" + source: hosted + version: "5.3.1" + archive: + dependency: transitive + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "3.3.6" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.0" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.9.0" + bloc: + dependency: "direct main" + description: + name: bloc + url: "https://pub.dartlang.org" + source: hosted + version: "8.1.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.3" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + cli_util: + dependency: transitive + description: + name: cli_util + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.5" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + cloud_firestore: + dependency: "direct main" + description: + name: cloud_firestore + url: "https://pub.dartlang.org" + source: hosted + version: "4.4.3" + cloud_firestore_platform_interface: + dependency: transitive + description: + name: cloud_firestore_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "5.11.3" + cloud_firestore_web: + dependency: transitive + description: + name: cloud_firestore_web + url: "https://pub.dartlang.org" + source: hosted + version: "3.3.3" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.16.0" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.1" + cross_file: + dependency: transitive + description: + name: cross_file + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.3+4" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" + cryptography: + dependency: transitive + description: + name: cryptography + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + eip55: + dependency: transitive + description: + name: eip55 + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + emoji_picker_flutter: + dependency: "direct main" + description: + name: emoji_picker_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.2" + equatable: + dependency: "direct main" + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.4" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.2" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "6.11.11" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + url: "https://pub.dartlang.org" + source: hosted + version: "5.2.2" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + url: "https://pub.dartlang.org" + source: hosted + version: "2.7.0" + firebase_core_platform_interface: + dependency: "direct main" + description: + name: firebase_core_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "4.5.3" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" + firebase_storage: + dependency: "direct main" + description: + name: firebase_storage + url: "https://pub.dartlang.org" + source: hosted + version: "11.0.14" + firebase_storage_platform_interface: + dependency: transitive + description: + name: firebase_storage_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.30" + firebase_storage_web: + dependency: transitive + description: + name: firebase_storage_web + url: "https://pub.dartlang.org" + source: hosted + version: "3.3.23" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + url: "https://pub.dartlang.org" + source: hosted + version: "8.1.2" + flutter_blurhash: + dependency: transitive + description: + name: flutter_blurhash + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.0" + flutter_branch_sdk: + dependency: "direct main" + description: + name: flutter_branch_sdk + url: "https://pub.dartlang.org" + source: hosted + version: "6.4.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + url: "https://pub.dartlang.org" + source: hosted + version: "3.3.0" + flutter_datetime_picker: + dependency: "direct main" + description: + name: flutter_datetime_picker + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.1" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + fluttertoast: + dependency: "direct main" + description: + name: fluttertoast + url: "https://pub.dartlang.org" + source: hosted + version: "8.2.1" + google_identity_services_web: + dependency: transitive + description: + name: google_identity_services_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" + google_sign_in: + dependency: "direct main" + description: + name: google_sign_in + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.1" + google_sign_in_android: + dependency: transitive + description: + name: google_sign_in_android + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.7" + google_sign_in_ios: + dependency: transitive + description: + name: google_sign_in_ios + url: "https://pub.dartlang.org" + source: hosted + version: "5.5.2" + google_sign_in_platform_interface: + dependency: transitive + description: + name: google_sign_in_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.1" + google_sign_in_web: + dependency: transitive + description: + name: google_sign_in_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.11.0+1" + http: + dependency: transitive + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.5" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.2" + image: + dependency: transitive + description: + name: image + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.15" + image_picker: + dependency: "direct main" + description: + name: image_picker + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.6+3" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.5+7" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.11" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.6+9" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.6.3" + intl: + dependency: "direct main" + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.18.0" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.4" + json_annotation: + dependency: transitive + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "4.8.0" + json_rpc_2: + dependency: transitive + description: + name: json_rpc_2 + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.12" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + octo_image: + dependency: transitive + description: + name: octo_image + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.2" + path_provider: + dependency: transitive + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.13" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.23" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.9" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.4" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.11.1" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + url: "https://pub.dartlang.org" + source: hosted + version: "10.2.0" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + url: "https://pub.dartlang.org" + source: hosted + version: "10.2.0" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + url: "https://pub.dartlang.org" + source: hosted + version: "9.0.7" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "3.9.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.2" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.0" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.4" + pointycastle: + dependency: transitive + description: + name: pointycastle + url: "https://pub.dartlang.org" + source: hosted + version: "3.6.2" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.4" + provider: + dependency: transitive + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.5" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.1" + rxdart: + dependency: transitive + description: + name: rxdart + url: "https://pub.dartlang.org" + source: hosted + version: "0.27.7" + sec: + dependency: transitive + description: + name: sec + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + share_plus: + dependency: "direct main" + description: + name: share_plus + url: "https://pub.dartlang.org" + source: hosted + version: "6.3.1" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.18" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.16" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.4" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.4" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.0" + sqflite: + dependency: transitive + description: + name: sqflite + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.5" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.2+2" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + synchronized: + dependency: transitive + description: + name: synchronized + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.12" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.10" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.24" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.3" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.3" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.15" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.4" + uuid: + dependency: transitive + description: + name: uuid + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.7" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + wakelock: + dependency: "direct main" + description: + name: wakelock + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.2" + wakelock_macos: + dependency: transitive + description: + name: wakelock_macos + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.0" + wakelock_platform_interface: + dependency: transitive + description: + name: wakelock_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0" + wakelock_web: + dependency: transitive + description: + name: wakelock_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.0" + wakelock_windows: + dependency: transitive + description: + name: wakelock_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.1" + walletconnect_dart: + dependency: "direct main" + description: + name: walletconnect_dart + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.11" + web3dart: + dependency: "direct main" + description: + name: web3dart + url: "https://pub.dartlang.org" + source: hosted + version: "2.5.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.0" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.3" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.0" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.1" +sdks: + dart: ">=2.18.0 <3.0.0" + flutter: ">=3.3.0" diff --git a/SportsFirst/pubspec.yaml b/SportsFirst/pubspec.yaml new file mode 100644 index 00000000..6302bc48 --- /dev/null +++ b/SportsFirst/pubspec.yaml @@ -0,0 +1,105 @@ +name: agora_video +description: A new Flutter project. + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.0+1 + +environment: + sdk: ">=2.16.1 <3.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + agora_rtc_engine: ^5.3.1 + bloc: ^8.0.3 + cached_network_image: ^3.2.0 + cloud_firestore: ^4.4.3 + cupertino_icons: ^1.0.2 + emoji_picker_flutter: ^1.1.2 + equatable: ^2.0.3 + firebase_auth: ^4.2.2 + firebase_core: ^2.7.0 + firebase_storage: ^11.0.14 + flutter: + sdk: flutter + flutter_bloc: ^8.0.1 + flutter_branch_sdk: ^6.4.0 + flutter_datetime_picker: ^1.5.1 + fluttertoast: ^8.0.9 + google_sign_in: ^6.0.1 + image_picker: ^0.8.5 + intl: ^0.18.0 + permission_handler: ^10.2.0 + share_plus: ^6.3.1 + shared_preferences: ^2.0.13 + wakelock: ^0.6.1+2 + firebase_core_platform_interface: ^4.5.3 + walletconnect_dart: ^0.0.10 + url_launcher: ^6.1.2 + web3dart: null + + +dev_dependencies: + flutter_lints: ^2.0.1 + flutter_test: + sdk: flutter + flutter_launcher_icons: ^0.12.0 + +flutter_icons: + android: "launcher_icon" + ios: true + image_path: "assets/mfvideo.png" + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + # To add assets to your application, add an assets section, like this: + assets: + - assets/mfvideo.png + - assets/ + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/SportsFirst/web/favicon.png b/SportsFirst/web/favicon.png new file mode 100644 index 00000000..8aaa46ac Binary files /dev/null and b/SportsFirst/web/favicon.png differ diff --git a/SportsFirst/web/icons/Icon-192.png b/SportsFirst/web/icons/Icon-192.png new file mode 100644 index 00000000..b749bfef Binary files /dev/null and b/SportsFirst/web/icons/Icon-192.png differ diff --git a/SportsFirst/web/icons/Icon-512.png b/SportsFirst/web/icons/Icon-512.png new file mode 100644 index 00000000..88cfd48d Binary files /dev/null and b/SportsFirst/web/icons/Icon-512.png differ diff --git a/SportsFirst/web/icons/Icon-maskable-192.png b/SportsFirst/web/icons/Icon-maskable-192.png new file mode 100644 index 00000000..eb9b4d76 Binary files /dev/null and b/SportsFirst/web/icons/Icon-maskable-192.png differ diff --git a/SportsFirst/web/icons/Icon-maskable-512.png b/SportsFirst/web/icons/Icon-maskable-512.png new file mode 100644 index 00000000..d69c5669 Binary files /dev/null and b/SportsFirst/web/icons/Icon-maskable-512.png differ diff --git a/SportsFirst/web/index.html b/SportsFirst/web/index.html new file mode 100644 index 00000000..55734f18 --- /dev/null +++ b/SportsFirst/web/index.html @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + agora_video + + + + + + + diff --git a/SportsFirst/web/manifest.json b/SportsFirst/web/manifest.json new file mode 100644 index 00000000..bb323511 --- /dev/null +++ b/SportsFirst/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "agora_video", + "short_name": "agora_video", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/SportsFirst/windows/.gitignore b/SportsFirst/windows/.gitignore new file mode 100644 index 00000000..d492d0d9 --- /dev/null +++ b/SportsFirst/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/SportsFirst/windows/CMakeLists.txt b/SportsFirst/windows/CMakeLists.txt new file mode 100644 index 00000000..51d2d300 --- /dev/null +++ b/SportsFirst/windows/CMakeLists.txt @@ -0,0 +1,95 @@ +cmake_minimum_required(VERSION 3.14) +project(agora_video LANGUAGES CXX) + +set(BINARY_NAME "agora_video") + +cmake_policy(SET CMP0063 NEW) + +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Configure build options. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() + +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") + +# Flutter library and tool build rules. +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build +add_subdirectory("runner") + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/SportsFirst/windows/flutter/CMakeLists.txt b/SportsFirst/windows/flutter/CMakeLists.txt new file mode 100644 index 00000000..b2e4bd8d --- /dev/null +++ b/SportsFirst/windows/flutter/CMakeLists.txt @@ -0,0 +1,103 @@ +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + windows-x64 $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/SportsFirst/windows/flutter/generated_plugin_registrant.cc b/SportsFirst/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..5bf75b64 --- /dev/null +++ b/SportsFirst/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,26 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + AgoraRtcEnginePluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AgoraRtcEnginePlugin")); + EmojiPickerFlutterPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("EmojiPickerFlutterPluginCApi")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + SharePlusWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/SportsFirst/windows/flutter/generated_plugin_registrant.h b/SportsFirst/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..dc139d85 --- /dev/null +++ b/SportsFirst/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/SportsFirst/windows/flutter/generated_plugins.cmake b/SportsFirst/windows/flutter/generated_plugins.cmake new file mode 100644 index 00000000..c0938a35 --- /dev/null +++ b/SportsFirst/windows/flutter/generated_plugins.cmake @@ -0,0 +1,28 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + agora_rtc_engine + emoji_picker_flutter + permission_handler_windows + share_plus + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/SportsFirst/windows/runner/CMakeLists.txt b/SportsFirst/windows/runner/CMakeLists.txt new file mode 100644 index 00000000..de2d8916 --- /dev/null +++ b/SportsFirst/windows/runner/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) +apply_standard_settings(${BINARY_NAME}) +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/SportsFirst/windows/runner/Runner.rc b/SportsFirst/windows/runner/Runner.rc new file mode 100644 index 00000000..daabe42a --- /dev/null +++ b/SportsFirst/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#ifdef FLUTTER_BUILD_NUMBER +#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#else +#define VERSION_AS_NUMBER 1,0,0 +#endif + +#ifdef FLUTTER_BUILD_NAME +#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.app.agora_video" "\0" + VALUE "FileDescription", "agora_video" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "agora_video" "\0" + VALUE "LegalCopyright", "Copyright (C) 2022 com.app.agora_video. All rights reserved." "\0" + VALUE "OriginalFilename", "agora_video.exe" "\0" + VALUE "ProductName", "agora_video" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/SportsFirst/windows/runner/flutter_window.cpp b/SportsFirst/windows/runner/flutter_window.cpp new file mode 100644 index 00000000..b43b9095 --- /dev/null +++ b/SportsFirst/windows/runner/flutter_window.cpp @@ -0,0 +1,61 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/SportsFirst/windows/runner/flutter_window.h b/SportsFirst/windows/runner/flutter_window.h new file mode 100644 index 00000000..6da0652f --- /dev/null +++ b/SportsFirst/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/SportsFirst/windows/runner/main.cpp b/SportsFirst/windows/runner/main.cpp new file mode 100644 index 00000000..c0b2b092 --- /dev/null +++ b/SportsFirst/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.CreateAndShow(L"agora_video", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/SportsFirst/windows/runner/resource.h b/SportsFirst/windows/runner/resource.h new file mode 100644 index 00000000..66a65d1e --- /dev/null +++ b/SportsFirst/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/SportsFirst/windows/runner/resources/app_icon.ico b/SportsFirst/windows/runner/resources/app_icon.ico new file mode 100644 index 00000000..c04e20ca Binary files /dev/null and b/SportsFirst/windows/runner/resources/app_icon.ico differ diff --git a/SportsFirst/windows/runner/runner.exe.manifest b/SportsFirst/windows/runner/runner.exe.manifest new file mode 100644 index 00000000..c977c4a4 --- /dev/null +++ b/SportsFirst/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/SportsFirst/windows/runner/utils.cpp b/SportsFirst/windows/runner/utils.cpp new file mode 100644 index 00000000..d19bdbbc --- /dev/null +++ b/SportsFirst/windows/runner/utils.cpp @@ -0,0 +1,64 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr); + if (target_length == 0) { + return std::string(); + } + std::string utf8_string; + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, utf8_string.data(), + target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/SportsFirst/windows/runner/utils.h b/SportsFirst/windows/runner/utils.h new file mode 100644 index 00000000..3879d547 --- /dev/null +++ b/SportsFirst/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/SportsFirst/windows/runner/win32_window.cpp b/SportsFirst/windows/runner/win32_window.cpp new file mode 100644 index 00000000..c10f08dc --- /dev/null +++ b/SportsFirst/windows/runner/win32_window.cpp @@ -0,0 +1,245 @@ +#include "win32_window.h" + +#include + +#include "resource.h" + +namespace { + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + FreeLibrary(user32_module); + } +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + return OnCreate(); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} diff --git a/SportsFirst/windows/runner/win32_window.h b/SportsFirst/windows/runner/win32_window.h new file mode 100644 index 00000000..17ba4311 --- /dev/null +++ b/SportsFirst/windows/runner/win32_window.h @@ -0,0 +1,98 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates and shows a win32 window with |title| and position and size using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size to will treat the width height passed in to this function + // as logical pixels and scale to appropriate for the default monitor. Returns + // true if the window was created successfully. + bool CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responsponds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_