diff --git a/GSMGateway/app/src/main/java/com/gqlabs/gsmgateway/CallService.java b/GSMGateway/app/src/main/java/com/gqlabs/gsmgateway/CallService.java new file mode 100644 index 00000000..07359405 --- /dev/null +++ b/GSMGateway/app/src/main/java/com/gqlabs/gsmgateway/CallService.java @@ -0,0 +1,4 @@ +package com.gqlabs.gsmgateway; + +public class CallService { +} diff --git a/news-app/.gitignore b/news-app/.gitignore new file mode 100644 index 00000000..0f5815ed --- /dev/null +++ b/news-app/.gitignore @@ -0,0 +1,102 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# VS Code related +.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# 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 + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/.last_build_id +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/ephemeral +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages +!/dev/ci/**/Gemfile.lock + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java +**/android/key.properties +*.keystore +!**/android/app/debug.keystore + +# Generated files +*.g.dart +*.freezed.dart + +# Local env files +.env +.env.* + +# Coverage +coverage/ diff --git a/news-app/analysis_options.yaml b/news-app/analysis_options.yaml new file mode 100644 index 00000000..109446c8 --- /dev/null +++ b/news-app/analysis_options.yaml @@ -0,0 +1,32 @@ +# 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. +analyzer: + errors: + deprecated_member_use: ignore + must_be_immutable: ignore + override_on_non_overriding_member: ignore + +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 + # 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/news-app/android/app/build.gradle b/news-app/android/app/build.gradle new file mode 100644 index 00000000..e5df0e2c --- /dev/null +++ b/news-app/android/app/build.gradle @@ -0,0 +1,87 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "com.google.gms.google-services" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" +} + + +def keystoreProperties = new Properties() + def keystorePropertiesFile = rootProject.file('key.properties') + if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) + } + +android { + testOptions.unitTests.includeAndroidResources = true + namespace 'com.news_app' + compileSdk flutter.compileSdkVersion + ndkVersion "27.0.12077973" + + compileOptions { + coreLibraryDesugaringEnabled true + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } + + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.news_app" + minSdk 27 + targetSdk flutter.targetSdkVersion + multiDexEnabled true + versionCode flutter.versionCode + versionName flutter.versionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + //Don't Change Signing Configs below. + signingConfigs { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null + storePassword keystoreProperties['storePassword'] + } + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + minifyEnabled false // Change from true to false + shrinkResources false // Change from true to false + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug //Change it to signingConfigs.release for release apk + } + } +} + + + +flutter { + source '../..' +} + +dependencies { + implementation 'com.android.support:multidex:2.0.1' + implementation platform('com.google.firebase:firebase-bom:32.3.1') + implementation 'com.google.firebase:firebase-auth' + implementation 'com.google.android.gms:play-services-ads:23.6.0' + implementation 'com.google.android.gms:play-services-measurement-api:21.6.1' + implementation 'com.google.android.gms:play-services-auth:21.3.0' + implementation 'com.google.firebase:firebase-analytics' + implementation 'com.google.guava:guava:30.1.1-android' + implementation 'com.facebook.android:facebook-android-sdk:[8,9)' + implementation 'com.google.android.ump:user-messaging-platform:2.1.0' + implementation 'com.google.android.recaptcha:recaptcha:18.4.0' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4' + } + diff --git a/news-app/android/app/google-services.json b/news-app/android/app/google-services.json new file mode 100644 index 00000000..313f2d09 --- /dev/null +++ b/news-app/android/app/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "106496138088", + "project_id": "news-demo-34c11", + "storage_bucket": "news-demo-34c11.firebasestorage.app" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:106496138088:android:ed64e0205fbece3c5514dd", + "android_client_info": { + "package_name": "com.news_app" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyD9qu92sCBHK2c85JPJSFRfviXDvDF9fD4" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/news-app/android/app/src/debug/AndroidManifest.xml b/news-app/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/news-app/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/news-app/android/app/src/main/AndroidManifest.xml b/news-app/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..8edf8856 --- /dev/null +++ b/news-app/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/news-app/android/app/src/main/kotlin/com/news_app/MainActivity.kt b/news-app/android/app/src/main/kotlin/com/news_app/MainActivity.kt new file mode 100644 index 00000000..f214b8ba --- /dev/null +++ b/news-app/android/app/src/main/kotlin/com/news_app/MainActivity.kt @@ -0,0 +1,5 @@ +package com.news_app + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() {} diff --git a/news-app/android/app/src/main/res/drawable-v21/launch_background.xml b/news-app/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..a864ec0f --- /dev/null +++ b/news-app/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/news-app/android/app/src/main/res/drawable/ic_launcher.png b/news-app/android/app/src/main/res/drawable/ic_launcher.png new file mode 100644 index 00000000..7f0f9843 Binary files /dev/null and b/news-app/android/app/src/main/res/drawable/ic_launcher.png differ diff --git a/news-app/android/app/src/main/res/drawable/launch_background.xml b/news-app/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000..a864ec0f --- /dev/null +++ b/news-app/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/news-app/android/app/src/main/res/drawable/notification_icon.png b/news-app/android/app/src/main/res/drawable/notification_icon.png new file mode 100644 index 00000000..7f0f9843 Binary files /dev/null and b/news-app/android/app/src/main/res/drawable/notification_icon.png differ diff --git a/news-app/android/app/src/main/res/logo_caribe.png b/news-app/android/app/src/main/res/logo_caribe.png new file mode 100644 index 00000000..7f0f9843 Binary files /dev/null and b/news-app/android/app/src/main/res/logo_caribe.png differ diff --git a/news-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/news-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..7f0f9843 Binary files /dev/null and b/news-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/news-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/news-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000..7f0f9843 Binary files /dev/null and b/news-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/news-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_squircle.png b/news-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_squircle.png new file mode 100644 index 00000000..7f0f9843 Binary files /dev/null and b/news-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_squircle.png differ diff --git a/news-app/android/app/src/main/res/mipmap-ldpi/ic_launcher.png b/news-app/android/app/src/main/res/mipmap-ldpi/ic_launcher.png new file mode 100644 index 00000000..7f0f9843 Binary files /dev/null and b/news-app/android/app/src/main/res/mipmap-ldpi/ic_launcher.png differ diff --git a/news-app/android/app/src/main/res/mipmap-ldpi/ic_launcher_round.png b/news-app/android/app/src/main/res/mipmap-ldpi/ic_launcher_round.png new file mode 100644 index 00000000..7f0f9843 Binary files /dev/null and b/news-app/android/app/src/main/res/mipmap-ldpi/ic_launcher_round.png differ diff --git a/news-app/android/app/src/main/res/mipmap-ldpi/ic_launcher_squircle.png b/news-app/android/app/src/main/res/mipmap-ldpi/ic_launcher_squircle.png new file mode 100644 index 00000000..7f0f9843 Binary files /dev/null and b/news-app/android/app/src/main/res/mipmap-ldpi/ic_launcher_squircle.png differ diff --git a/news-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/news-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..7f0f9843 Binary files /dev/null and b/news-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/news-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/news-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000..7f0f9843 Binary files /dev/null and b/news-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/news-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_squircle.png b/news-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_squircle.png new file mode 100644 index 00000000..7f0f9843 Binary files /dev/null and b/news-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_squircle.png differ diff --git a/news-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/news-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..7f0f9843 Binary files /dev/null and b/news-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/news-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/news-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000..7f0f9843 Binary files /dev/null and b/news-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/news-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_squircle.png b/news-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_squircle.png new file mode 100644 index 00000000..7f0f9843 Binary files /dev/null and b/news-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_squircle.png differ diff --git a/news-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/news-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..7f0f9843 Binary files /dev/null and b/news-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/news-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/news-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..7f0f9843 Binary files /dev/null and b/news-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/news-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_squircle.png b/news-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_squircle.png new file mode 100644 index 00000000..7f0f9843 Binary files /dev/null and b/news-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_squircle.png differ diff --git a/news-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/news-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..7f0f9843 Binary files /dev/null and b/news-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/news-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/news-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..7f0f9843 Binary files /dev/null and b/news-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/news-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_squircle.png b/news-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_squircle.png new file mode 100644 index 00000000..7f0f9843 Binary files /dev/null and b/news-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_squircle.png differ diff --git a/news-app/android/app/src/main/res/values-night/styles.xml b/news-app/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000..449a9f93 --- /dev/null +++ b/news-app/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/news-app/android/app/src/main/res/values/colors.xml b/news-app/android/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..31c07a61 --- /dev/null +++ b/news-app/android/app/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #008DA8 + diff --git a/news-app/android/app/src/main/res/values/strings.xml b/news-app/android/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..2add2288 --- /dev/null +++ b/news-app/android/app/src/main/res/values/strings.xml @@ -0,0 +1,7 @@ + + + elCaribe + 3187011931582004 + fb3187011931582004 + c5fcb2473b8557c9ff049dd807db8240 + diff --git a/news-app/android/app/src/main/res/values/styles.xml b/news-app/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..d74aa35c --- /dev/null +++ b/news-app/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/news-app/android/app/src/profile/AndroidManifest.xml b/news-app/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/news-app/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/news-app/android/build.gradle b/news-app/android/build.gradle new file mode 100644 index 00000000..17bb8a3a --- /dev/null +++ b/news-app/android/build.gradle @@ -0,0 +1,33 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' + +subprojects { + afterEvaluate { project -> + if (project.plugins.hasPlugin("com.android.application") || + project.plugins.hasPlugin("com.android.library")) { + project.android { + compileSdkVersion 36 + } + } + if (project.hasProperty('android')) { + project.android { + if (namespace == null) { + namespace project.group + } + } + } + } + project.buildDir = "${rootProject.buildDir}/${project.name}" + project.evaluationDependsOn(':app') +} + + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/news-app/android/gradle.properties b/news-app/android/gradle.properties new file mode 100644 index 00000000..ff5389fd --- /dev/null +++ b/news-app/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx2048m +android.useAndroidX=true +android.enableJetifier=true diff --git a/news-app/android/gradle/wrapper/gradle-wrapper.properties b/news-app/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..da32f1c2 --- /dev/null +++ b/news-app/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl = https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/news-app/android/settings.gradle b/news-app/android/settings.gradle new file mode 100644 index 00000000..4b1ccad6 --- /dev/null +++ b/news-app/android/settings.gradle @@ -0,0 +1,26 @@ + pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.9.1" apply false + id "org.jetbrains.kotlin.android" version "2.1.10" apply false + id "com.google.gms.google-services" version "4.4.2" apply false +} + +include ":app" diff --git a/news-app/assets/animations/noInternet.json b/news-app/assets/animations/noInternet.json new file mode 100644 index 00000000..bffec4e6 --- /dev/null +++ b/news-app/assets/animations/noInternet.json @@ -0,0 +1 @@ +{"nm":"No internet Illustration","ddd":0,"h":160,"w":160,"meta":{"g":"LottieFiles AE "},"layers":[{"ty":0,"nm":"Mobile Bar","sr":1,"st":14.0000005702317,"op":164.000006679857,"ip":14.0000005702317,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[80,80,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.667,"y":1},"s":[80,240,0],"t":14,"ti":[0,26.667,0],"to":[0,-26.667,0]},{"s":[80,80,0],"t":21.0000008553475}],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":18},{"s":[100],"t":20.0000008146167}],"ix":11}},"ef":[],"w":160,"h":160,"refId":"comp_0","ind":1},{"ty":4,"nm":"Phone Outlines","sr":1,"st":1.00000004073083,"op":75.0000030548126,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":true,"ao":0,"ks":{"a":{"a":0,"k":[80,99.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[80,99.5,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"masksProperties":[{"nm":"Mask 1","inv":false,"mode":"i","x":{"a":0,"k":0,"ix":4},"o":{"a":0,"k":100,"ix":3},"pt":{"a":0,"k":{"c":true,"i":[[44.183,0],[0,-44.183],[-44.183,0],[0,44.183]],"o":[[-44.183,0],[0,44.183],[44.183,0],[0,-44.183]],"v":[[80,0],[0,80],[80,160],[160,80]]},"ix":1}}],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-7.18,0],[0,0],[0,-7.18],[0,0],[7.18,0],[0,0],[0,7.18],[0,0]],"o":[[0,0],[7.18,0],[0,0],[0,7.18],[0,0],[-7.18,0],[0,0],[0,-7.18]],"v":[[-33,-72],[33,-72],[46,-59],[46,59],[33,72],[-33,72],[-46,59],[-46,-59]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"c":{"a":0,"k":[0.698,0.102,0.102],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":1,"k":[{"o":{"x":0.743,"y":0.748},"i":{"x":0.256,"y":1},"s":[80,240],"t":14,"ti":[0,19.167],"to":[0,-19.167]},{"s":[80,125],"t":21.0000008553475}],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 2","ix":2,"cix":2,"np":4,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-7.18,0],[0,0],[0,-7.18],[0,0],[7.18,0],[0,0],[0,7.18],[0,0]],"o":[[0,0],[7.18,0],[0,0],[0,7.18],[0,0],[-7.18,0],[0,0],[0,-7.18]],"v":[[-33,-53.5],[33,-53.5],[46,-40.5],[46,77.5],[33,90.5],[-33,90.5],[-46,77.5],[-46,-40.5]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,1,1],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":1,"k":[{"o":{"x":0.743,"y":0.733},"i":{"x":0.213,"y":1},"s":[80,240],"t":14,"ti":[0,22.25],"to":[0,-22.25]},{"s":[80,106.5],"t":21.0000008553475}],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":2},{"ty":4,"nm":"BG Circle Outlines","sr":1,"st":0,"op":75.0000030548126,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[80,80,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":1,"y":0.863},"i":{"x":0.565,"y":1},"s":[0,0,100],"t":0},{"s":[100,100,100],"t":13.0000005295009}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[80,80,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":4,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-44.183,0],[0,-44.183],[44.183,0],[0,44.183]],"o":[[44.183,0],[0,44.183],[-44.183,0],[0,-44.183]],"v":[[0,-80],[80,0],[0,80],[-80,0]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,0.9529,0.8784],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[80,80],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":3}],"v":"4.8.0","fr":29.9700012207031,"op":75.0000030548126,"ip":0,"assets":[{"nm":"","id":"comp_0","layers":[{"ty":4,"nm":"Cross Outlines","sr":1,"st":12.00000048877,"op":162.000006598395,"ip":-1.00000004073083,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[9.485,9.486,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.982,"y":0},"i":{"x":0.667,"y":1},"s":[0,0,100],"t":36},{"o":{"x":0.67,"y":0},"i":{"x":0.833,"y":0.833},"s":[120,120,100],"t":46},{"s":[100,100,100],"t":50.0000020365418}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[56.45,86.45,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0},"i":{"x":0.667,"y":1},"s":[0],"t":12},{"o":{"x":0.167,"y":0.167},"i":{"x":0.667,"y":1},"s":[0],"t":36},{"s":[100],"t":43.0000017514259}],"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":4,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[0.248,-0.247],[0,0],[0.248,0.248],[0,0],[-0.248,0.247],[-0.248,-0.248],[0,0]],"o":[[0,0],[-0.248,0.248],[0,0],[-0.248,-0.248],[0.248,-0.248],[0,0],[0.248,0.248]],"v":[[3.143,3.142],[3.143,3.142],[2.245,3.142],[-3.143,-2.245],[-3.143,-3.142],[-2.245,-3.142],[3.143,2.245]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,1,1],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[9.485,9.486],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 2","ix":2,"cix":2,"np":4,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-0.248,-0.248],[0,0],[0.248,-0.248],[0,0],[0.248,0.248],[-0.248,0.248],[0,0]],"o":[[0,0],[0.248,0.247],[0,0],[-0.248,0.248],[-0.248,-0.247],[0,0],[0.248,-0.248]],"v":[[3.143,-3.142],[3.143,-3.142],[3.143,-2.245],[-2.245,3.142],[-3.143,3.142],[-3.143,2.245],[2.245,-3.142]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,1,1],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[9.485,9.486],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 3","ix":3,"cix":2,"np":4,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-3.286,-3.286],[0,0],[3.286,-3.286],[3.286,3.287],[-3.286,3.286]],"o":[[0,0],[3.286,3.286],[-3.286,3.287],[-3.286,-3.286],[3.286,-3.286]],"v":[[5.95,-5.95],[5.95,-5.95],[5.95,5.949],[-5.949,5.949],[-5.949,-5.95]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.902,0.2902,0.098],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[9.485,9.486],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":1},{"ty":4,"nm":"Bar 1 Outlines","sr":1,"st":12.00000048877,"op":162.000006598395,"ip":-1.00000004073083,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[8,28,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[104,103,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-1.381,0],[0,0],[0,-1.381],[0,0],[1.381,0],[0,0],[0,1.381],[0,0]],"o":[[0,0],[1.381,0],[0,0],[0,1.381],[0,0],[-1.381,0],[0,0],[0,-1.381]],"v":[[-2.5,-25],[2.5,-25],[5,-22.5],[5,22.5],[2.5,25],[-2.5,25],[-5,22.5],[-5,-22.5]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[0.7098,0.0706,0.0706],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[8,28],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":2},{"ty":4,"nm":"Bar 1 Outlines 2","sr":1,"st":12.00000048877,"op":162.000006598395,"ip":-1.00000004073083,"hd":false,"ddd":0,"bm":0,"hasMask":true,"ao":0,"ks":{"a":{"a":0,"k":[8,28,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[104,103,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"masksProperties":[{"nm":"Mask 1","inv":false,"mode":"a","x":{"a":0,"k":0,"ix":4},"o":{"a":0,"k":100,"ix":3},"pt":{"a":1,"k":[{"o":{"x":0.517,"y":0.517},"i":{"x":0,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[3,3],[3,53],[13,53],[13,3]]}],"t":12},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[2.75,56.25],[2.75,106.25],[12.75,106.25],[12.75,56.25]]}],"t":23.0000009368092}],"ix":1}}],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-1.381,0],[0,0],[0,-1.381],[0,0],[1.381,0],[0,0],[0,1.381],[0,0]],"o":[[0,0],[1.381,0],[0,0],[0,1.381],[0,0],[-1.381,0],[0,0],[0,-1.381]],"v":[[-2.5,-25],[2.5,-25],[5,-22.5],[5,22.5],[2.5,25],[-2.5,25],[-5,22.5],[-5,-22.5]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[1,0.5412,0.3961],"ix":3}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,0.5412,0.3961],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[8,28],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":3},{"ty":4,"nm":"Bar 2 Outlines","sr":1,"st":12.00000048877,"op":162.000006598395,"ip":-1.00000004073083,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[8,23,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[88,108,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-1.381,0],[0,0],[0,-1.381],[0,0],[1.381,0],[0,0],[0,1.381],[0,0]],"o":[[0,0],[1.381,0],[0,0],[0,1.381],[0,0],[-1.381,0],[0,0],[0,-1.381]],"v":[[-2.5,-20],[2.5,-20],[5,-17.5],[5,17.5],[2.5,20],[-2.5,20],[-5,17.5],[-5,-17.5]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[0.6275,0.098,0.098],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[8,23],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":4},{"ty":4,"nm":"Bar 2 Outlines 2","sr":1,"st":22.0000008960784,"op":172.000007005704,"ip":-1.00000004073083,"hd":false,"ddd":0,"bm":0,"hasMask":true,"ao":0,"ks":{"a":{"a":0,"k":[8,23,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[88,108,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"masksProperties":[{"nm":"Mask 1","inv":false,"mode":"a","x":{"a":0,"k":0,"ix":4},"o":{"a":0,"k":100,"ix":3},"pt":{"a":1,"k":[{"o":{"x":0.623,"y":0.612},"i":{"x":0.047,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[3,3],[3,43],[13,43],[13,3]]}],"t":16},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[2.75,45],[2.75,85],[12.75,85],[12.75,45]]}],"t":28.0000011404634}],"ix":1}}],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-1.381,0],[0,0],[0,-1.381],[0,0],[1.381,0],[0,0],[0,1.381],[0,0]],"o":[[0,0],[1.381,0],[0,0],[0,1.381],[0,0],[-1.381,0],[0,0],[0,-1.381]],"v":[[-2.5,-20],[2.5,-20],[5,-17.5],[5,17.5],[2.5,20],[-2.5,20],[-5,17.5],[-5,-17.5]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[0.4745,0.0863,0.0863],"ix":3}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.4745,0.0863,0.0863],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[8,23],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":5},{"ty":4,"nm":"Bar 3 Outlines","sr":1,"st":12.00000048877,"op":162.000006598395,"ip":-1.00000004073083,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[8,18,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[72,113,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-1.381,0],[0,0],[0,-1.381],[0,0],[1.381,0],[0,0],[0,1.381],[0,0]],"o":[[0,0],[1.381,0],[0,0],[0,1.381],[0,0],[-1.381,0],[0,0],[0,-1.381]],"v":[[-2.5,-15],[2.5,-15],[5,-12.5],[5,12.5],[2.5,15],[-2.5,15],[-5,12.5],[-5,-12.5]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[0.5333,0.1294,0.102],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[8,18],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":6},{"ty":4,"nm":"Bar 3 Outlines 2","sr":1,"st":32.0000013033867,"op":182.000007413012,"ip":-1.00000004073083,"hd":false,"ddd":0,"bm":0,"hasMask":true,"ao":0,"ks":{"a":{"a":0,"k":[8,18,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[72,113,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"masksProperties":[{"nm":"Mask 1","inv":false,"mode":"a","x":{"a":0,"k":0,"ix":4},"o":{"a":0,"k":100,"ix":3},"pt":{"a":1,"k":[{"o":{"x":0.432,"y":0.402},"i":{"x":0.163,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[3,3],[3,33],[13,33],[13,3]]}],"t":22},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[2.75,35],[2.75,65],[12.75,65],[12.75,35]]}],"t":32.0000013033867}],"ix":1}}],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-1.381,0],[0,0],[0,-1.381],[0,0],[1.381,0],[0,0],[0,1.381],[0,0]],"o":[[0,0],[1.381,0],[0,0],[0,1.381],[0,0],[-1.381,0],[0,0],[0,-1.381]],"v":[[-2.5,-15],[2.5,-15],[5,-12.5],[5,12.5],[2.5,15],[-2.5,15],[-5,12.5],[-5,-12.5]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[0.3647,0.0824,0.0784],"ix":3}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.3647,0.0824,0.0784],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[8,18],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":7},{"ty":4,"nm":"Bar 4 Outlines","sr":1,"st":12.00000048877,"op":162.000006598395,"ip":-1.00000004073083,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[8,13,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[56,118,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-1.381,0],[0,0],[0,-1.381],[0,0],[1.381,0],[0,0],[0,1.381],[0,0]],"o":[[0,0],[1.381,0],[0,0],[0,1.381],[0,0],[-1.381,0],[0,0],[0,-1.381]],"v":[[-2.5,-10],[2.5,-10],[5,-7.5],[5,7.5],[2.5,10],[-2.5,10],[-5,7.5],[-5,-7.5]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":1,"lj":1,"ml":4,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"c":{"a":0,"k":[0.4627,0.1137,0.0824],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[8,13],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":8},{"ty":4,"nm":"Bar 4 Outlines 2","sr":1,"st":63.0000025660426,"op":213.000008675668,"ip":-1.00000004073083,"hd":false,"ddd":0,"bm":0,"hasMask":true,"ao":0,"ks":{"a":{"a":0,"k":[8,13,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[56,118,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"masksProperties":[{"nm":"Mask 1","inv":false,"mode":"i","x":{"a":0,"k":0,"ix":4},"o":{"a":0,"k":100,"ix":3},"pt":{"a":1,"k":[{"o":{"x":0.359,"y":0.354},"i":{"x":0.137,"y":1},"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[3,3],[3,23],[13,23],[13,3]]}],"t":27},{"s":[{"c":true,"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[3,25.25],[3,45.25],[13,45.25],[13,25.25]]}],"t":39.0000015885026}],"ix":1}}],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-1.381,0],[0,0],[0,-1.381],[0,0],[1.381,0],[0,0],[0,1.381],[0,0]],"o":[[0,0],[1.381,0],[0,0],[0,1.381],[0,0],[-1.381,0],[0,0],[0,-1.381]],"v":[[-2.5,-10],[2.5,-10],[5,-7.5],[5,7.5],[2.5,10],[-2.5,10],[-5,7.5],[-5,-7.5]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,0.5412,0.3961],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[8,13],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":9}]}]} \ No newline at end of file diff --git a/news-app/assets/font/Roboto-Bold.ttf b/news-app/assets/font/Roboto-Bold.ttf new file mode 100644 index 00000000..43da14d8 Binary files /dev/null and b/news-app/assets/font/Roboto-Bold.ttf differ diff --git a/news-app/assets/font/Roboto-Light.ttf b/news-app/assets/font/Roboto-Light.ttf new file mode 100644 index 00000000..e7307e72 Binary files /dev/null and b/news-app/assets/font/Roboto-Light.ttf differ diff --git a/news-app/assets/font/Roboto-Medium.ttf b/news-app/assets/font/Roboto-Medium.ttf new file mode 100644 index 00000000..ac0f908b Binary files /dev/null and b/news-app/assets/font/Roboto-Medium.ttf differ diff --git a/news-app/assets/font/Roboto-Regular.ttf b/news-app/assets/font/Roboto-Regular.ttf new file mode 100644 index 00000000..ddf4bfac Binary files /dev/null and b/news-app/assets/font/Roboto-Regular.ttf differ diff --git a/news-app/assets/images/live_news.png b/news-app/assets/images/live_news.png new file mode 100644 index 00000000..f4f2fe9c Binary files /dev/null and b/news-app/assets/images/live_news.png differ diff --git a/news-app/assets/images/live_news_dark.png b/news-app/assets/images/live_news_dark.png new file mode 100644 index 00000000..5f2736dd Binary files /dev/null and b/news-app/assets/images/live_news_dark.png differ diff --git a/news-app/assets/images/placeholder.png b/news-app/assets/images/placeholder.png new file mode 100644 index 00000000..2eb62c48 Binary files /dev/null and b/news-app/assets/images/placeholder.png differ diff --git a/news-app/assets/images/svgImage/apple_logo.svg b/news-app/assets/images/svgImage/apple_logo.svg new file mode 100644 index 00000000..34d10c7c --- /dev/null +++ b/news-app/assets/images/svgImage/apple_logo.svg @@ -0,0 +1,6 @@ + + + diff --git a/news-app/assets/images/svgImage/cameraIcon.svg b/news-app/assets/images/svgImage/cameraIcon.svg new file mode 100644 index 00000000..3cbe607f --- /dev/null +++ b/news-app/assets/images/svgImage/cameraIcon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/news-app/assets/images/svgImage/caribe_blanco.svg b/news-app/assets/images/svgImage/caribe_blanco.svg new file mode 100644 index 00000000..e3c157a7 --- /dev/null +++ b/news-app/assets/images/svgImage/caribe_blanco.svg @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/news-app/assets/images/svgImage/deactivatedNews.svg b/news-app/assets/images/svgImage/deactivatedNews.svg new file mode 100644 index 00000000..9face8bb --- /dev/null +++ b/news-app/assets/images/svgImage/deactivatedNews.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/news-app/assets/images/svgImage/deleteAccount.svg b/news-app/assets/images/svgImage/deleteAccount.svg new file mode 100644 index 00000000..2d964d93 --- /dev/null +++ b/news-app/assets/images/svgImage/deleteAccount.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/news-app/assets/images/svgImage/deleteMyNews.svg b/news-app/assets/images/svgImage/deleteMyNews.svg new file mode 100644 index 00000000..c8e8bcb0 --- /dev/null +++ b/news-app/assets/images/svgImage/deleteMyNews.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/news-app/assets/images/svgImage/delete_icon.svg b/news-app/assets/images/svgImage/delete_icon.svg new file mode 100644 index 00000000..98c88c11 --- /dev/null +++ b/news-app/assets/images/svgImage/delete_icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/news-app/assets/images/svgImage/editMyNews.svg b/news-app/assets/images/svgImage/editMyNews.svg new file mode 100644 index 00000000..fd2eb589 --- /dev/null +++ b/news-app/assets/images/svgImage/editMyNews.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/news-app/assets/images/svgImage/expiredNews.svg b/news-app/assets/images/svgImage/expiredNews.svg new file mode 100644 index 00000000..d8626e0e --- /dev/null +++ b/news-app/assets/images/svgImage/expiredNews.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/news-app/assets/images/svgImage/facebook.svg b/news-app/assets/images/svgImage/facebook.svg new file mode 100644 index 00000000..1618cd03 --- /dev/null +++ b/news-app/assets/images/svgImage/facebook.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/news-app/assets/images/svgImage/facebook_button.svg b/news-app/assets/images/svgImage/facebook_button.svg new file mode 100644 index 00000000..6adffefd --- /dev/null +++ b/news-app/assets/images/svgImage/facebook_button.svg @@ -0,0 +1,3 @@ + + + diff --git a/news-app/assets/images/svgImage/filter.svg b/news-app/assets/images/svgImage/filter.svg new file mode 100644 index 00000000..50fcf7cf --- /dev/null +++ b/news-app/assets/images/svgImage/filter.svg @@ -0,0 +1,3 @@ + + + diff --git a/news-app/assets/images/svgImage/flag_icon.svg b/news-app/assets/images/svgImage/flag_icon.svg new file mode 100644 index 00000000..5d788ab7 --- /dev/null +++ b/news-app/assets/images/svgImage/flag_icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/news-app/assets/images/svgImage/forgot.svg b/news-app/assets/images/svgImage/forgot.svg new file mode 100644 index 00000000..979581e0 --- /dev/null +++ b/news-app/assets/images/svgImage/forgot.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/news-app/assets/images/svgImage/gallaryIcon.svg b/news-app/assets/images/svgImage/gallaryIcon.svg new file mode 100644 index 00000000..2d491918 --- /dev/null +++ b/news-app/assets/images/svgImage/gallaryIcon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/news-app/assets/images/svgImage/google_button.svg b/news-app/assets/images/svgImage/google_button.svg new file mode 100644 index 00000000..59d1c254 --- /dev/null +++ b/news-app/assets/images/svgImage/google_button.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/news-app/assets/images/svgImage/intro_icon.svg b/news-app/assets/images/svgImage/intro_icon.svg new file mode 100644 index 00000000..6a6b5071 --- /dev/null +++ b/news-app/assets/images/svgImage/intro_icon.svg @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/news-app/assets/images/svgImage/linkedin.svg b/news-app/assets/images/svgImage/linkedin.svg new file mode 100644 index 00000000..902724e5 --- /dev/null +++ b/news-app/assets/images/svgImage/linkedin.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/news-app/assets/images/svgImage/live_news.svg b/news-app/assets/images/svgImage/live_news.svg new file mode 100644 index 00000000..973009ce --- /dev/null +++ b/news-app/assets/images/svgImage/live_news.svg @@ -0,0 +1,11 @@ + + + + diff --git a/news-app/assets/images/svgImage/live_news_dark.svg b/news-app/assets/images/svgImage/live_news_dark.svg new file mode 100644 index 00000000..ae1ceedb --- /dev/null +++ b/news-app/assets/images/svgImage/live_news_dark.svg @@ -0,0 +1,11 @@ + + + + diff --git a/news-app/assets/images/svgImage/logo_caribe.svg b/news-app/assets/images/svgImage/logo_caribe.svg new file mode 100644 index 00000000..6c0b69a7 --- /dev/null +++ b/news-app/assets/images/svgImage/logo_caribe.svg @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/news-app/assets/images/svgImage/logout.svg b/news-app/assets/images/svgImage/logout.svg new file mode 100644 index 00000000..b60d8621 --- /dev/null +++ b/news-app/assets/images/svgImage/logout.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/news-app/assets/images/svgImage/maintenance.svg b/news-app/assets/images/svgImage/maintenance.svg new file mode 100644 index 00000000..3a4474a3 --- /dev/null +++ b/news-app/assets/images/svgImage/maintenance.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/news-app/assets/images/svgImage/news.svg b/news-app/assets/images/svgImage/news.svg new file mode 100644 index 00000000..c945a54d --- /dev/null +++ b/news-app/assets/images/svgImage/news.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/news-app/assets/images/svgImage/onboarding1.svg b/news-app/assets/images/svgImage/onboarding1.svg new file mode 100644 index 00000000..d715c902 --- /dev/null +++ b/news-app/assets/images/svgImage/onboarding1.svg @@ -0,0 +1,262 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/news-app/assets/images/svgImage/onboarding2.svg b/news-app/assets/images/svgImage/onboarding2.svg new file mode 100644 index 00000000..67670ee9 --- /dev/null +++ b/news-app/assets/images/svgImage/onboarding2.svg @@ -0,0 +1,948 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/news-app/assets/images/svgImage/onboarding3.svg b/news-app/assets/images/svgImage/onboarding3.svg new file mode 100644 index 00000000..3ea81f5d --- /dev/null +++ b/news-app/assets/images/svgImage/onboarding3.svg @@ -0,0 +1,895 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/news-app/assets/images/svgImage/phone_button.svg b/news-app/assets/images/svgImage/phone_button.svg new file mode 100644 index 00000000..4f568022 --- /dev/null +++ b/news-app/assets/images/svgImage/phone_button.svg @@ -0,0 +1,3 @@ + + + diff --git a/news-app/assets/images/svgImage/placeholder_video.svg b/news-app/assets/images/svgImage/placeholder_video.svg new file mode 100644 index 00000000..df4b14e3 --- /dev/null +++ b/news-app/assets/images/svgImage/placeholder_video.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/news-app/assets/images/svgImage/rss_feed.svg b/news-app/assets/images/svgImage/rss_feed.svg new file mode 100644 index 00000000..a9261d31 --- /dev/null +++ b/news-app/assets/images/svgImage/rss_feed.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/news-app/assets/images/svgImage/searchbar_arrow.svg b/news-app/assets/images/svgImage/searchbar_arrow.svg new file mode 100644 index 00000000..13e706a2 --- /dev/null +++ b/news-app/assets/images/svgImage/searchbar_arrow.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/news-app/assets/images/svgImage/splash_icon.svg b/news-app/assets/images/svgImage/splash_icon.svg new file mode 100644 index 00000000..99ea7b16 --- /dev/null +++ b/news-app/assets/images/svgImage/splash_icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/news-app/assets/images/svgImage/telegram.svg b/news-app/assets/images/svgImage/telegram.svg new file mode 100644 index 00000000..f47bb43e --- /dev/null +++ b/news-app/assets/images/svgImage/telegram.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/news-app/assets/images/svgImage/whatsapp.svg b/news-app/assets/images/svgImage/whatsapp.svg new file mode 100644 index 00000000..e945a88e --- /dev/null +++ b/news-app/assets/images/svgImage/whatsapp.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/news-app/assets/images/svgImage/wrteam_logo.svg b/news-app/assets/images/svgImage/wrteam_logo.svg new file mode 100644 index 00000000..25a598ba --- /dev/null +++ b/news-app/assets/images/svgImage/wrteam_logo.svg @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/news-app/assets/languages/jsonData.json b/news-app/assets/languages/jsonData.json new file mode 100644 index 00000000..a417275a --- /dev/null +++ b/news-app/assets/languages/jsonData.json @@ -0,0 +1,244 @@ +{ + "somethingMSg": "Something went wrong. Please try again after some time", + "bookmarkLbl": "Bookmarks", + "loginLbl": "Login", + "welTitle1": "Always Up-to-Date", + "welTitle2": "Bookmark & Share", + "welTitle3": "New Categories", + "welDes1": "Receive notifications for the most recent news updates and many more.", + "welDes2": "Save and easily share news with your friends using our intuitive news app feature.", + "welDes3": "Enjoy expertly tailored news, crafted exclusively for your interests.", + "nameLbl": "Name", + "emailLbl": "Email", + "passLbl": "Password", + "confpassLbl": "Confirm Password", + "priPolicy": "Privacy Policy", + "andLbl": " and ", + "termLbl": "Terms of Service", + "forgotPassLbl": "Forgot Password ?", + "internetmsg": "Internet Connection not available", + "loginMsg": "Login Successfully", + "loginNowLbl": "Login Now", + "logoutLbl": "Logout", + "cancelBtn": "Cancel", + "noNews": "News Not Available", + "exitWR": "Double tap back button to exit", + "shareLbl": "Share", + "deactiveMsg": "You are deactivated by admin", + "bookmarkNotAvail": "Bookmarks Not Available", + "notiNotAvail": "Notifications Not Available", + "notificationLbl": "Notifications", + "logoutTxt": "Are you sure you want to Logout?", + "yesLbl": "Yes", + "noLbl": "No", + "frgtPassHead": "Enter the email address associated with your account", + "forgotPassSub": "We will email you a link to reset your password", + "submitBtn": "Submit", + "verifyEmailMsg": "Please first verify your email address!!!", + "passReset": "Password reset link has been sent to your mail", + "profileUpdateMsg": "Profile Data Updated Successfully", + "bookmarkLogin": "Please Login to Access Your Bookmarks !!", + "preferenceSave": "Your preference saved!!", + "managePreferences": "Manage Preferences", + "loginReqMsg": "Login Required...", + "firstFillData": "Please First Fill Data...!", + "deleteTxt": "Delete", + "reportTxt": "Report", + "nameRequired": "Name is Required", + "nameLength": "Name should be atleast 2 character long", + "emailRequired": "email address is Required", + "emailValid": "Please enter a valid email Address!", + "pwdRequired": "Password is Required", + "confPassRequired": "Confirm Password is Required", + "confPassNotMatch": "Confirm Password not match", + "photoLibLbl": "Photo Library", + "cameraLbl": "Camera", + "verifSentMail": "Verification email sent to ", + "cancelLogin": "Login cancelled by the user.", + "loginTxt": "Log In", + "loginBtn": "Login", + "signupBtn": "Sign Up", + "otpVerifyLbl": "OTP Verification", + "enterMblLbl": "Enter Your Mobile Number", + "receiveDigitLbl": "You'll Receive 6 digit code for phone number verification", + "reqOtpLbl": "Request OTP", + "otpSentLbl": "OTP has been sent to ", + "resendCodeLbl": "Resend Code in", + "mobileLbl": "Mobile", + "darkModeLbl": "Dark Mode", + "changeLang": "Change Language", + "rateUs": "Rate Us", + "shareApp": "Share App", + "weatherLbl": "Weather Forecast", + "categoryLbl": "Categories", + "allLbl": "All", + "comLbl": "Comment ", + "saveLbl": "Save", + "txtSizeLbl": "Text Size", + "speakLoudLbl": "Speak Loud", + "likeLbl": "likes", + "comsLbl": "Comments", + "shareThoghtLbl": "Share Your Thoughts.", + "repliesLbl": "Replies", + "publicReply": "Add a public reply...", + "personalLbl": "Personal", + "newsLbl": "News", + "plzLbl": "Please", + "fastTrendNewsLbl": "Porque Mereces Verdaderas Respuestas! ", + "enterOtpTxt": "Please Enter OTP", + "otpError": "Error validating OTP, try again", + "otpMsg": "OTP verified successfully", + "resendLbl": "Resend OTP", + "otpTimeoutLbl": "Otp Retrieval Timeout!!!", + "mblRequired": "Mobile number is Required", + "mblValid": "Please enter a valid mobile number!", + "codeSent": "Code Sent Successfully!!!", + "relatedNews": "You might also like", + "optSel": "Please Select One Option!!!", + "madeBy": "Made by", + "skip": "Skip", + "nxt": "Next", + "signInTab": "Sign In", + "agreeTermPolicyLbl": "By Logging In, you agree to our", + "addTCFirst": "Please Ask Admin to Add Privacy Policy & Terms and Conditions first !!", + "orLbl": "or Log In with", + "signupDescr": "Create\nan Account", + "firstAccLbl": "First to access", + "allFunLbl": "all Functions", + "chooseLanLbl": "Select Language", + "videosLbl": "Videos", + "search": "Search", + "searchHomeNews": "Search News, Categories, etc.", + "viewMore": "View More", + "viewFullCoverage": "View full Coverage", + "updateName": "Update your Name", + "loginDescr": "Let's Sign \nYou In", + "logoutAcc": "Logout Account", + "deleteAcc": "Delete Account", + "deleteAlertTitle": "Re-Login", + "deleteRelogin": "To Delete your Account, You need to Login again.\nAfter that you will be able to Delete your Account.", + "deleteConfirm": "Are you sure?\nDo You Really Want to Delete Your Account?", + "pwdLength": "Password should be more than 6 character long", + "userNotFound": "No user found for given email.", + "wrongPassword": "Wrong password provided for that user.", + "weakPassword": "The password provided is too weak.", + "emailAlreadyInUse": "The account already exists for that email.", + "invalidPhoneNumber": "The provided phone number is not valid.", + "invalidVerificationCode": "The sms verification code used to create the phone auth credential is invalid.", + "ago": "ago", + "years": "years", + "months": "months", + "minutes": "minutes", + "seconds": "seconds", + "hours": "hours", + "days": "days", + "justNow": "just now", + "about": "about", + "liveVideosLbl": "Live Videos", + "stdPostLbl": "Standard Post", + "videoYoutubeLbl": "Video (Youtube)", + "videoOtherUrlLbl": "Video (Other Url)", + "videoUploadLbl": "Video (Upload)", + "createNewsLbl": "Create News", + "step1Of2Lbl": "Step 1 of 2", + "catLbl": "Category", + "plzSelCatLbl": "Please select category", + "subcatLbl": "SubCategory", + "contentTypeLbl": "Content Type", + "uploadVideoLbl": "Upload Video", + "youtubeUrlLbl": "Youtube Url", + "otherUrlLbl": "Other Url", + "selContentTypeLbl": "Select Content Type", + "titleLbl": "Title", + "tagLbl": "Tag", + "showTilledDate": "Show Till Date", + "uploadMainImageLbl": "Upload Main Image", + "uploadOtherImageLbl": "Upload Other Image", + "plzUploadVideoLbl": "Please upload video!!!", + "plzAddMainImageLbl": "Please add main image!!!", + "selTagLbl": "Select Tag", + "selSubCatLbl": "Select Sub Category", + "selCatLbl": "Select Category", + "editNewsLbl": "Edit News", + "doYouReallyNewsLbl": "Do You Really Want to Delete this News?", + "delNewsLbl": "Delete News", + "newsTitleReqLbl": "News title is required!!!", + "plzAddValidTitleLbl": "Please add valid news title!!!", + "urlReqLbl": "Url is required!!!", + "plzValidUrlLbl": "Please add valid url!!!", + "manageNewsLbl": "Manage News", + "step2of2Lbl": "Step 2 of 2", + "descLbl": "Description", + "RetryLbl": "Retry", + "previewLbl": "Preview", + "sponsoredLbl": "Sponsored", + "searchForLbl": "Search Result for", + "readLessLbl": "Read less", + "readMoreLbl": "Read more", + "myProfile": "My Profile", + "editProfile": "Edit Profile", + "noComments": "Be the First One to Comment !!!", + "minute": "minute", + "read": "read", + "selLocationLbl": "Select Location", + "metaKeywordLbl": "Meta Keyword", + "metaTitleLbl": "Meta Title", + "metaDescriptionLbl": "Meta Description", + "slugLbl": "Slug", + "metaTitleWarningLbl": "Meta Title length should not exceed 60 characters.", + "metaDescriptionWarningLbl": "Meta Description length should between 50 to 160 characters.", + "metaKeywordWarningLbl": "Meta Keywords are not more than 10 keyword phrases & should be comma separated.", + "slugWarningLbl": "Slug only accept lowercase letters, numbers, and hyphens. No spaces or special characters allowed.", + "metaTitleRequired": "Meta title is Required", + "metaDescriptionRequired": "Meta Description is Required", + "metaKeywordRequired": "Meta Keyword is Required", + "slugRequired": "Slug is Required", + "slugValid": "Please enter valid Slug!", + "slugUsedAlready": "This slug is already in use. Please add any other slug.", + "maintenanceMessageLbl": "We're Under Maintenance\nPlease try again later.", + "notificationLogin": "Please Login to view Your Notifications !!", + "publishDate": "Publish Date", + "dateConfirmation": "Please change Show till date , as it can't be set before Publish date", + "expired": "Expired", + "deactivated": "Deactivated", + "clearFilter": "Clear filter", + "FilterBy": "Filter by", + "rssFeed": "RSS Feed", + "profile": "Profile", + "homeLbl": "Home", + "replyLbl": "Reply", + "disabledCommentsMsg": "Comments are disabled for this News by admin", + "last": "Last", + "today": "Today", + "clear": "Clear", + "apply": "Apply", + "date": "Date", + "continueWith": "Continue with", + "google": "Google", + "apple": "Apple", + "fb": "Facebook", + "didntGetCode": "Didn't get Code?", + "viewsLbl": "Views", + "recentVidLbl": "Recent News Videos", + "forceUpdateTitleLbl": "Update Required", + "newVersionAvailableTitleLbl": "New Version Available !!!", + "newVersionAvailableDescLbl": "Would you like to update now ?", + "forceUpdateDescLbl": "A new version of this app is available with important improvements and features. To continue using the app, please update to the latest version.", + "exitLbl": "Exit", + "newsCreatedSuccessfully": "Your news has been created successfully! It will be visible to others after admin approval.", + "summarizedDescription": "Summarized Description", + "clickSummarizeDescription": "Click 'Summarize Description' to generate a summary of your content", + "enterDescriptionFirst": "Add description First to generate Summarized Description", + "authorReviewPendingLbl": "Pending Review", + "becomeAuthorLbl": "Become an Author", + "authorLbl": "Author", + "saveAsDraftLbl": "Save As Draft", + "manageNewsAllLbl": "All News", + "manageNewsDraftLbl": "Draft News", + "publishBtnLbl": "Publish", + "addYourBioHintLbl": "Add your bio", + "addLinkHereHintLbl": "Add {type} Link here", + "noteForAuthorLbl": "You can add social media links & Bio only if you are the author.", + "socialMediaLinksLbl": "Social media links", + "followLbl": "Follow :" +} \ No newline at end of file diff --git a/news-app/devtools_options.yaml b/news-app/devtools_options.yaml new file mode 100644 index 00000000..fa0b357c --- /dev/null +++ b/news-app/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/news-app/ios/Flutter/AppFrameworkInfo.plist b/news-app/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000..c95f1736 --- /dev/null +++ b/news-app/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 + 15.0 + + diff --git a/news-app/ios/Flutter/Debug.xcconfig b/news-app/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000..ec97fc6f --- /dev/null +++ b/news-app/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/news-app/ios/Flutter/Release.xcconfig b/news-app/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000..aa0ffe9d --- /dev/null +++ b/news-app/ios/Flutter/Release.xcconfig @@ -0,0 +1,3 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +//#include "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig" +#include "Generated.xcconfig" diff --git a/news-app/ios/Podfile b/news-app/ios/Podfile new file mode 100644 index 00000000..5c0aae9f --- /dev/null +++ b/news-app/ios/Podfile @@ -0,0 +1,46 @@ +# Uncomment this line to define a global platform for your project + platform :ios, '15.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0' + end + end +end diff --git a/news-app/ios/Runner.xcodeproj/project.pbxproj b/news-app/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..f7d9cc32 --- /dev/null +++ b/news-app/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,624 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + 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 */; }; + 6D5AA4DC1D9BE0D29BBC3D47 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8406A87EDAB3F42C9E9C8625 /* Pods_Runner.framework */; }; + 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 */; }; + C64F89812BEA00CF00945F40 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = C64F89802BEA00CF00945F40 /* PrivacyInfo.xcprivacy */; }; + C6B156C729ED4959004F7E4D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C6B156C629ED4959004F7E4D /* GoogleService-Info.plist */; }; +/* 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 = ""; }; + 3EB85B3888093677A730AD02 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 62A0D45B537B579D78E7A373 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; 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 = ""; }; + 8406A87EDAB3F42C9E9C8625 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 88E5CF1A7479A9215E46F87B /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.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 = ""; }; + C64F89802BEA00CF00945F40 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + C6B156C629ED4959004F7E4D /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + C739818628E4631E00744836 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6D5AA4DC1D9BE0D29BBC3D47 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 541E6B33177097DE8D31256D /* Pods */ = { + isa = PBXGroup; + children = ( + 62A0D45B537B579D78E7A373 /* Pods-Runner.debug.xcconfig */, + 88E5CF1A7479A9215E46F87B /* Pods-Runner.release.xcconfig */, + 3EB85B3888093677A730AD02 /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 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 */, + 541E6B33177097DE8D31256D /* Pods */, + CC2378898FAFB2F8CCB774D0 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + C64F89802BEA00CF00945F40 /* PrivacyInfo.xcprivacy */, + C6B156C629ED4959004F7E4D /* GoogleService-Info.plist */, + C739818628E4631E00744836 /* Runner.entitlements */, + 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 = ""; + }; + CC2378898FAFB2F8CCB774D0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 8406A87EDAB3F42C9E9C8625 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 11FA84E1B6E74DCAB7043F8B /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 66D5C516A2D49648D98696C5 /* [CP] Embed Pods Frameworks */, + 7452FB0F6FB0AC1495261F6F /* [CP] Copy Pods Resources */, + ); + 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 = 1510; + 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 = ( + C64F89812BEA00CF00945F40 /* PrivacyInfo.xcprivacy in Resources */, + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + C6B156C729ED4959004F7E4D /* GoogleService-Info.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 11FA84E1B6E74DCAB7043F8B /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 66D5C516A2D49648D98696C5 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 7452FB0F6FB0AC1495261F6F /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + 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 = 15.6; + 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_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 32; + DEVELOPMENT_TEAM = 89C47N4UTZ; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "elCaribe"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + PRODUCT_BUNDLE_IDENTIFIER = YOUR_PACKAGE_NAME_HERE; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + 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 = 15.6; + 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 = 15.6; + 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_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 32; + DEVELOPMENT_TEAM = 89C47N4UTZ; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "elCaribe"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + PRODUCT_BUNDLE_IDENTIFIER = YOUR_PACKAGE_NAME_HERE; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 32; + DEVELOPMENT_TEAM = 89C47N4UTZ; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "elCaribe"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + PRODUCT_BUNDLE_IDENTIFIER = YOUR_PACKAGE_NAME_HERE; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + 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 */; +} diff --git a/news-app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/news-app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/news-app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/news-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/news-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/news-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/news-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/news-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/news-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/news-app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/news-app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..9c12df59 --- /dev/null +++ b/news-app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/news-app/ios/Runner.xcworkspace/contents.xcworkspacedata b/news-app/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..21a3cc14 --- /dev/null +++ b/news-app/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/news-app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/news-app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/news-app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/news-app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/news-app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/news-app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/news-app/ios/Runner/AppDelegate.swift b/news-app/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000..d97a1839 --- /dev/null +++ b/news-app/ios/Runner/AppDelegate.swift @@ -0,0 +1,71 @@ +import UIKit +import Flutter +import Firebase +import AppTrackingTransparency +import GoogleMobileAds + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + FirebaseApp.configure() + GeneratedPluginRegistrant.register(with: self) + if #available(iOS 10.0, *) { + UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate + } + + // Disables Publisher first-party ID + MobileAds.shared.requestConfiguration.setPublisherFirstPartyIDEnabled(false) + + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + override func application( + _ application: UIApplication, + open url: URL, + options: [UIApplication.OpenURLOptionsKey : Any] = [:] + ) -> Bool { + let urlString = url.absoluteString + return true + } + + override func applicationDidBecomeActive(_ application: UIApplication) { + if #available(iOS 15.0, *) { + ATTrackingManager.requestTrackingAuthorization(completionHandler: { status in + // Tracking authorization completed. Start loading ads here. + // loadAd + }) + } + } + } +class AppLinks { + + var window: UIWindow? + + func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { + return handleDeepLink(url: url) + } + + func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + if userActivity.activityType == NSUserActivityTypeBrowsingWeb, + let url = userActivity.webpageURL { + return handleDeepLink(url: url) + } + return false + } + + private func handleDeepLink(url: URL) -> Bool { + guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { + return false + } + + if let urlPattern = components.path.split(separator: "/").last { + return true + } + + return false + } + +} diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..53611299 --- /dev/null +++ b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "filename" : "Icon-App-20x20@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "Icon-App-20x20@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "Icon-App-29x29@1x.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-29x29@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-29x29@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-40x40@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "Icon-App-40x40@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "Icon-App-60x60@2x.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "Icon-App-60x60@3x.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "Icon-App-20x20@1x.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "Icon-App-20x20@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "Icon-App-29x29@1x.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-29x29@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "Icon-App-40x40@1x.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "Icon-App-40x40@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "Icon-App-76x76@1x.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "Icon-App-76x76@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "Icon-App-83.5x83.5@2x.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "Icon-App-1024x1024@1x.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 00000000..f8454df2 Binary files /dev/null and b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 00000000..dd91ab16 Binary files /dev/null and b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 00000000..07e6271d Binary files /dev/null and b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 00000000..eb4993b7 Binary files /dev/null and b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 00000000..060886bc Binary files /dev/null and b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 00000000..24c21081 Binary files /dev/null and b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 00000000..fef339a9 Binary files /dev/null and b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 00000000..1dde7284 Binary files /dev/null and b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 00000000..2031b942 Binary files /dev/null and b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 00000000..0d685eda Binary files /dev/null and b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 00000000..e5d6d49c Binary files /dev/null and b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 00000000..9993fd19 Binary files /dev/null and b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 00000000..3d9688b1 Binary files /dev/null and b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 00000000..043543ad Binary files /dev/null and b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 00000000..29fabb69 Binary files /dev/null and b/news-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 00000000..0bedcf2f --- /dev/null +++ b/news-app/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/news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/news-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000..89c2725b --- /dev/null +++ b/news-app/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/news-app/ios/Runner/Base.lproj/LaunchScreen.storyboard b/news-app/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..f2e259c7 --- /dev/null +++ b/news-app/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/news-app/ios/Runner/Base.lproj/Main.storyboard b/news-app/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f3c28516 --- /dev/null +++ b/news-app/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/news-app/ios/Runner/Info.plist b/news-app/ios/Runner/Info.plist new file mode 100644 index 00000000..f8824ccb --- /dev/null +++ b/news-app/ios/Runner/Info.plist @@ -0,0 +1,138 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + elCaribe + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + elCaribe + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + fb3187011931582004 + + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + com.googleusercontent.apps.1085975883181-q56lvc3nb2j3lc2rpkl9sv0ve6sf12hn + + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + news + + + + CFBundleURLName + YOUR_PACKAGE_NAME_HERE + CFBundleURLSchemes + + news + + + + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + FacebookAppID + 3187011931582004 + FacebookClientToken + c5fcb2473b8557c9ff049dd807db8240 + FacebookDisplayName + news + GADApplicationIdentifier + ca-app-pub-3940256099942544~1458002511 + LSApplicationQueriesSchemes + + https + http + + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSAllowsArbitraryLoadsInWebContent + + + NSCameraUsageDescription + Allow Camera access to upload photo from your device to set Profile Picture & add images in News + NSLocationAlwaysAndWhenInUseUsageDescription + Your location is required to get current weather of your location + NSLocationAlwaysUsageDescription + We kindly request your location to provide you with relevant local news. + NSLocationWhenInUseUsageDescription + To ensure personalized news, please share your location with us. + NSMicrophoneUsageDescription + Allow microphone access to listen to news content + NSPhotoLibraryUsageDescription + Allow library access to upload photo from your device to set Profile Picture & add images in News + NSUserTrackingUsageDescription + This identifier will be used to deliver personalized ads to you. + SKAdNetworkItems + + + SKAdNetworkIdentifier + cstr6suwn9.skadnetwork + + + UIApplicationSupportsIndirectInputEvents + + UIBackgroundModes + + fetch + remote-notification + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + UIViewControllerBasedStatusBarAppearance + + io.flutter.embedded_views_preview + + setAdvertiserTrackingEnabled + + ITSAppUsesNonExemptEncryption + + + + diff --git a/news-app/ios/Runner/PrivacyInfo.xcprivacy b/news-app/ios/Runner/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..1f8b2799 --- /dev/null +++ b/news-app/ios/Runner/PrivacyInfo.xcprivacy @@ -0,0 +1,91 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypePreciseLocation + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypePhotosorVideos + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypePhoneNumber + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising + App Functionality +App Functionality +App Functionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeEmailAddress + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeName + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + C56D.1 + + + + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + + + + diff --git a/news-app/ios/Runner/Runner-Bridging-Header.h b/news-app/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..4a45a93f --- /dev/null +++ b/news-app/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1,2 @@ +#import "GeneratedPluginRegistrant.h" + diff --git a/news-app/ios/Runner/Runner.entitlements b/news-app/ios/Runner/Runner.entitlements new file mode 100644 index 00000000..08046e6f --- /dev/null +++ b/news-app/ios/Runner/Runner.entitlements @@ -0,0 +1,18 @@ + + + + + aps-environment + development + com.apple.developer.applesignin + + Default + + com.apple.developer.associated-domains + + applinks:enter_your_website_url_here + + com.apple.developer.usernotifications.time-sensitive + + + diff --git a/news-app/lib/app/app.dart b/news-app/lib/app/app.dart new file mode 100644 index 00000000..35bd8ca3 --- /dev/null +++ b/news-app/lib/app/app.dart @@ -0,0 +1,258 @@ +import 'dart:io'; +import 'dart:ui'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; +import 'package:hive_flutter/adapters.dart'; +import 'package:intl/date_symbol_data_local.dart'; +import 'package:intl/intl.dart' as intl; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/AddNewsCubit.dart'; +import 'package:news/cubits/Auth/deleteUserCubit.dart'; +import 'package:news/cubits/Auth/registerTokenCubit.dart'; +import 'package:news/cubits/Auth/updateUserCubit.dart'; +import 'package:news/cubits/Author/authorCubit.dart'; +import 'package:news/cubits/Author/authorNewsCubit.dart'; +import 'package:news/cubits/Bookmark/UpdateBookmarkCubit.dart'; +import 'package:news/cubits/Bookmark/bookmarkCubit.dart'; +import 'package:news/cubits/ConnectivityCubit.dart'; +import 'package:news/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart'; +import 'package:news/cubits/LikeAndDislikeNews/updateLikeAndDislikeCubit.dart'; +import 'package:news/cubits/NewsComment/deleteCommentCubit.dart'; +import 'package:news/cubits/NewsComment/flagCommentCubit.dart'; +import 'package:news/cubits/NewsComment/likeAndDislikeCommCubit.dart'; +import 'package:news/cubits/NewsComment/setCommentCubit.dart'; +import 'package:news/cubits/UserPreferences/setUserPreferenceCatCubit.dart'; +import 'package:news/cubits/adSpacesNewsDetailsCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/cubits/breakingNewsCubit.dart'; +import 'package:news/cubits/commentNewsCubit.dart'; +import 'package:news/cubits/deleteImageId.dart'; +import 'package:news/cubits/deleteUserNewsCubit.dart'; +import 'package:news/cubits/generalNewsCubit.dart'; +import 'package:news/cubits/getSurveyAnswerCubit.dart'; +import 'package:news/cubits/getUserDataByIdCubit.dart'; +import 'package:news/cubits/getUserNewsCubit.dart'; +import 'package:news/cubits/languageCubit.dart'; +import 'package:news/cubits/locationCityCubit.dart'; +import 'package:news/cubits/privacyTermsCubit.dart'; +import 'package:news/cubits/relatedNewsCubit.dart'; +import 'package:news/cubits/rssFeedCubit.dart'; +import 'package:news/cubits/sectionByIdCubit.dart'; +import 'package:news/cubits/setNewsViewsCubit.dart'; +import 'package:news/cubits/setSurveyAnswerCubit.dart'; +import 'package:news/cubits/settingCubit.dart'; +import 'package:news/cubits/Auth/socialSignUpCubit.dart'; +import 'package:news/cubits/UserPreferences/userByCategoryCubit.dart'; +import 'package:news/cubits/slugCheckCubit.dart'; +import 'package:news/cubits/slugNewsCubit.dart'; +import 'package:news/cubits/tagCubit.dart'; +import 'package:news/cubits/tagNewsCubit.dart'; +import 'package:news/cubits/NewsByIdCubit.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; +import 'package:news/cubits/categoryCubit.dart'; +import 'package:news/cubits/featureSectionCubit.dart'; +import 'package:news/cubits/languageJsonCubit.dart'; +import 'package:news/cubits/liveStreamCubit.dart'; +import 'package:news/cubits/otherPagesCubit.dart'; +import 'package:news/cubits/subCategoryCubit.dart'; +import 'package:news/cubits/surveyQuestionCubit.dart'; +import 'package:news/cubits/themeCubit.dart'; +import 'package:news/cubits/updateBottomsheetContentCubit.dart'; +import 'package:news/cubits/videosCubit.dart'; +import 'package:news/cubits/weatherCubit.dart'; +import 'package:news/cubits/GetUserDraftedNewsCubit.dart'; +import 'package:news/data/repositories/AddNews/addNewsRepository.dart'; +import 'package:news/data/repositories/Auth/authRepository.dart'; +import 'package:news/data/repositories/Bookmark/bookmarkRepository.dart'; +import 'package:news/data/repositories/BreakingNews/breakNewsRepository.dart'; +import 'package:news/data/repositories/CommentNews/commNewsRepository.dart'; +import 'package:news/data/repositories/DeleteImageId/deleteImageRepository.dart'; +import 'package:news/data/repositories/DeleteUserNews/deleteUserNewsRepository.dart'; +import 'package:news/data/repositories/GetSurveyAnswer/getSurveyAnsRepository.dart'; +import 'package:news/data/repositories/GetUserById/getUserByIdRepository.dart'; +import 'package:news/data/repositories/GetUserNews/getUserNewsRepository.dart'; +import 'package:news/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsRepository.dart'; +import 'package:news/data/repositories/NewsComment/DeleteComment/deleteCommRepository.dart'; +import 'package:news/data/repositories/NewsComment/FlagComment/flagCommRepository.dart'; +import 'package:news/data/repositories/NewsComment/LikeAndDislikeComment/likeAndDislikeCommRepository.dart'; +import 'package:news/data/repositories/NewsComment/SetComment/setComRepository.dart'; +import 'package:news/data/repositories/RelatedNews/relatedNewsRepository.dart'; +import 'package:news/data/repositories/SectionById/sectionByIdRepository.dart'; +import 'package:news/data/repositories/SetNewsViews/setNewsViewsRepository.dart'; +import 'package:news/data/repositories/SetSurveyAnswer/setSurveyAnsRepository.dart'; +import 'package:news/data/repositories/SetUserPreferenceCat/setUserPrefCatRepository.dart'; +import 'package:news/data/repositories/SurveyQuestion/surveyQueRepository.dart'; +import 'package:news/data/repositories/Tag/tagRepository.dart'; +import 'package:news/data/repositories/TagNews/tagNewsRepository.dart'; +import 'package:news/data/repositories/UserByCategory/userByCatRepository.dart'; +import 'package:news/data/repositories/language/languageRepository.dart'; +import 'package:news/data/repositories/Settings/settingRepository.dart'; +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; +import 'package:news/data/repositories/AppSystemSetting/systemRepository.dart'; +import 'package:news/data/repositories/Category/categoryRepository.dart'; +import 'package:news/data/repositories/FeatureSection/sectionRepository.dart'; +import 'package:news/data/repositories/LanguageJson/languageJsonRepository.dart'; +import 'package:news/data/repositories/LiveStream/liveStreamRepository.dart'; +import 'package:news/data/repositories/NewsById/NewsByIdRepository.dart'; +import 'package:news/data/repositories/OtherPages/otherPagesRepository.dart'; +import 'package:news/data/repositories/SubCategory/subCatRepository.dart'; +import 'package:news/data/repositories/Videos/videosRepository.dart'; +import 'package:news/ui/screens/PushNotificationService.dart'; +import 'package:news/ui/styles/appTheme.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/hiveBoxKeys.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +late PackageInfo packageInfo; + +Future initializeApp() async { + WidgetsFlutterBinding.ensureInitialized(); + HttpOverrides.global = MyHttpOverrides(); + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); + + MobileAds.instance.initialize(); + + packageInfo = await PackageInfo.fromPlatform(); + + await Firebase.initializeApp(); + + await Hive.initFlutter(); + + await Hive.openBox(authBoxKey); + + await Hive.openBox(settingsBoxKey); + + await Hive.openBox(locationCityBoxKey); + await Hive.openBox(videoPreferenceKey); + + runApp(MultiBlocProvider(providers: [ + BlocProvider(create: (_) => ConnectivityCubit(ConnectivityService())), + BlocProvider(create: (context) => AppConfigurationCubit(SystemRepository())), + BlocProvider(create: (_) => SettingsCubit(SettingsRepository())), + BlocProvider(create: (_) => AppLocalizationCubit(SettingsLocalDataRepository())), + BlocProvider(create: (_) => ThemeCubit(SettingsLocalDataRepository())), + BlocProvider(create: (_) => LanguageJsonCubit(LanguageJsonRepository())), + BlocProvider(create: (context) => LanguageCubit(LanguageRepository())), + BlocProvider(create: (_) => SectionCubit(SectionRepository())), + BlocProvider(create: (_) => PrivacyTermsCubit(OtherPageRepository())), + BlocProvider(create: (_) => VideoCubit(VideoRepository())), + BlocProvider(create: (_) => NewsByIdCubit(NewsByIdRepository())), + BlocProvider(create: (_) => OtherPageCubit(OtherPageRepository())), + BlocProvider(create: (_) => LiveStreamCubit(LiveStreamRepository())), + BlocProvider(create: (_) => CategoryCubit(CategoryRepository())), + BlocProvider(create: (_) => SubCategoryCubit(SubCategoryRepository())), + BlocProvider(create: (_) => SurveyQuestionCubit(SurveyQuestionRepository())), + BlocProvider(create: (_) => SetSurveyAnsCubit(SetSurveyAnsRepository())), + BlocProvider(create: (_) => GetSurveyAnsCubit(GetSurveyAnsRepository())), + BlocProvider(create: (_) => CommentNewsCubit(CommentNewsRepository())), + BlocProvider(create: (_) => RelatedNewsCubit(RelatedNewsRepository())), + BlocProvider(create: (_) => SocialSignUpCubit(AuthRepository())), + BlocProvider(create: (_) => AuthCubit(AuthRepository())), + BlocProvider(create: (_) => RegisterTokenCubit(AuthRepository())), + BlocProvider(create: (_) => UserByCatCubit(UserByCatRepository())), + BlocProvider(create: (_) => SetUserPrefCatCubit(SetUserPrefCatRepository())), + BlocProvider(create: (_) => UpdateUserCubit(AuthRepository())), + BlocProvider(create: (_) => DeleteUserCubit(AuthRepository())), + BlocProvider(create: (_) => BookmarkCubit(BookmarkRepository())), + BlocProvider(create: (_) => UpdateBookmarkStatusCubit(BookmarkRepository())), + BlocProvider(create: (_) => LikeAndDisLikeCubit(LikeAndDisLikeRepository())), + BlocProvider(create: (_) => UpdateLikeAndDisLikeStatusCubit(LikeAndDisLikeRepository())), + BlocProvider(create: (_) => BreakingNewsCubit(BreakingNewsRepository())), + BlocProvider(create: (_) => TagNewsCubit(TagNewsRepository())), + BlocProvider(create: (_) => SetCommentCubit(SetCommentRepository())), + BlocProvider(create: (_) => LikeAndDislikeCommCubit(LikeAndDislikeCommRepository())), + BlocProvider(create: (_) => DeleteCommCubit(DeleteCommRepository())), + BlocProvider(create: (_) => SetFlagCubit(SetFlagRepository())), + BlocProvider(create: (_) => AddNewsCubit(AddNewsRepository())), + BlocProvider(create: (_) => TagCubit(TagRepository())), + BlocProvider(create: (_) => GetUserNewsCubit(GetUserNewsRepository())), + BlocProvider(create: (_) => DeleteUserNewsCubit(DeleteUserNewsRepository())), + BlocProvider(create: (_) => DeleteImageCubit(DeleteImageRepository())), + BlocProvider(create: (_) => GetUserByIdCubit(GetUserByIdRepository())), + BlocProvider(create: (_) => SectionByIdCubit(SectionByIdRepository())), + BlocProvider(create: (_) => SetNewsViewsCubit(SetNewsViewsRepository())), + BlocProvider(create: (_) => AdSpacesNewsDetailsCubit()), + BlocProvider(create: (_) => LocationCityCubit()), + BlocProvider(create: (_) => BottomSheetCubit()), + BlocProvider(create: (_) => SlugCheckCubit()), + BlocProvider(create: (_) => GeneralNewsCubit()), + BlocProvider(create: (_) => RSSFeedCubit()), + BlocProvider(create: (_) => SlugNewsCubit()), + BlocProvider(create: (_) => WeatherCubit()), + BlocProvider(create: (_) => AuthorCubit()), + BlocProvider(create: (_) => AuthorNewsCubit()), + BlocProvider(create: (_) => GetUserDraftedNewsCubit()) + ], child: const MyApp())); +} + +class GlobalScrollBehavior extends ScrollBehavior { + @override + ScrollPhysics getScrollPhysics(BuildContext context) { + return const BouncingScrollPhysics(); + } +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + @override + void initState() { + final pushNotificationService = PushNotificationService(context: context); + pushNotificationService.initialise(); + var brightness = PlatformDispatcher.instance.platformBrightness; + if (SettingsLocalDataRepository().getCurrentTheme().isEmpty) { + (brightness == Brightness.dark) ? context.read().changeTheme(AppTheme.Dark) : context.read().changeTheme(AppTheme.Light); + } + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Builder(builder: (context) { + if (Hive.box(settingsBoxKey).get(currentLanguageCodeKey) != null || Hive.box(settingsBoxKey).get(currentLanguageCodeKey) != "") { + initializeDateFormatting(); + intl.Intl.defaultLocale = Hive.box(settingsBoxKey).get(currentLanguageCodeKey); //set default Locale @Start + } + final currentTheme = context.watch().state.appTheme; + return BlocBuilder( + builder: (context, state) { + return MaterialApp( + navigatorKey: UiUtils.rootNavigatorKey, + theme: appThemeData[currentTheme], + debugShowCheckedModeBanner: false, + initialRoute: Routes.splash, + title: appName, + onGenerateRoute: Routes.onGenerateRouted, + builder: (context, widget) { + return ScrollConfiguration( + behavior: GlobalScrollBehavior(), + child: Directionality( + textDirection: state.isRTL == '' || state.isRTL == 0 ? TextDirection.ltr : TextDirection.rtl, + child: Padding( + padding: EdgeInsets.only(bottom: MediaQuery.viewPaddingOf(context).bottom), + child: widget!, + ))); + }); + }, + ); + }); + } +} + +class MyHttpOverrides extends HttpOverrides { + @override + HttpClient createHttpClient(SecurityContext? context) { + return super.createHttpClient(context)..badCertificateCallback = (X509Certificate cert, String host, int port) => true; + } +} diff --git a/news-app/lib/app/routes.dart b/news-app/lib/app/routes.dart new file mode 100644 index 00000000..f5e8577b --- /dev/null +++ b/news-app/lib/app/routes.dart @@ -0,0 +1,203 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:news/ui/screens/AddEditNews/AddNews.dart'; +import 'package:news/ui/screens/BookmarkScreen.dart'; +import 'package:news/ui/screens/ImagePreviewScreen.dart'; +import 'package:news/ui/screens/LiveStreaming.dart'; +import 'package:news/ui/screens/NewsDetail/NewsDetailScreen.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/ShowMoreNewsList.dart'; +import 'package:news/ui/screens/PrivacyPolicyScreen.dart'; +import 'package:news/ui/screens/Profile/userProfile.dart'; +import 'package:news/ui/screens/RSSFeedDetailsScreen.dart'; +import 'package:news/ui/screens/Search.dart'; +import 'package:news/ui/screens/AddEditNews/ManageUserNews.dart'; +import 'package:news/ui/screens/SubCategory/SubCategoryScreen.dart'; +import 'package:news/ui/screens/TagNewsScreen.dart'; +import 'package:news/ui/screens/Videos/videoDetailsScreen.dart'; +import 'package:news/ui/screens/auth/ForgotPassword.dart'; +import 'package:news/ui/screens/auth/RequestOtpScreen.dart'; +import 'package:news/ui/screens/auth/VerifyOtpScreen.dart'; +import 'package:news/ui/screens/authorDetailsScreen.dart'; +import 'package:news/ui/screens/dashBoard/dashBoardScreen.dart'; +import 'package:news/ui/screens/introSlider.dart'; +import 'package:news/ui/screens/languageList.dart'; +import 'package:news/ui/screens/maintenanceScreen.dart'; +import 'package:news/ui/screens/splashScreen.dart'; +import 'package:news/ui/screens/ManagePreference.dart'; +import 'package:news/ui/screens/SectionMoreNews/SectionMoreBreakNewsList.dart'; +import 'package:news/ui/screens/SectionMoreNews/SectionMoreNewsList.dart'; +import 'package:news/ui/screens/auth/loginScreen.dart'; +import 'package:news/ui/widgets/loadingScreen.dart'; + +class Routes { + static const String splash = "splash"; + static const String home = "/"; + static const String introSlider = "introSlider"; + static const String languageList = "languageList"; + static const String login = "login"; + static const String privacy = "privacy"; + static const String search = "search"; + static const String live = "live"; + static const String subCat = "subCat"; + static const String requestOtp = "requestOtp"; + static const String verifyOtp = "verifyOtp"; + static const String managePref = "managePref"; + static const String newsVideo = "newsVideo"; + static const String bookmark = "bookmark"; + static const String newsDetails = "newsDetails"; + static const String imagePreview = "imagePreview"; + static const String tagScreen = "tagScreen"; + static const String addNews = "AddNews"; + static const String editNews = "editNews"; + static const String manageUserNews = "showNews"; + static const String forgotPass = "forgotPass"; + static const String sectionNews = "sectionNews"; + static const String sectionBreakNews = "sectionBreakNews"; + static const String showMoreRelatedNews = "showMoreRelatedNews"; + static const String editUserProfile = "editUserProfile"; + static const String maintenance = "maintenance"; + static const String rssFeedDetails = "rssFeedDetails"; + static const String authorDetails = "authorDetails"; + + static String currentRoute = splash; + static String previousRoute = ""; + + static Route onGenerateRouted(RouteSettings routeSettings) { + previousRoute = currentRoute; + currentRoute = routeSettings.name ?? ""; + + if (routeSettings.name!.contains('/news/') || routeSettings.name!.contains('/breaking-news/') || routeSettings.name!.contains('/video-news/')) { + final String? currNewsSlug = routeSettings.name!.split('/').last.split('?language_id').first; + + if (previousRoute == splash) { + //app is closed + isShared = true; + + Uri uri = Uri.parse(routeSettings.name!); + String path = uri.path; + routeSettingsName = path; + newsSlug = currNewsSlug; + return CupertinoPageRoute(builder: (_) => const Splash()); + } else { + //app is running + return CupertinoPageRoute(builder: (_) => LoadingScreen(routeSettingsName: routeSettings.name!, newsSlug: currNewsSlug ?? "")); + } + } + switch (routeSettings.name) { + case splash: + { + return CupertinoPageRoute(builder: (_) => const Splash()); + } + case home: + { + return DashBoard.route(routeSettings); + } + case introSlider: + { + return CupertinoPageRoute(builder: (_) => const IntroSliderScreen()); + } + case login: + { + return LoginScreen.route(routeSettings); + } + case languageList: + { + return LanguageList.route(routeSettings); + } + case privacy: + { + return PrivacyPolicy.route(routeSettings); + } + case search: + { + return CupertinoPageRoute(builder: (_) => const Search()); + } + case live: + { + return LiveStreaming.route(routeSettings); + } + case subCat: + { + return SubCategoryScreen.route(routeSettings); + } + case requestOtp: + { + return CupertinoPageRoute(builder: (_) => const RequestOtp()); + } + case verifyOtp: + { + return VerifyOtp.route(routeSettings); + } + case managePref: + { + return ManagePref.route(routeSettings); + } + case newsVideo: + { + return VideoDetailsScreen.route(routeSettings); + } + case bookmark: + { + return CupertinoPageRoute(builder: (_) => const BookmarkScreen()); + } + case newsDetails: + { + return NewsDetailScreen.route(routeSettings); + } + case imagePreview: + { + return ImagePreview.route(routeSettings); + } + case tagScreen: + { + return NewsTag.route(routeSettings); + } + case addNews: + { + return AddNews.route(routeSettings); + } + case manageUserNews: + { + return CupertinoPageRoute(builder: (_) => const ManageUserNews()); + } + case forgotPass: + { + return CupertinoPageRoute(builder: (_) => const ForgotPassword()); + } + + case sectionNews: + { + return SectionMoreNewsList.route(routeSettings); + } + case sectionBreakNews: + { + return SectionMoreBreakingNewsList.route(routeSettings); + } + case showMoreRelatedNews: + { + return ShowMoreNewsList.route(routeSettings); + } + case editUserProfile: + { + return UserProfileScreen.route(routeSettings); + } + case maintenance: + { + return CupertinoPageRoute(builder: (_) => const MaintenanceScreen()); + } + case rssFeedDetails: + { + return RSSFeedDetailsScreen.route(routeSettings); + } + case authorDetails: + { + return AuthorDetailsScreen.route(routeSettings); + } + + default: + { + return CupertinoPageRoute(builder: (context) => const Scaffold()); + } + } + } +} diff --git a/news-app/lib/cubits/AddNewsCubit.dart b/news-app/lib/cubits/AddNewsCubit.dart new file mode 100644 index 00000000..1616dd06 --- /dev/null +++ b/news-app/lib/cubits/AddNewsCubit.dart @@ -0,0 +1,86 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/AddNews/addNewsRepository.dart'; + +abstract class AddNewsState {} + +class AddNewsInitial extends AddNewsState {} + +class AddNewsFetchInProgress extends AddNewsState {} + +class AddNewsFetchSuccess extends AddNewsState { + var addNews; + + AddNewsFetchSuccess({required this.addNews}); +} + +class AddNewsFetchFailure extends AddNewsState { + final String errorMessage; + + AddNewsFetchFailure(this.errorMessage); +} + +class AddNewsCubit extends Cubit { + final AddNewsRepository _addNewsRepository; + + AddNewsCubit(this._addNewsRepository) : super(AddNewsInitial()); + + void addNews( + {required BuildContext context, + required String actionType, + required String catId, + required String title, + required String conTypeId, + required String conType, + required String langId, + File? image, + String? newsId, + String? subCatId, + String? showTill, + String? tagId, + String? url, + String? desc, + String? summDescription, + String? locationId, + File? videoUpload, + List? otherImage, + String? publishDate, + required String metaTitle, + required String metaDescription, + required String metaKeyword, + required String slug, + required int isDraft}) async { + try { + emit(AddNewsFetchInProgress()); + final result = await _addNewsRepository.addNews( + context: context, + actionType: actionType, + newsId: newsId, + title: title, + image: image, + conTypeId: conTypeId, + conType: conType, + langId: langId, + catId: catId, + videoUpload: videoUpload, + url: url, + tagId: tagId, + otherImage: otherImage, + desc: desc, + showTill: showTill, + subCatId: subCatId, + locationId: locationId, + metaTitle: metaTitle, + metaDescription: metaDescription, + metaKeyword: metaKeyword, + slug: slug, + publishDate: publishDate ?? null, + summDescription: summDescription, + isDraft: isDraft); + emit(AddNewsFetchSuccess(addNews: result)); + } catch (e) { + emit(AddNewsFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/Auth/authCubit.dart b/news-app/lib/cubits/Auth/authCubit.dart new file mode 100644 index 00000000..9ce3daa3 --- /dev/null +++ b/news-app/lib/cubits/Auth/authCubit.dart @@ -0,0 +1,98 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/AuthModel.dart'; +import 'package:news/data/repositories/Auth/authRepository.dart'; + +const String loginEmail = "email"; +const String loginGmail = "gmail"; +const String loginFb = "fb"; +const String loginMbl = "mobile"; +const String loginApple = "apple"; + +enum AuthProviders { gmail, fb, apple, mobile, email } + +@immutable +abstract class AuthState {} + +class AuthInitial extends AuthState {} + +class Authenticated extends AuthState { + //to store authDetails + final AuthModel authModel; + + Authenticated({required this.authModel}); +} + +class Unauthenticated extends AuthState {} + +class AuthCubit extends Cubit { + final AuthRepository _authRepository; + + AuthCubit(this._authRepository) : super(AuthInitial()) { + checkAuthStatus(); + } + + AuthRepository get authRepository => _authRepository; + + void checkAuthStatus() { + //authDetails is map. keys are isLogin,userId,authProvider + final authDetails = _authRepository.getLocalAuthDetails(); + + (authDetails['isLogIn']) ? emit(Authenticated(authModel: AuthModel.fromJson(authDetails))) : emit(Unauthenticated()); + } + + String getUserId() { + return (state is Authenticated) ? (state as Authenticated).authModel.id! : "0"; + } + + String getProfile() { + return (state is Authenticated) + ? ((state as Authenticated).authModel.profile!.trim().isNotEmpty) + ? (state as Authenticated).authModel.profile! + : "" + : ""; + } + + String getMobile() { + return (state is Authenticated) ? (state as Authenticated).authModel.mobile! : ""; + } + + String getType() { + return (state is Authenticated) ? (state as Authenticated).authModel.type! : ""; + } + + String getAuthorBio() { + return (state is Authenticated && (state as Authenticated).authModel.authorDetails != null) ? ((state as Authenticated).authModel.authorDetails?.bio ?? "") : ""; + } + + String getAuthorWhatsappLink() { + return (state is Authenticated && (state as Authenticated).authModel.authorDetails != null) ? (state as Authenticated).authModel.authorDetails?.whatsappLink ?? "" : "0"; + } + + String getAuthorTelegramLink() { + return (state is Authenticated && (state as Authenticated).authModel.authorDetails != null) ? (state as Authenticated).authModel.authorDetails?.telegramLink ?? "" : "0"; + } + + String getAuthorFacebookLink() { + return (state is Authenticated && (state as Authenticated).authModel.authorDetails != null) ? (state as Authenticated).authModel.authorDetails?.facebookLink ?? "" : "0"; + } + + String getAuthorLinkedInLink() { + return (state is Authenticated && (state as Authenticated).authModel.authorDetails != null) ? (state as Authenticated).authModel.authorDetails?.linkedinLink ?? "" : "0"; + } + + void updateDetails({required AuthModel authModel}) { + emit(Authenticated(authModel: authModel)); + } + + Future signOut(AuthProviders authProvider) async { + if (state is Authenticated) { + _authRepository.signOut(authProvider); + emit(Unauthenticated()); + } + } + + bool isAuthor() { + return (state is Authenticated) ? ((state as Authenticated).authModel.isAuthor == 1) : false; + } +} diff --git a/news-app/lib/cubits/Auth/deleteUserCubit.dart b/news-app/lib/cubits/Auth/deleteUserCubit.dart new file mode 100644 index 00000000..908ada42 --- /dev/null +++ b/news-app/lib/cubits/Auth/deleteUserCubit.dart @@ -0,0 +1,37 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/Auth/authRepository.dart'; + +abstract class DeleteUserState {} + +class DeleteUserInitial extends DeleteUserState {} + +class DeleteUserFetchInProgress extends DeleteUserState {} + +class DeleteUserFetchSuccess extends DeleteUserState { + dynamic deleteUser; + + DeleteUserFetchSuccess({required this.deleteUser}); +} + +class DeleteUserFetchFailure extends DeleteUserState { + final String errorMessage; + + DeleteUserFetchFailure(this.errorMessage); +} + +class DeleteUserCubit extends Cubit { + final AuthRepository _deleteUserRepository; + + DeleteUserCubit(this._deleteUserRepository) : super(DeleteUserInitial()); + + Future deleteUser({String? name, String? mobile, String? email, String? filePath}) async { + try { + emit(DeleteUserFetchInProgress()); + final result = await _deleteUserRepository.deleteUser(); + emit(DeleteUserFetchSuccess(deleteUser: result)); + return result; + } catch (e) { + emit(DeleteUserFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/Auth/registerTokenCubit.dart b/news-app/lib/cubits/Auth/registerTokenCubit.dart new file mode 100644 index 00000000..423b7b41 --- /dev/null +++ b/news-app/lib/cubits/Auth/registerTokenCubit.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/Auth/authRepository.dart'; + +@immutable +abstract class RegisterTokenState {} + +class RegisterTokenInitial extends RegisterTokenState {} + +class RegisterTokenProgress extends RegisterTokenState { + RegisterTokenProgress(); +} + +class RegisterTokenSuccess extends RegisterTokenState { + RegisterTokenSuccess(); +} + +class RegisterTokenFailure extends RegisterTokenState { + final String errorMessage; + + RegisterTokenFailure(this.errorMessage); +} + +class RegisterTokenCubit extends Cubit { + final AuthRepository _authRepository; + + RegisterTokenCubit(this._authRepository) : super(RegisterTokenInitial()); + + void registerToken({required String fcmId, required BuildContext context}) { + emit(RegisterTokenProgress()); + _authRepository.registerToken(fcmId: fcmId, context: context).then((result) { + emit(RegisterTokenSuccess()); + }).catchError((e) { + emit(RegisterTokenFailure(e.toString())); + }); + } +} diff --git a/news-app/lib/cubits/Auth/socialSignUpCubit.dart b/news-app/lib/cubits/Auth/socialSignUpCubit.dart new file mode 100644 index 00000000..d7da2acf --- /dev/null +++ b/news-app/lib/cubits/Auth/socialSignUpCubit.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/utils/strings.dart'; +import 'package:news/data/models/AuthModel.dart'; +import 'package:news/data/repositories/Auth/authRepository.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; + +@immutable +abstract class SocialSignUpState {} + +class SocialSignUpInitial extends SocialSignUpState {} + +class SocialSignUpProgress extends SocialSignUpState {} + +class SocialSignUpSuccess extends SocialSignUpState { + final AuthModel authModel; + + SocialSignUpSuccess({required this.authModel}); +} + +class SocialSignUpFailure extends SocialSignUpState { + final String errorMessage; + + SocialSignUpFailure(this.errorMessage); +} + +class SocialSignUpCubit extends Cubit { + final AuthRepository _authRepository; + + SocialSignUpCubit(this._authRepository) : super(SocialSignUpInitial()); + + void socialSignUpUser({required AuthProviders authProvider, required BuildContext context, String? email, String? password, String? otp, String? verifiedId}) { + emit(SocialSignUpProgress()); + _authRepository.signInUser(email: email, otp: otp, password: password, verifiedId: verifiedId, authProvider: authProvider, context: context).then((result) { + emit(SocialSignUpSuccess(authModel: AuthModel.fromJson(result[DATA]))); + }).catchError((e) { + emit(SocialSignUpFailure(e.toString())); + }); + } +} diff --git a/news-app/lib/cubits/Auth/updateUserCubit.dart b/news-app/lib/cubits/Auth/updateUserCubit.dart new file mode 100644 index 00000000..cd03aaf2 --- /dev/null +++ b/news-app/lib/cubits/Auth/updateUserCubit.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/data/models/AuthModel.dart'; +import 'package:news/data/repositories/Auth/authRepository.dart'; + +abstract class UpdateUserState {} + +class UpdateUserInitial extends UpdateUserState {} + +class UpdateUserFetchInProgress extends UpdateUserState {} + +class UpdateUserFetchSuccess extends UpdateUserState { + AuthModel? updatedUser; + String? imgUpdatedPath; + + UpdateUserFetchSuccess({this.updatedUser, this.imgUpdatedPath}); +} + +class UpdateUserFetchFailure extends UpdateUserState { + final String errorMessage; + + UpdateUserFetchFailure(this.errorMessage); +} + +class UpdateUserCubit extends Cubit { + final AuthRepository _updateUserRepository; + + UpdateUserCubit(this._updateUserRepository) : super(UpdateUserInitial()); + + void setUpdateUser( + {String? name, + String? mobile, + String? email, + String? filePath, + required BuildContext context, + String? authorBio, + String? whatsappLink, + String? facebookLink, + String? telegramLink, + String? linkedInLink}) async { + try { + emit(UpdateUserFetchInProgress()); + final Map result = await _updateUserRepository.updateUserData( + mobile: mobile, + name: name, + email: email, + filePath: filePath, + authorBio: authorBio, + whatsappLink: whatsappLink, + facebookLink: facebookLink, + telegramLink: telegramLink, + linkedInLink: linkedInLink); + //only incase of name,mobile & mail, not Profile Picture + context.read().updateDetails(authModel: AuthModel.fromJson(result["data"])); + emit(UpdateUserFetchSuccess(updatedUser: AuthModel.fromJson(result["data"]))); + } catch (e) { + emit(UpdateUserFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/Author/authorCubit.dart b/news-app/lib/cubits/Author/authorCubit.dart new file mode 100644 index 00000000..9258a6d5 --- /dev/null +++ b/news-app/lib/cubits/Author/authorCubit.dart @@ -0,0 +1,43 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +abstract class AuthorState {} + +class AuthorInitial extends AuthorState {} + +class AuthorInProgress extends AuthorState {} + +class AuthorApproved extends AuthorState { + AuthorApproved(); +} + +class AuthorRequestSent extends AuthorState { + final String responseMessage; + AuthorRequestSent(this.responseMessage); +} + +class AuthorPending extends AuthorState { + final String errorMessage; + + AuthorPending(this.errorMessage); +} + +class AuthorRejected extends AuthorState { + final String errorMessage; + + AuthorRejected(this.errorMessage); +} + +class AuthorCubit extends Cubit { + AuthorCubit() : super(AuthorInitial()); + + void requestToBecomeAuthor() async { + try { + final result = await Api.sendApiRequest(body: {}, url: Api.becomeAnAuthorApi); + (!result[ERROR]) ? emit(AuthorRequestSent(result[MESSAGE])) : emit(AuthorPending(result[MESSAGE])); + } catch (e) { + emit(AuthorPending(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/Author/authorNewsCubit.dart b/news-app/lib/cubits/Author/authorNewsCubit.dart new file mode 100644 index 00000000..3181a801 --- /dev/null +++ b/news-app/lib/cubits/Author/authorNewsCubit.dart @@ -0,0 +1,49 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +abstract class AuthorNewsState {} + +class AuthorNewsInitial extends AuthorNewsState {} + +class AuthorNewsFetchInProgress extends AuthorNewsState {} + +class AuthorNewsFetchSuccess extends AuthorNewsState { + final List AuthorNewsList; + final UserAuthorModel authorData; + final int totalAuthorNewsCount; + final bool hasMoreFetchError; + final bool hasMore; + + AuthorNewsFetchSuccess({required this.AuthorNewsList, required this.authorData, required this.totalAuthorNewsCount, required this.hasMoreFetchError, required this.hasMore}); +} + +class AuthorNewsFetchFailed extends AuthorNewsState { + final String errorMessage; + + AuthorNewsFetchFailed(this.errorMessage); +} + +class AuthorNewsCubit extends Cubit { + AuthorNewsCubit() : super(AuthorNewsInitial()); + + void getAuthorNews({required String authorId}) async { + try { + emit(AuthorNewsInitial()); + final apiUrl = "${Api.getAuthorNewsApi}/${authorId}"; + final result = await Api.sendApiRequest(body: null, url: apiUrl, isGet: true); + + (!result[ERROR]) + ? emit(AuthorNewsFetchSuccess( + AuthorNewsList: (result[DATA][NEWS][DATA] as List).map((e) => NewsModel.fromJson(e)).toList(), + authorData: UserAuthorModel.fromJson(result[DATA][USER]), + totalAuthorNewsCount: result[DATA][NEWS][TOTAL], + hasMore: false, + hasMoreFetchError: false)) + : emit(AuthorNewsFetchFailed(result[MESSAGE])); + } catch (e) { + emit(AuthorNewsFetchFailed(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/Bookmark/UpdateBookmarkCubit.dart b/news-app/lib/cubits/Bookmark/UpdateBookmarkCubit.dart new file mode 100644 index 00000000..e5958803 --- /dev/null +++ b/news-app/lib/cubits/Bookmark/UpdateBookmarkCubit.dart @@ -0,0 +1,38 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/Bookmark/bookmarkRepository.dart'; +import 'package:news/utils/api.dart'; + +abstract class UpdateBookmarkStatusState {} + +class UpdateBookmarkStatusInitial extends UpdateBookmarkStatusState {} + +class UpdateBookmarkStatusInProgress extends UpdateBookmarkStatusState {} + +class UpdateBookmarkStatusSuccess extends UpdateBookmarkStatusState { + final NewsModel news; + final bool wasBookmarkNewsProcess; //to check that process of Bookmark done or not + UpdateBookmarkStatusSuccess(this.news, this.wasBookmarkNewsProcess); +} + +class UpdateBookmarkStatusFailure extends UpdateBookmarkStatusState { + final String errorMessage; + + UpdateBookmarkStatusFailure(this.errorMessage); +} + +class UpdateBookmarkStatusCubit extends Cubit { + final BookmarkRepository bookmarkRepository; + + UpdateBookmarkStatusCubit(this.bookmarkRepository) : super(UpdateBookmarkStatusInitial()); + + void setBookmarkNews({required NewsModel news, required String status}) { + emit(UpdateBookmarkStatusInProgress()); + bookmarkRepository.setBookmark(newsId: (news.newsId != null) ? news.newsId! : news.id!, status: status).then((value) { + emit(UpdateBookmarkStatusSuccess(news, status == "1" ? true : false)); + }).catchError((e) { + ApiMessageAndCodeException apiMessageAndCodeException = e; + emit(UpdateBookmarkStatusFailure(apiMessageAndCodeException.errorMessage.toString())); + }); + } +} diff --git a/news-app/lib/cubits/Bookmark/bookmarkCubit.dart b/news-app/lib/cubits/Bookmark/bookmarkCubit.dart new file mode 100644 index 00000000..2d89aef0 --- /dev/null +++ b/news-app/lib/cubits/Bookmark/bookmarkCubit.dart @@ -0,0 +1,111 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/Bookmark/bookmarkRepository.dart'; +import 'package:news/utils/strings.dart'; + +abstract class BookmarkState {} + +class BookmarkInitial extends BookmarkState {} + +class BookmarkFetchInProgress extends BookmarkState {} + +class BookmarkFetchSuccess extends BookmarkState { + final List bookmark; + final int totalBookmarkCount; + final bool hasMoreFetchError; + final bool hasMore; + + BookmarkFetchSuccess({required this.bookmark, required this.totalBookmarkCount, required this.hasMoreFetchError, required this.hasMore}); +} + +class BookmarkFetchFailure extends BookmarkState { + final String errorMessage; + + BookmarkFetchFailure(this.errorMessage); +} + +class BookmarkCubit extends Cubit { + final BookmarkRepository bookmarkRepository; + int perPageLimit = 25; + + BookmarkCubit(this.bookmarkRepository) : super(BookmarkInitial()); + + void getBookmark({required String langId}) async { + emit(BookmarkFetchInProgress()); + + try { + final result = await bookmarkRepository.getBookmark(limit: perPageLimit.toString(), offset: "0", langId: langId); + + if (result[ERROR]) { + final message = result[MESSAGE]; + if (message == "No Data Found") { + emit(BookmarkFetchSuccess(bookmark: [], totalBookmarkCount: 0, hasMoreFetchError: false, hasMore: false)); + } else { + emit(BookmarkFetchFailure(message)); + } + return; + } + + final bookmarks = result['Bookmark'] as List; + final total = result[TOTAL] as int; + + emit(BookmarkFetchSuccess(bookmark: bookmarks, totalBookmarkCount: total, hasMoreFetchError: false, hasMore: bookmarks.length < total)); + } catch (e) { + final error = e.toString(); + emit(error == "No Data Found" ? BookmarkFetchSuccess(bookmark: [], totalBookmarkCount: 0, hasMoreFetchError: false, hasMore: false) : BookmarkFetchFailure(error)); + } + } + + bool hasMoreBookmark() { + return (state is BookmarkFetchSuccess) ? (state as BookmarkFetchSuccess).hasMore : false; + } + + void getMoreBookmark({required String langId}) async { + if (state is BookmarkFetchSuccess) { + try { + final result = await bookmarkRepository.getBookmark(limit: perPageLimit.toString(), offset: (state as BookmarkFetchSuccess).bookmark.length.toString(), langId: langId); + List updatedResults = (state as BookmarkFetchSuccess).bookmark; + updatedResults.addAll(result['Bookmark'] as List); + emit(BookmarkFetchSuccess(bookmark: updatedResults, totalBookmarkCount: result[TOTAL], hasMoreFetchError: false, hasMore: updatedResults.length < result[TOTAL])); + } catch (e) { + emit(BookmarkFetchSuccess( + bookmark: (state as BookmarkFetchSuccess).bookmark, + hasMoreFetchError: (e.toString() == "No Data Found") ? false : true, + totalBookmarkCount: (state as BookmarkFetchSuccess).totalBookmarkCount, + hasMore: (state as BookmarkFetchSuccess).hasMore)); + } + } + } + + void addBookmarkNews(NewsModel model) { + if (state is BookmarkFetchSuccess) { + List bookmarklist = []; + bookmarklist.insert(0, model); + bookmarklist.addAll((state as BookmarkFetchSuccess).bookmark); + + emit(BookmarkFetchSuccess( + bookmark: List.from(bookmarklist), hasMoreFetchError: true, totalBookmarkCount: (state as BookmarkFetchSuccess).totalBookmarkCount, hasMore: (state as BookmarkFetchSuccess).hasMore)); + } + } + + void removeBookmarkNews(NewsModel model) { + if (state is BookmarkFetchSuccess) { + final bookmark = (state as BookmarkFetchSuccess).bookmark; + bookmark.removeWhere(((element) => (element.id == model.id || element.newsId == model.id))); + emit(BookmarkFetchSuccess( + bookmark: List.from(bookmark), hasMoreFetchError: true, totalBookmarkCount: (state as BookmarkFetchSuccess).totalBookmarkCount, hasMore: (state as BookmarkFetchSuccess).hasMore)); + } + } + + bool isNewsBookmark(String newsId) { + if (state is BookmarkFetchSuccess) { + final bookmark = (state as BookmarkFetchSuccess).bookmark; + return (bookmark.isNotEmpty) ? (bookmark.indexWhere((element) => (element.newsId == newsId || element.id == newsId)) != -1) : false; + } + return false; + } + + void resetState() { + emit(BookmarkFetchInProgress()); + } +} diff --git a/news-app/lib/cubits/ConnectivityCubit.dart b/news-app/lib/cubits/ConnectivityCubit.dart new file mode 100644 index 00000000..0eb95129 --- /dev/null +++ b/news-app/lib/cubits/ConnectivityCubit.dart @@ -0,0 +1,79 @@ +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class ConnectivityCubit extends Cubit { + final ConnectivityService _connectivityService; + late StreamSubscription _subscription; + + ConnectivityCubit(this._connectivityService) : super(ConnectivityInitial()) { + _startMonitoring(); + } + + void _startMonitoring() { + _subscription = _connectivityService.connectivityStream.listen((result) { + if (result == ConnectivityResult.none) { + emit(ConnectivityDisconnected()); + } else { + emit(ConnectivityConnected(result)); + } + }); + } + + Future checkInitialConnection() async { + final result = await _connectivityService.checkConnectivity(); + if (result == ConnectivityResult.none) { + emit(ConnectivityDisconnected()); + } else { + emit(ConnectivityConnected(result as ConnectivityResult)); + } + } + + @override + Future close() { + _subscription.cancel(); + return super.close(); + } +} + +abstract class ConnectivityState extends Equatable { + const ConnectivityState(); + + @override + List get props => []; +} + +class ConnectivityInitial extends ConnectivityState {} + +class ConnectivityConnected extends ConnectivityState { + final ConnectivityResult result; + + const ConnectivityConnected(this.result); + + @override + List get props => [result]; +} + +class ConnectivityDisconnected extends ConnectivityState {} + +class ConnectivityService { + static final ConnectivityService _instance = ConnectivityService._internal(); + final Connectivity _connectivity = Connectivity(); + final StreamController _controller = StreamController.broadcast(); + + factory ConnectivityService() => _instance; + + ConnectivityService._internal() { + _connectivity.onConnectivityChanged.listen((result) { + _controller.add(result.first); // Notify all listeners + }); + } + + Stream get connectivityStream => _controller.stream; + + Future> checkConnectivity() async { + return await _connectivity.checkConnectivity(); + } +} diff --git a/news-app/lib/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart b/news-app/lib/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart new file mode 100644 index 00000000..218ddc0d --- /dev/null +++ b/news-app/lib/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart @@ -0,0 +1,55 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsRepository.dart'; +import 'package:news/utils/strings.dart'; + +abstract class LikeAndDisLikeState {} + +class LikeAndDisLikeInitial extends LikeAndDisLikeState {} + +class LikeAndDisLikeFetchInProgress extends LikeAndDisLikeState {} + +class LikeAndDisLikeFetchSuccess extends LikeAndDisLikeState { + final List likeAndDisLike; + final int totalLikeAndDisLikeCount; + final bool hasMoreFetchError; + final bool hasMore; + + LikeAndDisLikeFetchSuccess({required this.likeAndDisLike, required this.totalLikeAndDisLikeCount, required this.hasMoreFetchError, required this.hasMore}); +} + +class LikeAndDisLikeFetchFailure extends LikeAndDisLikeState { + final String errorMessage; + + LikeAndDisLikeFetchFailure(this.errorMessage); +} + +class LikeAndDisLikeCubit extends Cubit { + final LikeAndDisLikeRepository likeAndDisLikeRepository; + int perPageLimit = 25; + + LikeAndDisLikeCubit(this.likeAndDisLikeRepository) : super(LikeAndDisLikeInitial()); + + void getLike({required String langId}) async { + try { + emit(LikeAndDisLikeFetchInProgress()); + final result = await likeAndDisLikeRepository.getLike(limit: perPageLimit.toString(), offset: "0", langId: langId); + emit(LikeAndDisLikeFetchSuccess( + likeAndDisLike: result['LikeAndDisLike'], totalLikeAndDisLikeCount: result[TOTAL], hasMoreFetchError: false, hasMore: (result['LikeAndDisLike'] as List).length < result[TOTAL])); + } catch (e) { + emit(LikeAndDisLikeFetchFailure(e.toString())); + } + } + + bool isNewsLikeAndDisLike(String newsId) { + if (state is LikeAndDisLikeFetchSuccess) { + final likeAndDisLike = (state as LikeAndDisLikeFetchSuccess).likeAndDisLike; + return likeAndDisLike.indexWhere((element) => (element.id == newsId || element.newsId == newsId)) != -1; + } + return false; + } + + void resetState() { + emit(LikeAndDisLikeFetchInProgress()); + } +} diff --git a/news-app/lib/cubits/LikeAndDislikeNews/updateLikeAndDislikeCubit.dart b/news-app/lib/cubits/LikeAndDislikeNews/updateLikeAndDislikeCubit.dart new file mode 100644 index 00000000..639deb6a --- /dev/null +++ b/news-app/lib/cubits/LikeAndDislikeNews/updateLikeAndDislikeCubit.dart @@ -0,0 +1,38 @@ +import 'package:news/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsRepository.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/utils/api.dart'; + +abstract class UpdateLikeAndDisLikeStatusState {} + +class UpdateLikeAndDisLikeStatusInitial extends UpdateLikeAndDisLikeStatusState {} + +class UpdateLikeAndDisLikeStatusInProgress extends UpdateLikeAndDisLikeStatusState {} + +class UpdateLikeAndDisLikeStatusSuccess extends UpdateLikeAndDisLikeStatusState { + final NewsModel news; + final bool wasLikeAndDisLikeNewsProcess; //to check that process of favorite done or not + UpdateLikeAndDisLikeStatusSuccess(this.news, this.wasLikeAndDisLikeNewsProcess); +} + +class UpdateLikeAndDisLikeStatusFailure extends UpdateLikeAndDisLikeStatusState { + final String errorMessage; + + UpdateLikeAndDisLikeStatusFailure(this.errorMessage); +} + +class UpdateLikeAndDisLikeStatusCubit extends Cubit { + final LikeAndDisLikeRepository likeAndDisLikeRepository; + + UpdateLikeAndDisLikeStatusCubit(this.likeAndDisLikeRepository) : super(UpdateLikeAndDisLikeStatusInitial()); + + void setLikeAndDisLikeNews({required NewsModel news, required String status}) { + emit(UpdateLikeAndDisLikeStatusInProgress()); + likeAndDisLikeRepository.setLike(newsId: (news.newsId != null) ? news.newsId! : news.id!, status: status).then((value) { + emit(UpdateLikeAndDisLikeStatusSuccess(news, status == "1" ? true : false)); + }).catchError((e) { + ApiMessageAndCodeException apiMessageAndCodeException = e; + emit(UpdateLikeAndDisLikeStatusFailure(apiMessageAndCodeException.errorMessage.toString())); + }); + } +} diff --git a/news-app/lib/cubits/NewsByIdCubit.dart b/news-app/lib/cubits/NewsByIdCubit.dart new file mode 100644 index 00000000..f525be3c --- /dev/null +++ b/news-app/lib/cubits/NewsByIdCubit.dart @@ -0,0 +1,39 @@ +import 'package:news/data/models/NewsModel.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/NewsById/NewsByIdRepository.dart'; + +abstract class NewsByIdState {} + +class NewsByIdInitial extends NewsByIdState {} + +class NewsByIdFetchInProgress extends NewsByIdState {} + +class NewsByIdFetchSuccess extends NewsByIdState { + final List newsById; + + NewsByIdFetchSuccess({required this.newsById}); +} + +class NewsByIdFetchFailure extends NewsByIdState { + final String errorMessage; + + NewsByIdFetchFailure(this.errorMessage); +} + +class NewsByIdCubit extends Cubit { + final NewsByIdRepository _newsByIdRepository; + + NewsByIdCubit(this._newsByIdRepository) : super(NewsByIdInitial()); + + Future> getNewsById({required String newsId, required String langId}) async { + try { + emit(NewsByIdFetchInProgress()); + final result = await _newsByIdRepository.getNewsById(langId: langId, newsId: newsId); + emit(NewsByIdFetchSuccess(newsById: result['NewsById'])); + return result['NewsById']; + } catch (e) { + emit(NewsByIdFetchFailure(e.toString())); + return []; + } + } +} diff --git a/news-app/lib/cubits/NewsComment/deleteCommentCubit.dart b/news-app/lib/cubits/NewsComment/deleteCommentCubit.dart new file mode 100644 index 00000000..71f67223 --- /dev/null +++ b/news-app/lib/cubits/NewsComment/deleteCommentCubit.dart @@ -0,0 +1,37 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/NewsComment/DeleteComment/deleteCommRepository.dart'; +import 'package:news/utils/api.dart'; + +abstract class DeleteCommState {} + +class DeleteCommInitial extends DeleteCommState {} + +class DeleteCommInProgress extends DeleteCommState {} + +class DeleteCommSuccess extends DeleteCommState { + final String message; + + DeleteCommSuccess(this.message); +} + +class DeleteCommFailure extends DeleteCommState { + final String errorMessage; + + DeleteCommFailure(this.errorMessage); +} + +class DeleteCommCubit extends Cubit { + final DeleteCommRepository _deleteCommRepository; + + DeleteCommCubit(this._deleteCommRepository) : super(DeleteCommInitial()); + + void setDeleteComm({required String commId}) { + emit(DeleteCommInProgress()); + _deleteCommRepository.setDeleteComm(commId: commId).then((value) { + emit(DeleteCommSuccess(value["message"])); + }).catchError((e) { + ApiMessageAndCodeException apiMessageAndCodeException = e; + emit(DeleteCommFailure(apiMessageAndCodeException.errorMessage.toString())); + }); + } +} diff --git a/news-app/lib/cubits/NewsComment/flagCommentCubit.dart b/news-app/lib/cubits/NewsComment/flagCommentCubit.dart new file mode 100644 index 00000000..5eada905 --- /dev/null +++ b/news-app/lib/cubits/NewsComment/flagCommentCubit.dart @@ -0,0 +1,36 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/NewsComment/FlagComment/flagCommRepository.dart'; + +abstract class SetFlagState {} + +class SetFlagInitial extends SetFlagState {} + +class SetFlagFetchInProgress extends SetFlagState {} + +class SetFlagFetchSuccess extends SetFlagState { + String message; + + SetFlagFetchSuccess({required this.message}); +} + +class SetFlagFetchFailure extends SetFlagState { + final String errorMessage; + + SetFlagFetchFailure(this.errorMessage); +} + +class SetFlagCubit extends Cubit { + final SetFlagRepository _setFlagRepository; + + SetFlagCubit(this._setFlagRepository) : super(SetFlagInitial()); + + void setFlag({required String commId, required String newsId, required String message}) async { + try { + emit(SetFlagFetchInProgress()); + final result = await _setFlagRepository.setFlag(message: message, newsId: newsId, commId: commId); + emit(SetFlagFetchSuccess(message: result['message'])); + } catch (e) { + emit(SetFlagFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/NewsComment/likeAndDislikeCommCubit.dart b/news-app/lib/cubits/NewsComment/likeAndDislikeCommCubit.dart new file mode 100644 index 00000000..28849d29 --- /dev/null +++ b/news-app/lib/cubits/NewsComment/likeAndDislikeCommCubit.dart @@ -0,0 +1,41 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/CommentModel.dart'; +import 'package:news/data/repositories/NewsComment/LikeAndDislikeComment/likeAndDislikeCommRepository.dart'; +import 'package:news/utils/strings.dart'; + +abstract class LikeAndDislikeCommState {} + +class LikeAndDislikeCommInitial extends LikeAndDislikeCommState {} + +class LikeAndDislikeCommInProgress extends LikeAndDislikeCommState {} + +class LikeAndDislikeCommSuccess extends LikeAndDislikeCommState { + final CommentModel comment; + final bool wasLikeAndDislikeCommNewsProcess; + final bool fromLike; + + LikeAndDislikeCommSuccess(this.comment, this.wasLikeAndDislikeCommNewsProcess, this.fromLike); +} + +class LikeAndDislikeCommFailure extends LikeAndDislikeCommState { + final String errorMessage; + + LikeAndDislikeCommFailure(this.errorMessage); +} + +class LikeAndDislikeCommCubit extends Cubit { + final LikeAndDislikeCommRepository _likeAndDislikeCommRepository; + + LikeAndDislikeCommCubit(this._likeAndDislikeCommRepository) : super(LikeAndDislikeCommInitial()); + + void setLikeAndDislikeComm({required String langId, required String commId, required String status, required bool fromLike}) async { + try { + emit(LikeAndDislikeCommInProgress()); + final result = await _likeAndDislikeCommRepository.setLikeAndDislikeComm(langId: langId, commId: commId, status: status); + + (!result[ERROR]) ? emit(LikeAndDislikeCommSuccess(result['updatedComment'], true, fromLike)) : emit(LikeAndDislikeCommFailure(result[MESSAGE])); + } catch (e) { + emit(LikeAndDislikeCommFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/NewsComment/setCommentCubit.dart b/news-app/lib/cubits/NewsComment/setCommentCubit.dart new file mode 100644 index 00000000..308220da --- /dev/null +++ b/news-app/lib/cubits/NewsComment/setCommentCubit.dart @@ -0,0 +1,39 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/CommentModel.dart'; +import 'package:news/data/repositories/NewsComment/SetComment/setComRepository.dart'; +import 'package:news/utils/strings.dart'; + +abstract class SetCommentState {} + +class SetCommentInitial extends SetCommentState {} + +class SetCommentFetchInProgress extends SetCommentState {} + +class SetCommentFetchSuccess extends SetCommentState { + List setComment; + int total; + + SetCommentFetchSuccess({required this.setComment, required this.total}); +} + +class SetCommentFetchFailure extends SetCommentState { + final String errorMessage; + + SetCommentFetchFailure(this.errorMessage); +} + +class SetCommentCubit extends Cubit { + final SetCommentRepository _setCommentRepository; + + SetCommentCubit(this._setCommentRepository) : super(SetCommentInitial()); + + void setComment({required String parentId, required String newsId, required String message}) async { + emit(SetCommentFetchInProgress()); + try { + final result = await _setCommentRepository.setComment(message: message, newsId: newsId, parentId: parentId); + emit(SetCommentFetchSuccess(setComment: result['SetComment'], total: result[TOTAL])); + } catch (e) { + emit(SetCommentFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/UserPreferences/setUserPreferenceCatCubit.dart b/news-app/lib/cubits/UserPreferences/setUserPreferenceCatCubit.dart new file mode 100644 index 00000000..613dbe29 --- /dev/null +++ b/news-app/lib/cubits/UserPreferences/setUserPreferenceCatCubit.dart @@ -0,0 +1,37 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/SetUserPreferenceCat/setUserPrefCatRepository.dart'; + +abstract class SetUserPrefCatState {} + +class SetUserPrefCatInitial extends SetUserPrefCatState {} + +class SetUserPrefCatFetchInProgress extends SetUserPrefCatState {} + +class SetUserPrefCatFetchSuccess extends SetUserPrefCatState { + var setUserPrefCat; + + SetUserPrefCatFetchSuccess({required this.setUserPrefCat}); +} + +class SetUserPrefCatFetchFailure extends SetUserPrefCatState { + final String errorMessage; + + SetUserPrefCatFetchFailure(this.errorMessage); +} + +class SetUserPrefCatCubit extends Cubit { + final SetUserPrefCatRepository _setUserPrefCatRepository; + + SetUserPrefCatCubit(this._setUserPrefCatRepository) : super(SetUserPrefCatInitial()); + + void setUserPrefCat({required String catId}) async { + emit(SetUserPrefCatFetchInProgress()); + try { + final result = await _setUserPrefCatRepository.setUserPrefCat(catId: catId); + + emit(SetUserPrefCatFetchSuccess(setUserPrefCat: result['SetUserPrefCat'])); + } catch (e) { + emit(SetUserPrefCatFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/UserPreferences/userByCategoryCubit.dart b/news-app/lib/cubits/UserPreferences/userByCategoryCubit.dart new file mode 100644 index 00000000..162cc531 --- /dev/null +++ b/news-app/lib/cubits/UserPreferences/userByCategoryCubit.dart @@ -0,0 +1,36 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/UserByCategory/userByCatRepository.dart'; + +abstract class UserByCatState {} + +class UserByCatInitial extends UserByCatState {} + +class UserByCatFetchInProgress extends UserByCatState {} + +class UserByCatFetchSuccess extends UserByCatState { + var userByCat; + + UserByCatFetchSuccess({required this.userByCat}); +} + +class UserByCatFetchFailure extends UserByCatState { + final String errorMessage; + + UserByCatFetchFailure(this.errorMessage); +} + +class UserByCatCubit extends Cubit { + final UserByCatRepository _userByCatRepository; + + UserByCatCubit(this._userByCatRepository) : super(UserByCatInitial()); + + void getUserById() async { + emit(UserByCatFetchInProgress()); + try { + final result = await _userByCatRepository.getUserById(); + emit(UserByCatFetchSuccess(userByCat: result['UserByCat']['user_category'])); + } catch (e) { + emit(UserByCatFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/adSpacesNewsDetailsCubit.dart b/news-app/lib/cubits/adSpacesNewsDetailsCubit.dart new file mode 100644 index 00000000..8e4c062c --- /dev/null +++ b/news-app/lib/cubits/adSpacesNewsDetailsCubit.dart @@ -0,0 +1,41 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/adSpaceModel.dart'; +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +abstract class AdSpacesNewsDetailsState {} + +class AdSpacesNewsDetailsInitial extends AdSpacesNewsDetailsState {} + +class AdSpacesNewsDetailsFetchInProgress extends AdSpacesNewsDetailsState {} + +class AdSpacesNewsDetailsFetchSuccess extends AdSpacesNewsDetailsState { + final AdSpaceModel? adSpaceTopData; + final AdSpaceModel? adSpaceBottomData; + + AdSpacesNewsDetailsFetchSuccess({this.adSpaceTopData, this.adSpaceBottomData}); +} + +class AdSpacesNewsDetailsFetchFailure extends AdSpacesNewsDetailsState { + final String errorMessage; + + AdSpacesNewsDetailsFetchFailure(this.errorMessage); +} + +class AdSpacesNewsDetailsCubit extends Cubit { + AdSpacesNewsDetailsCubit() : super(AdSpacesNewsDetailsInitial()); + + void getAdspaceForNewsDetails({required String langId}) async { + emit(AdSpacesNewsDetailsFetchInProgress()); + try { + final body = {LANGUAGE_ID: langId}; + final Map result = await Api.sendApiRequest(body: body, url: Api.getAdsNewsDetailsApi); + final Map resultData = result[DATA]; + emit(AdSpacesNewsDetailsFetchSuccess( + adSpaceTopData: (resultData.containsKey('ad_spaces_top')) ? (AdSpaceModel.fromJson(resultData['ad_spaces_top'])) : null, + adSpaceBottomData: (resultData.containsKey('ad_spaces_bottom')) ? AdSpaceModel.fromJson(resultData['ad_spaces_bottom']) : null)); + } catch (e) { + emit(AdSpacesNewsDetailsFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/appLocalizationCubit.dart b/news-app/lib/cubits/appLocalizationCubit.dart new file mode 100644 index 00000000..51c4def5 --- /dev/null +++ b/news-app/lib/cubits/appLocalizationCubit.dart @@ -0,0 +1,26 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; + +class AppLocalizationState { + String languageCode; + String id; + int isRTL; + + AppLocalizationState( + this.languageCode, + this.id, + this.isRTL, + ); +} + +class AppLocalizationCubit extends Cubit { + final SettingsLocalDataRepository _settingsRepository; + + AppLocalizationCubit(this._settingsRepository) + : super(AppLocalizationState(_settingsRepository.getCurrentLanguageCode(), _settingsRepository.getCurrentLanguageId(), _settingsRepository.getCurrentLanguageRTL())); + + void changeLanguage(String lanCode, String lanId, int lanRTL) { + _settingsRepository.setLanguagePreferences(code: lanCode, id: lanId, rtl: lanRTL); + emit(AppLocalizationState(lanCode, lanId, lanRTL)); + } +} diff --git a/news-app/lib/cubits/appSystemSettingCubit.dart b/news-app/lib/cubits/appSystemSettingCubit.dart new file mode 100644 index 00000000..f213c945 --- /dev/null +++ b/news-app/lib/cubits/appSystemSettingCubit.dart @@ -0,0 +1,254 @@ +import 'dart:io'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/app/app.dart'; +import 'package:news/data/models/AppSystemSettingModel.dart'; +import 'package:news/data/repositories/AppSystemSetting/systemRepository.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/uiUtils.dart'; + +abstract class AppConfigurationState {} + +class AppConfigurationInitial extends AppConfigurationState { + @override + List get props => []; +} + +class AppConfigurationFetchInProgress extends AppConfigurationState { + @override + List get props => []; +} + +class AppConfigurationFetchSuccess extends AppConfigurationState { + final AppSystemSettingModel appConfiguration; + + AppConfigurationFetchSuccess({required this.appConfiguration}); + + @override + List get props => [appConfiguration]; +} + +class AppConfigurationFetchFailure extends AppConfigurationState { + final String errorMessage; + + AppConfigurationFetchFailure(this.errorMessage); + + @override + List get props => [errorMessage]; +} + +class AppConfigurationCubit extends Cubit { + final SystemRepository _systemRepository; + + AppConfigurationCubit(this._systemRepository) : super(AppConfigurationInitial()); + + fetchAppConfiguration() async { + emit(AppConfigurationFetchInProgress()); + try { + final appConfiguration = AppSystemSettingModel.fromJson(await _systemRepository.fetchSettings()); + emit(AppConfigurationFetchSuccess(appConfiguration: appConfiguration)); + } catch (e) { + emit(AppConfigurationFetchFailure(e.toString())); + } + } + + AppSystemSettingModel getAppConfiguration() { + return (state is AppConfigurationFetchSuccess) ? (state as AppConfigurationFetchSuccess).appConfiguration : AppSystemSettingModel.fromJson({}); + } + + String? getBreakingNewsMode() { + return ((state is AppConfigurationFetchSuccess)) ? getAppConfiguration().breakNewsMode : ""; + } + + String? getLiveStreamMode() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().liveStreamMode : ""; + } + + String? getCategoryMode() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().catMode : ""; + } + + String? getSubCatMode() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().subCatMode : ""; + } + + String? getCommentsMode() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().commentMode : ""; + } + + String? getInAppAdsMode() { + return (state is AppConfigurationFetchSuccess) ? ((Platform.isAndroid) ? getAppConfiguration().inAppAdsMode : getAppConfiguration().iosInAppAdsMode) : ""; + } + + String? getLocationWiseNewsMode() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().locationWiseNewsMode : ""; + } + + String? getWeatherMode() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().weatherMode : ""; + } + + String? getMaintenanceMode() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().maintenanceMode : "0"; + } + + String? checkAdsType() { + if (getInAppAdsMode() == "1") { + return (Platform.isIOS) ? getIOSAdsType() : getAdsType(); + } + return null; + } + + String? getAdsType() { + if (state is AppConfigurationFetchSuccess) { + switch (getAppConfiguration().adsType) { + case "1": + return "google"; + case "3": + return "unity"; + default: + return ""; + } + } + return ""; + } + + String? getIOSAdsType() { + if (state is AppConfigurationFetchSuccess) { + switch (getAppConfiguration().iosAdsType) { + case "1": + return "google"; + case "3": + return "unity"; + default: + return ""; + } + } + return ""; + } + + String? bannerId() { + if (state is AppConfigurationFetchSuccess) { + if (Platform.isAndroid && getInAppAdsMode() != "0") { + if (getAdsType() == "google") return getAppConfiguration().goBannerId; + if (getAdsType() == "unity") return getAppConfiguration().unityBannerId; + } + if (Platform.isIOS && getInAppAdsMode() != "0") { + if (getIOSAdsType() == "google") return getAppConfiguration().goIOSBannerId; + if (getIOSAdsType() == "unity") return getAppConfiguration().unityIOSBannerId; + } + } + return ""; + } + + String? rewardId() { + if (state is AppConfigurationFetchSuccess) { + if (Platform.isAndroid && getInAppAdsMode() != "0") { + if (getAdsType() == "google") return getAppConfiguration().goRewardedId; + if (getAdsType() == "unity") return getAppConfiguration().unityRewardedId; + } + if (Platform.isIOS && getInAppAdsMode() != "0") { + if (getIOSAdsType() == "google") return getAppConfiguration().goIOSRewardedId; + if (getIOSAdsType() == "unity") return getAppConfiguration().unityIOSRewardedId; + } + } + return ""; + } + + String? nativeId() { + if (state is AppConfigurationFetchSuccess) { + if (Platform.isAndroid && getInAppAdsMode() != "0") { + if (getAdsType() == "google") return getAppConfiguration().goNativeId; + if (getAdsType() == "unity") return ""; //no native ads in unity + } + if (Platform.isIOS && getInAppAdsMode() != "0") { + if (getIOSAdsType() == "google") return getAppConfiguration().goIOSNativeId; + if (getIOSAdsType() == "unity") return ""; //no native ads in unity + } + } + return ""; + } + + String? interstitialId() { + if (state is AppConfigurationFetchSuccess) { + if (Platform.isAndroid && getInAppAdsMode() != "0") { + if (getAdsType() == "google") return getAppConfiguration().goInterId; + if (getAdsType() == "unity") return getAppConfiguration().unityInterId; + } + if (Platform.isIOS && getInAppAdsMode() != "0") { + if (getIOSAdsType() == "google") return getAppConfiguration().goIOSInterId; + if (getIOSAdsType() == "unity") return getAppConfiguration().unityIOSInterId; + } + } + return ""; + } + + String? unityGameId() { + return (Platform.isAndroid) ? getAppConfiguration().gameId : getAppConfiguration().iosGameId; + } + + String? getRSSFeedMode() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().rssFeedMode : ""; + } + + String? getMobileLoginMode() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().mobileLoginMode : ""; + } + + String? getCountryCode() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().countryCode : "IN"; //India bydefault + } + + String? getShareAppText() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().shareAppText : ""; + } + + String? getAppstoreId() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().appstoreId : ""; + } + + String? getiOSAppLink() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().iosAppLink : ""; + } + + String? getAndroidAppLink() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().androidAppLink : ""; + } + + VideoViewType? getVideoTypePreference() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().videoTypePreference : VideoViewType.normal; + } + + String? getForceUpdateMode() { + return (state is AppConfigurationFetchSuccess) ? getAppConfiguration().forceUpdateMode : ""; + } + + bool needsUpdate(String enforceVersion) { + final List currentVersion = packageInfo.version.split('.').map((String number) => int.parse(number)).toList(); + final List enforcedVersion = enforceVersion.split('.').map((String number) => int.parse(number)).toList(); + + for (int i = 0; i < 3; i++) { + if (enforcedVersion[i] > currentVersion[i]) { + return true; + } else if (currentVersion[i] > enforcedVersion[i]) { + return false; + } + } + return false; + } + + String getGeminiAPiKey() { + return ((state is AppConfigurationFetchSuccess)) ? UiUtils.decryptKey(geminiKey: getAppConfiguration().googleGeminiApiKey) : ""; + } + + bool isUpdateRequired() { + if (state is AppConfigurationFetchSuccess) { + AppSystemSettingModel appConfig = (state as AppConfigurationFetchSuccess).appConfiguration; + if (defaultTargetPlatform == TargetPlatform.android && needsUpdate(appConfig.androidAppVersion ?? "") || + defaultTargetPlatform == TargetPlatform.iOS && needsUpdate(appConfig.iosAppVersion ?? "")) { + return true; + } + } + return false; + } +} diff --git a/news-app/lib/cubits/breakingNewsCubit.dart b/news-app/lib/cubits/breakingNewsCubit.dart new file mode 100644 index 00000000..9be06d4f --- /dev/null +++ b/news-app/lib/cubits/breakingNewsCubit.dart @@ -0,0 +1,40 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/data/repositories/BreakingNews/breakNewsRepository.dart'; +import 'package:news/utils/strings.dart'; + +abstract class BreakingNewsState {} + +class BreakingNewsInitial extends BreakingNewsState {} + +class BreakingNewsFetchInProgress extends BreakingNewsState {} + +class BreakingNewsFetchSuccess extends BreakingNewsState { + final List breakingNews; + + BreakingNewsFetchSuccess({required this.breakingNews}); +} + +class BreakingNewsFetchFailure extends BreakingNewsState { + final String errorMessage; + + BreakingNewsFetchFailure(this.errorMessage); +} + +class BreakingNewsCubit extends Cubit { + final BreakingNewsRepository _breakingNewsRepository; + + BreakingNewsCubit(this._breakingNewsRepository) : super(BreakingNewsInitial()); + + Future> getBreakingNews({required String langId}) async { + emit(BreakingNewsFetchInProgress()); + try { + final result = await _breakingNewsRepository.getBreakingNews(langId: langId); + (!result[ERROR]) ? emit(BreakingNewsFetchSuccess(breakingNews: result['BreakingNews'])) : emit(BreakingNewsFetchFailure(result[MESSAGE])); + return (!result[ERROR]) ? result['BreakingNews'] : []; + } catch (e) { + emit(BreakingNewsFetchFailure(e.toString())); + return []; + } + } +} diff --git a/news-app/lib/cubits/categoryCubit.dart b/news-app/lib/cubits/categoryCubit.dart new file mode 100644 index 00000000..100e4d04 --- /dev/null +++ b/news-app/lib/cubits/categoryCubit.dart @@ -0,0 +1,112 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/CategoryModel.dart'; +import 'package:news/data/repositories/Category/categoryRepository.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +abstract class CategoryState {} + +class CategoryInitial extends CategoryState {} + +class CategoryFetchInProgress extends CategoryState {} + +class CategoryFetchSuccess extends CategoryState { + final List category; + final int totalCategoryCount; + final bool hasMoreFetchError; + final bool hasMore; + + CategoryFetchSuccess( + {required this.category, + required this.totalCategoryCount, + required this.hasMoreFetchError, + required this.hasMore}); +} + +class CategoryFetchFailure extends CategoryState { + final String errorMessage; + + CategoryFetchFailure(this.errorMessage); +} + +class CategoryCubit extends Cubit { + final CategoryRepository _categoryRepository; + + CategoryCubit(this._categoryRepository) : super(CategoryInitial()); + + void getCategory({required String langId}) async { + try { + emit(CategoryFetchInProgress()); + int catLimit = 20; + final result = await _categoryRepository.getCategory( + limit: catLimit.toString(), offset: "0", langId: langId); + (!result[ERROR]) + ? emit(CategoryFetchSuccess( + category: result['Category'], + totalCategoryCount: result[TOTAL], + hasMoreFetchError: false, + hasMore: (result['Category'] as List).length < + result[TOTAL])) + : emit(CategoryFetchFailure(result[MESSAGE])); + } catch (e) { + emit(CategoryFetchFailure(e.toString())); + } + } + + // Load categories if not already loaded or failed + void loadIfFailed({required String langId}) { + if (state is CategoryInitial || state is CategoryFetchFailure) { + getCategory(langId: langId); + } + } + + bool hasMoreCategory() { + return (state is CategoryFetchSuccess) + ? (state as CategoryFetchSuccess).hasMore + : false; + } + + void getMoreCategory({required String langId}) async { + if (state is CategoryFetchSuccess) { + try { + final result = await _categoryRepository.getCategory( + limit: limitOfAPIData.toString(), + langId: langId, + offset: (state as CategoryFetchSuccess).category.length.toString()); + if (!result[ERROR]) { + List updatedResults = + (state as CategoryFetchSuccess).category; + updatedResults.addAll(result['Category'] as List); + emit(CategoryFetchSuccess( + category: updatedResults, + totalCategoryCount: result[TOTAL], + hasMoreFetchError: false, + hasMore: updatedResults.length < result[TOTAL])); + } else { + emit(CategoryFetchFailure(result[MESSAGE])); + } + } catch (e) { + emit(CategoryFetchSuccess( + category: (state as CategoryFetchSuccess).category, + hasMoreFetchError: true, + totalCategoryCount: + (state as CategoryFetchSuccess).totalCategoryCount, + hasMore: (state as CategoryFetchSuccess).hasMore)); + } + } + } + + List getCatList() { + return (state is CategoryFetchSuccess) + ? (state as CategoryFetchSuccess).category + : []; + } + + int getCategoryIndex({required String categoryName}) { + return (state is CategoryFetchSuccess) + ? (state as CategoryFetchSuccess) + .category + .indexWhere((element) => element.categoryName == categoryName) + : 0; + } +} diff --git a/news-app/lib/cubits/commentNewsCubit.dart b/news-app/lib/cubits/commentNewsCubit.dart new file mode 100644 index 00000000..36169274 --- /dev/null +++ b/news-app/lib/cubits/commentNewsCubit.dart @@ -0,0 +1,111 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/CommentModel.dart'; +import 'package:news/data/repositories/CommentNews/commNewsRepository.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +abstract class CommentNewsState {} + +class CommentNewsInitial extends CommentNewsState {} + +class CommentNewsFetchInProgress extends CommentNewsState {} + +class CommentNewsFetchSuccess extends CommentNewsState { + final List commentNews; + final int totalCommentNewsCount; + final bool hasMoreFetchError; + final bool hasMore; + + CommentNewsFetchSuccess({required this.commentNews, required this.totalCommentNewsCount, required this.hasMoreFetchError, required this.hasMore}); + CommentNewsFetchSuccess copyWith({List? commentNews, int? totalCommentNewsCount, bool? hasMoreFetchError, bool? hasMore}) { + return CommentNewsFetchSuccess( + commentNews: commentNews ?? this.commentNews, + totalCommentNewsCount: totalCommentNewsCount ?? this.totalCommentNewsCount, + hasMoreFetchError: hasMoreFetchError ?? this.hasMoreFetchError, + hasMore: hasMore ?? this.hasMore); + } +} + +class CommentNewsFetchFailure extends CommentNewsState { + final String errorMessage; + + CommentNewsFetchFailure(this.errorMessage); +} + +class CommentNewsCubit extends Cubit { + final CommentNewsRepository _commentNewsRepository; + + CommentNewsCubit(this._commentNewsRepository) : super(CommentNewsInitial()); + + void getCommentNews({required String newsId}) async { + try { + emit(CommentNewsFetchInProgress()); + final result = await _commentNewsRepository.getCommentNews(limit: limitOfAPIData.toString(), offset: "0", newsId: newsId); + + (!result[ERROR]) + ? emit(CommentNewsFetchSuccess( + commentNews: result['CommentNews'], totalCommentNewsCount: result[TOTAL], hasMoreFetchError: false, hasMore: ((result['CommentNews'] as List).length) < result[TOTAL])) + : emit(CommentNewsFetchFailure(result[MESSAGE])); + } catch (e) { + emit(CommentNewsFetchFailure(e.toString())); + } + } + + bool hasMoreCommentNews() { + return (state is CommentNewsFetchSuccess) ? (state as CommentNewsFetchSuccess).hasMore : false; + } + + void getMoreCommentNews({required String newsId}) async { + if (state is CommentNewsFetchSuccess) { + try { + final result = await _commentNewsRepository.getCommentNews(limit: limitOfAPIData.toString(), newsId: newsId, offset: (state as CommentNewsFetchSuccess).commentNews.length.toString()); + List updatedResults = (state as CommentNewsFetchSuccess).commentNews; + updatedResults.addAll(result['CommentNews'] as List); + emit(CommentNewsFetchSuccess(commentNews: updatedResults, totalCommentNewsCount: result[TOTAL], hasMoreFetchError: false, hasMore: updatedResults.length < result[TOTAL])); + } catch (e) { + emit(CommentNewsFetchSuccess( + commentNews: (state as CommentNewsFetchSuccess).commentNews, + hasMoreFetchError: true, + totalCommentNewsCount: (state as CommentNewsFetchSuccess).totalCommentNewsCount, + hasMore: (state as CommentNewsFetchSuccess).hasMore)); + } + } + } + + emitSuccessState(List commments) { + emit((state as CommentNewsFetchSuccess).copyWith(commentNews: commments)); + } + + void commentUpdateList(List commentList, int total) { + if (state is CommentNewsFetchSuccess || state is CommentNewsFetchFailure) { + bool haseMore = (state is CommentNewsFetchSuccess) ? (state as CommentNewsFetchSuccess).hasMore : false; + emit(CommentNewsFetchSuccess(commentNews: commentList, hasMore: haseMore, hasMoreFetchError: false, totalCommentNewsCount: total)); + } + } + + void deleteComment(int index) { + if (state is CommentNewsFetchSuccess) { + List commentList = List.from((state as CommentNewsFetchSuccess).commentNews)..removeAt(index); + + emit(CommentNewsFetchSuccess( + commentNews: commentList, + hasMore: (state as CommentNewsFetchSuccess).hasMore, + hasMoreFetchError: false, + totalCommentNewsCount: (state as CommentNewsFetchSuccess).totalCommentNewsCount - 1)); + } + } + + void deleteCommentReply(String commentId, int index) { + if (state is CommentNewsFetchSuccess) { + List commentList = (state as CommentNewsFetchSuccess).commentNews; + commentList.forEach((element) { + if (element.id! == commentId) { + int commIndex = commentList.indexWhere((model) => model.id == commentId); + commentList[commIndex].replyComList!.removeAt(index); + } + }); + emit(CommentNewsFetchSuccess( + commentNews: commentList, hasMore: (state as CommentNewsFetchSuccess).hasMore, hasMoreFetchError: false, totalCommentNewsCount: (state as CommentNewsFetchSuccess).totalCommentNewsCount)); + } + } +} diff --git a/news-app/lib/cubits/deleteImageId.dart b/news-app/lib/cubits/deleteImageId.dart new file mode 100644 index 00000000..93b07334 --- /dev/null +++ b/news-app/lib/cubits/deleteImageId.dart @@ -0,0 +1,37 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../data/repositories/DeleteImageId/deleteImageRepository.dart'; +import '../utils/api.dart'; + +abstract class DeleteImageState {} + +class DeleteImageInitial extends DeleteImageState {} + +class DeleteImageInProgress extends DeleteImageState {} + +class DeleteImageSuccess extends DeleteImageState { + final String message; + + DeleteImageSuccess(this.message); +} + +class DeleteImageFailure extends DeleteImageState { + final String errorMessage; + + DeleteImageFailure(this.errorMessage); +} + +class DeleteImageCubit extends Cubit { + final DeleteImageRepository _deleteImageRepository; + + DeleteImageCubit(this._deleteImageRepository) : super(DeleteImageInitial()); + + void setDeleteImage({required String imageId}) { + emit(DeleteImageInProgress()); + _deleteImageRepository.setDeleteImage(imageId: imageId).then((value) { + emit(DeleteImageSuccess(value["message"])); + }).catchError((e) { + ApiMessageAndCodeException apiMessageAndCodeException = e; + emit(DeleteImageFailure(apiMessageAndCodeException.errorMessage.toString())); + }); + } +} diff --git a/news-app/lib/cubits/deleteUserNewsCubit.dart b/news-app/lib/cubits/deleteUserNewsCubit.dart new file mode 100644 index 00000000..d444d1b0 --- /dev/null +++ b/news-app/lib/cubits/deleteUserNewsCubit.dart @@ -0,0 +1,44 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../data/repositories/DeleteUserNews/deleteUserNewsRepository.dart'; +import '../utils/api.dart'; + +abstract class DeleteUserNewsState {} + +class DeleteUserNewsInitial extends DeleteUserNewsState {} + +class DeleteUserNewsInProgress extends DeleteUserNewsState {} + +class DeleteUserNewsSuccess extends DeleteUserNewsState { + final String message; + + DeleteUserNewsSuccess(this.message); +} + +class DeleteUserNewsFailure extends DeleteUserNewsState { + final String errorMessage; + + DeleteUserNewsFailure(this.errorMessage); +} + +class DeleteUserNewsCubit extends Cubit { + final DeleteUserNewsRepository _deleteUserNewsRepository; + + DeleteUserNewsCubit(this._deleteUserNewsRepository) : super(DeleteUserNewsInitial()); + + void setDeleteUserNews({ + required String newsId, + }) { + emit(DeleteUserNewsInProgress()); + _deleteUserNewsRepository + .setDeleteUserNews( + newsId: newsId, + ) + .then((value) { + emit(DeleteUserNewsSuccess(value["message"])); + }).catchError((e) { + ApiMessageAndCodeException apiMessageAndCodeException = e; + emit(DeleteUserNewsFailure(apiMessageAndCodeException.errorMessage.toString())); + }); + } +} diff --git a/news-app/lib/cubits/featureSectionCubit.dart b/news-app/lib/cubits/featureSectionCubit.dart new file mode 100644 index 00000000..1acea8c2 --- /dev/null +++ b/news-app/lib/cubits/featureSectionCubit.dart @@ -0,0 +1,71 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/FeatureSectionModel.dart'; +import 'package:news/data/repositories/FeatureSection/sectionRepository.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +abstract class SectionState {} + +class SectionInitial extends SectionState {} + +class SectionFetchInProgress extends SectionState {} + +class SectionFetchSuccess extends SectionState { + final List section; + final int totalCount; + final bool hasMoreFetchError; + final bool hasMore; + SectionFetchSuccess({required this.section, required this.totalCount, required this.hasMore, required this.hasMoreFetchError}); +} + +class SectionFetchFailure extends SectionState { + final String errorMessage; + + SectionFetchFailure(this.errorMessage); +} + +class SectionCubit extends Cubit { + final SectionRepository _sectionRepository; + + SectionCubit(this._sectionRepository) : super(SectionInitial()); + + void getSection({required String langId, String? latitude, String? longitude}) async { + try { + emit(SectionFetchInProgress()); + final result = await _sectionRepository.getSection(langId: langId, latitude: latitude, longitude: longitude, limit: limitOfAPIData.toString(), offset: "0", sectionOffset: "0"); + (!result[ERROR]) + ? emit(SectionFetchSuccess( + section: result['Section'], + totalCount: result[TOTAL], + hasMore: result['Section'].length < result[TOTAL], + hasMoreFetchError: false, + )) + : emit(SectionFetchFailure(result[MESSAGE])); + } catch (e) { + emit(SectionFetchFailure(e.toString())); + } + } + + bool hasMoreSections() { + return (state is SectionFetchSuccess) ? (state as SectionFetchSuccess).hasMore : false; + } + + void getMoreSections({required String langId, String? latitude, String? longitude}) async { + if (state is SectionFetchSuccess) { + try { + final result = await _sectionRepository.getSection( + langId: langId, latitude: latitude, longitude: longitude, limit: limitOfAPIData.toString(), offset: "0", sectionOffset: (state as SectionFetchSuccess).section.length.toString()); + if (!result[ERROR]) { + List updatedResults = (state as SectionFetchSuccess).section; + updatedResults.addAll(result['Section'] as List); + emit(SectionFetchSuccess(section: updatedResults, totalCount: result[TOTAL], hasMoreFetchError: false, hasMore: updatedResults.length < result[TOTAL])); + } else { + emit(SectionFetchFailure(result[MESSAGE])); + } + } catch (e) { + emit(SectionFetchSuccess( + section: (state as SectionFetchSuccess).section, hasMoreFetchError: true, totalCount: (state as SectionFetchSuccess).totalCount, hasMore: (state as SectionFetchSuccess).hasMore)); + } + } + } +} diff --git a/news-app/lib/cubits/generalNewsCubit.dart b/news-app/lib/cubits/generalNewsCubit.dart new file mode 100644 index 00000000..e758e086 --- /dev/null +++ b/news-app/lib/cubits/generalNewsCubit.dart @@ -0,0 +1,77 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +abstract class GeneralNewsState {} + +class GeneralNewsInitial extends GeneralNewsState {} + +class GeneralNewsFetchInProgress extends GeneralNewsState {} + +class GeneralNewsFetchSuccess extends GeneralNewsState { + final List generalNews; + final int totalCount; + final bool hasMoreFetchError; + final bool hasMore; + + GeneralNewsFetchSuccess({required this.totalCount, required this.hasMoreFetchError, required this.hasMore, required this.generalNews}); +} + +class GeneralNewsFetchFailure extends GeneralNewsState { + final String errorMessage; + + GeneralNewsFetchFailure(this.errorMessage); +} + +class GeneralNewsCubit extends Cubit { + GeneralNewsCubit() : super(GeneralNewsInitial()); + + Future getGeneralNews({required String langId, String? latitude, String? longitude, int? offset, String? newsSlug}) async { + emit(GeneralNewsInitial()); + try { + final body = {LANGUAGE_ID: langId, LIMIT: 20}; + + if (latitude != null && latitude != "null") body[LATITUDE] = latitude; + if (longitude != null && longitude != "null") body[LONGITUDE] = longitude; + if (offset != null) body[OFFSET] = offset; + if (newsSlug != null && newsSlug != "null" && newsSlug.trim().isNotEmpty) body[SLUG] = newsSlug; // used in case of native link , details screen redirection + + final result = await Api.sendApiRequest(body: body, url: Api.getNewsApi); + if (!result[ERROR]) { + emit(GeneralNewsFetchSuccess( + generalNews: (result[DATA] as List).map((e) => NewsModel.fromJson(e)).toList(), totalCount: result[TOTAL], hasMoreFetchError: false, hasMore: result[DATA].length < result[TOTAL])); + } else { + emit(GeneralNewsFetchFailure(result[MESSAGE])); + } + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } + + bool hasMoreGeneralNews() { + return (state is GeneralNewsFetchSuccess) ? (state as GeneralNewsFetchSuccess).hasMore : false; + } + + void getMoreGeneralNews({required String langId, String? latitude, String? longitude}) async { + if (state is GeneralNewsFetchSuccess) { + try { + final result = await getGeneralNews(langId: langId, latitude: latitude, longitude: longitude, offset: (state as GeneralNewsFetchSuccess).generalNews.length); + if (!result[ERROR]) { + List updatedResults = (state as GeneralNewsFetchSuccess).generalNews; + updatedResults.addAll((result[DATA] as List).map((e) => NewsModel.fromJson(e)).toList()); + emit(GeneralNewsFetchSuccess(generalNews: updatedResults, totalCount: result[TOTAL], hasMoreFetchError: false, hasMore: updatedResults.length < result[TOTAL])); + } else { + emit(GeneralNewsFetchFailure(result[MESSAGE])); + } + } catch (e) { + emit(GeneralNewsFetchSuccess( + generalNews: (state as GeneralNewsFetchSuccess).generalNews, + hasMoreFetchError: true, + totalCount: (state as GeneralNewsFetchSuccess).totalCount, + hasMore: (state as GeneralNewsFetchSuccess).hasMore)); + } + } + } +} diff --git a/news-app/lib/cubits/getSurveyAnswerCubit.dart b/news-app/lib/cubits/getSurveyAnswerCubit.dart new file mode 100644 index 00000000..545b0191 --- /dev/null +++ b/news-app/lib/cubits/getSurveyAnswerCubit.dart @@ -0,0 +1,37 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/GetSurveyAnswer/getSurveyAnsRepository.dart'; + +abstract class GetSurveyAnsState {} + +class GetSurveyAnsInitial extends GetSurveyAnsState {} + +class GetSurveyAnsFetchInProgress extends GetSurveyAnsState {} + +class GetSurveyAnsFetchSuccess extends GetSurveyAnsState { + final List getSurveyAns; + + GetSurveyAnsFetchSuccess({required this.getSurveyAns}); +} + +class GetSurveyAnsFetchFailure extends GetSurveyAnsState { + final String errorMessage; + + GetSurveyAnsFetchFailure(this.errorMessage); +} + +class GetSurveyAnsCubit extends Cubit { + final GetSurveyAnsRepository _getSurveyAnsRepository; + + GetSurveyAnsCubit(this._getSurveyAnsRepository) : super(GetSurveyAnsInitial()); + + void getSurveyAns({required String langId}) async { + try { + emit(GetSurveyAnsFetchInProgress()); + final result = await _getSurveyAnsRepository.getSurveyAns(langId: langId); + emit(GetSurveyAnsFetchSuccess(getSurveyAns: result['GetSurveyAns'])); + } catch (e) { + emit(GetSurveyAnsFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/getUserDataByIdCubit.dart b/news-app/lib/cubits/getUserDataByIdCubit.dart new file mode 100644 index 00000000..92ac2dc0 --- /dev/null +++ b/news-app/lib/cubits/getUserDataByIdCubit.dart @@ -0,0 +1,35 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/GetUserById/getUserByIdRepository.dart'; + +abstract class GetUserByIdState {} + +class GetUserByIdInitial extends GetUserByIdState {} + +class GetUserByIdFetchInProgress extends GetUserByIdState {} + +class GetUserByIdFetchSuccess extends GetUserByIdState { + var result; + + GetUserByIdFetchSuccess({required this.result}); +} + +class GetUserByIdFetchFailure extends GetUserByIdState { + final String errorMessage; + + GetUserByIdFetchFailure(this.errorMessage); +} + +class GetUserByIdCubit extends Cubit { + final GetUserByIdRepository _getUserByIdRepository; + + GetUserByIdCubit(this._getUserByIdRepository) : super(GetUserByIdInitial()); + + void getUserById() { + emit(GetUserByIdFetchInProgress()); + _getUserByIdRepository.getUserById().then((value) { + emit(GetUserByIdFetchSuccess(result: value)); + }).catchError((e) { + emit(GetUserByIdFetchFailure(e.toString())); + }); + } +} diff --git a/news-app/lib/cubits/getUserDraftedNewsCubit.dart b/news-app/lib/cubits/getUserDraftedNewsCubit.dart new file mode 100644 index 00000000..b00269c5 --- /dev/null +++ b/news-app/lib/cubits/getUserDraftedNewsCubit.dart @@ -0,0 +1,76 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +abstract class GetUserDraftedNewsState {} + +class GetUserDraftedNewsInitial extends GetUserDraftedNewsState {} + +class GetUserDraftedNewsFetchInProgress extends GetUserDraftedNewsState {} + +class GetUserDraftedNewsFetchSuccess extends GetUserDraftedNewsState { + final List GetUserDraftedNews; + final int totalGetUserDraftedNewsCount; + final bool hasMoreFetchError; + final bool hasMore; + + GetUserDraftedNewsFetchSuccess({required this.GetUserDraftedNews, required this.totalGetUserDraftedNewsCount, required this.hasMoreFetchError, required this.hasMore}); +} + +class GetUserDraftedNewsFetchFailure extends GetUserDraftedNewsState { + final String errorMessage; + + GetUserDraftedNewsFetchFailure(this.errorMessage); +} + +class GetUserDraftedNewsCubit extends Cubit { + GetUserDraftedNewsCubit() : super(GetUserDraftedNewsInitial()); + + void getGetUserDraftedNews({int? userId}) async { + try { + emit(GetUserDraftedNewsFetchInProgress()); + final apiUrl = "${Api.getDraftNewsApi}/${userId}"; + final result = await Api.sendApiRequest(body: null, url: apiUrl); + (!result[ERROR]) + ? emit(GetUserDraftedNewsFetchSuccess( + GetUserDraftedNews: (result[DATA][NEWS][DATA] as List).map((e) => NewsModel.fromJson(e)).toList(), + totalGetUserDraftedNewsCount: result[DATA][NEWS][TOTAL], + hasMoreFetchError: false, + hasMore: (result[DATA][NEWS][DATA] as List).length < result[DATA][NEWS][TOTAL])) + : emit(GetUserDraftedNewsFetchFailure(result[MESSAGE])); + } catch (e) { + emit(GetUserDraftedNewsFetchFailure(e.toString())); + } + } + + bool hasMoreGetUserDraftedNews() { + return (state is GetUserDraftedNewsFetchSuccess) ? (state as GetUserDraftedNewsFetchSuccess).hasMore : false; + } + + void deleteNews(int index) { + if (state is GetUserDraftedNewsFetchSuccess) { + List newsList = List.from((state as GetUserDraftedNewsFetchSuccess).GetUserDraftedNews)..removeAt(index); + + emit(GetUserDraftedNewsFetchSuccess( + GetUserDraftedNews: newsList, + hasMore: (state as GetUserDraftedNewsFetchSuccess).hasMore, + hasMoreFetchError: false, + totalGetUserDraftedNewsCount: (state as GetUserDraftedNewsFetchSuccess).totalGetUserDraftedNewsCount - 1)); + } + } + + void deleteImageId(int index) { + if (state is GetUserDraftedNewsFetchSuccess) { + List newsList = (state as GetUserDraftedNewsFetchSuccess).GetUserDraftedNews; + + newsList[index].imageDataList!.removeAt(index); + + emit(GetUserDraftedNewsFetchSuccess( + GetUserDraftedNews: newsList, + hasMore: (state as GetUserDraftedNewsFetchSuccess).hasMore, + hasMoreFetchError: false, + totalGetUserDraftedNewsCount: (state as GetUserDraftedNewsFetchSuccess).totalGetUserDraftedNewsCount)); + } + } +} diff --git a/news-app/lib/cubits/getUserNewsCubit.dart b/news-app/lib/cubits/getUserNewsCubit.dart new file mode 100644 index 00000000..4e3f31cc --- /dev/null +++ b/news-app/lib/cubits/getUserNewsCubit.dart @@ -0,0 +1,87 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/GetUserNews/getUserNewsRepository.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +abstract class GetUserNewsState {} + +class GetUserNewsInitial extends GetUserNewsState {} + +class GetUserNewsFetchInProgress extends GetUserNewsState {} + +class GetUserNewsFetchSuccess extends GetUserNewsState { + final List getUserNews; + final int totalGetUserNewsCount; + final bool hasMoreFetchError; + final bool hasMore; + + GetUserNewsFetchSuccess({required this.getUserNews, required this.totalGetUserNewsCount, required this.hasMoreFetchError, required this.hasMore}); +} + +class GetUserNewsFetchFailure extends GetUserNewsState { + final String errorMessage; + + GetUserNewsFetchFailure(this.errorMessage); +} + +class GetUserNewsCubit extends Cubit { + final GetUserNewsRepository _getUserNewsRepository; + + GetUserNewsCubit(this._getUserNewsRepository) : super(GetUserNewsInitial()); + + void getGetUserNews({String? latitude, String? longitude}) async { + try { + emit(GetUserNewsFetchInProgress()); + final result = await _getUserNewsRepository.getGetUserNews(limit: limitOfAPIData.toString(), offset: "0", latitude: latitude, longitude: longitude); + (!result[ERROR]) + ? emit(GetUserNewsFetchSuccess( + getUserNews: result['GetUserNews'], totalGetUserNewsCount: result[TOTAL], hasMoreFetchError: false, hasMore: (result['GetUserNews'] as List).length < result[TOTAL])) + : emit(GetUserNewsFetchFailure(result[MESSAGE])); + } catch (e) { + emit(GetUserNewsFetchFailure(e.toString())); + } + } + + bool hasMoreGetUserNews() { + return (state is GetUserNewsFetchSuccess) ? (state as GetUserNewsFetchSuccess).hasMore : false; + } + + void getMoreGetUserNews({String? latitude, String? longitude}) async { + if (state is GetUserNewsFetchSuccess) { + try { + final result = await _getUserNewsRepository.getGetUserNews( + limit: limitOfAPIData.toString(), offset: (state as GetUserNewsFetchSuccess).getUserNews.length.toString(), latitude: latitude, longitude: longitude); + List updatedResults = (state as GetUserNewsFetchSuccess).getUserNews; + updatedResults.addAll(result['GetUserNews'] as List); + emit(GetUserNewsFetchSuccess(getUserNews: updatedResults, totalGetUserNewsCount: result[TOTAL], hasMoreFetchError: false, hasMore: updatedResults.length < result[TOTAL])); + } catch (e) { + emit(GetUserNewsFetchSuccess( + getUserNews: (state as GetUserNewsFetchSuccess).getUserNews, + hasMoreFetchError: true, + totalGetUserNewsCount: (state as GetUserNewsFetchSuccess).totalGetUserNewsCount, + hasMore: (state as GetUserNewsFetchSuccess).hasMore)); + } + } + } + + void deleteNews(int index) { + if (state is GetUserNewsFetchSuccess) { + List newsList = List.from((state as GetUserNewsFetchSuccess).getUserNews)..removeAt(index); + + emit(GetUserNewsFetchSuccess( + getUserNews: newsList, hasMore: (state as GetUserNewsFetchSuccess).hasMore, hasMoreFetchError: false, totalGetUserNewsCount: (state as GetUserNewsFetchSuccess).totalGetUserNewsCount - 1)); + } + } + + void deleteImageId(int index) { + if (state is GetUserNewsFetchSuccess) { + List newsList = (state as GetUserNewsFetchSuccess).getUserNews; + + newsList[index].imageDataList!.removeAt(index); + + emit(GetUserNewsFetchSuccess( + getUserNews: newsList, hasMore: (state as GetUserNewsFetchSuccess).hasMore, hasMoreFetchError: false, totalGetUserNewsCount: (state as GetUserNewsFetchSuccess).totalGetUserNewsCount)); + } + } +} diff --git a/news-app/lib/cubits/languageCubit.dart b/news-app/lib/cubits/languageCubit.dart new file mode 100644 index 00000000..a6a6c83a --- /dev/null +++ b/news-app/lib/cubits/languageCubit.dart @@ -0,0 +1,47 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/appLanguageModel.dart'; +import 'package:news/data/repositories/language/languageRepository.dart'; + +abstract class LanguageState {} + +class LanguageInitial extends LanguageState {} + +class LanguageFetchInProgress extends LanguageState {} + +class LanguageFetchSuccess extends LanguageState { + final List language; + + LanguageFetchSuccess({required this.language}); +} + +class LanguageFetchFailure extends LanguageState { + final String errorMessage; + + LanguageFetchFailure(this.errorMessage); +} + +class LanguageCubit extends Cubit { + final LanguageRepository _languageRepository; + + LanguageCubit(this._languageRepository) : super(LanguageInitial()); + + Future> getLanguage() async { + try { + emit(LanguageFetchInProgress()); + final result = await _languageRepository.getLanguage(); + emit(LanguageFetchSuccess(language: result['Language'])); + return result['Language']; + } catch (e) { + emit(LanguageFetchFailure(e.toString())); + return []; + } + } + + List langList() { + return (state is LanguageFetchSuccess) ? (state as LanguageFetchSuccess).language : []; + } + + int getLanguageIndex({required String langName}) { + return (state is LanguageFetchSuccess) ? (state as LanguageFetchSuccess).language.indexWhere((element) => element.language == langName) : 0; + } +} diff --git a/news-app/lib/cubits/languageJsonCubit.dart b/news-app/lib/cubits/languageJsonCubit.dart new file mode 100644 index 00000000..dd680841 --- /dev/null +++ b/news-app/lib/cubits/languageJsonCubit.dart @@ -0,0 +1,57 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/LanguageJson/languageJsonRepository.dart'; +import 'package:news/utils/appLanguages.dart'; + +abstract class LanguageJsonState {} + +class LanguageJsonInitial extends LanguageJsonState {} + +class LanguageJsonFetchInProgress extends LanguageJsonState {} + +class LanguageJsonFetchSuccess extends LanguageJsonState { + Map languageJson; + + LanguageJsonFetchSuccess({required this.languageJson}); +} + +class LanguageJsonFetchFailure extends LanguageJsonState { + final String errorMessage; + + LanguageJsonFetchFailure(this.errorMessage); +} + +class LanguageJsonCubit extends Cubit { + final LanguageJsonRepository _languageJsonRepository; + + LanguageJsonCubit(this._languageJsonRepository) : super(LanguageJsonInitial()); + + void fetchCurrentLanguageAndLabels(String currentLanguage) async { + try { + emit(LanguageJsonFetchInProgress()); + + await _languageJsonRepository.fetchLanguageLabels(currentLanguage).then((value) { + emit(LanguageJsonFetchSuccess(languageJson: value)); + }); + } catch (e) { + emit(LanguageJsonFetchSuccess(languageJson: appLanguageLabelKeys)); + } + } + + void getLanguageJson({required String lanCode}) async { + try { + emit(LanguageJsonFetchInProgress()); + final result = await _languageJsonRepository.getLanguageJson(lanCode: lanCode); + emit(LanguageJsonFetchSuccess(languageJson: result)); + } catch (e) { + emit(LanguageJsonFetchSuccess(languageJson: appLanguageLabelKeys)); + } + } + + String getTranslatedLabels(String label) { + if (state is LanguageJsonFetchSuccess) { + return (state as LanguageJsonFetchSuccess).languageJson[label] ?? appLanguageLabelKeys[label] ?? label; + } else { + return appLanguageLabelKeys[label] ?? label; + } + } +} diff --git a/news-app/lib/cubits/liveStreamCubit.dart b/news-app/lib/cubits/liveStreamCubit.dart new file mode 100644 index 00000000..ef291e8f --- /dev/null +++ b/news-app/lib/cubits/liveStreamCubit.dart @@ -0,0 +1,39 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/LiveStreamingModel.dart'; +import 'package:news/data/repositories/LiveStream/liveStreamRepository.dart'; +import 'package:news/utils/strings.dart'; + +abstract class LiveStreamState {} + +class LiveStreamInitial extends LiveStreamState {} + +class LiveStreamFetchInProgress extends LiveStreamState {} + +class LiveStreamFetchSuccess extends LiveStreamState { + final List liveStream; + + LiveStreamFetchSuccess({required this.liveStream}); +} + +class LiveStreamFetchFailure extends LiveStreamState { + final String errorMessage; + + LiveStreamFetchFailure(this.errorMessage); +} + +class LiveStreamCubit extends Cubit { + final LiveStreamRepository _liveStreamRepository; + + LiveStreamCubit(this._liveStreamRepository) : super(LiveStreamInitial()); + + void getLiveStream({required String langId}) async { + try { + emit(LiveStreamFetchInProgress()); + final result = await _liveStreamRepository.getLiveStream(langId: langId); + + (!result[ERROR]) ? emit(LiveStreamFetchSuccess(liveStream: result['LiveStream'])) : emit(LiveStreamFetchFailure(result[MESSAGE])); + } catch (e) { + emit(LiveStreamFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/locationCityCubit.dart b/news-app/lib/cubits/locationCityCubit.dart new file mode 100644 index 00000000..c36c633f --- /dev/null +++ b/news-app/lib/cubits/locationCityCubit.dart @@ -0,0 +1,73 @@ +import 'package:news/utils/api.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/locationCityModel.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +abstract class LocationCityState {} + +class LocationCityInitial extends LocationCityState {} + +class LocationCityFetchInProgress extends LocationCityState {} + +class LocationCityFetchSuccess extends LocationCityState { + final List locationCity; + final int totalLocations; + final bool hasMoreFetchError; + final bool hasMore; + + LocationCityFetchSuccess({required this.locationCity, required this.totalLocations, required this.hasMoreFetchError, required this.hasMore}); +} + +class LocationCityFetchFailure extends LocationCityState { + final String errorMessage; + + LocationCityFetchFailure(this.errorMessage); +} + +class LocationCityCubit extends Cubit { + LocationCityCubit() : super(LocationCityInitial()); + + void getLocationCity() async { + try { + final result = await Api.sendApiRequest(body: {LIMIT: limitOfAPIData, OFFSET: 0}, url: Api.getLocationCityApi); + emit(LocationCityFetchSuccess( + totalLocations: result[TOTAL], + locationCity: (result[DATA] as List).map((e) => LocationCityModel.fromJson(e)).toList(), + hasMore: (result[DATA] as List).map((e) => LocationCityModel.fromJson(e)).toList().length < result[TOTAL], + hasMoreFetchError: false)); + } catch (e) { + emit(LocationCityFetchFailure(e.toString())); + } + } + + void getMoreLocationCity() async { + if (state is LocationCityFetchSuccess) { + try { + await Api.sendApiRequest(body: {LIMIT: limitOfAPIData, OFFSET: (state as LocationCityFetchSuccess).locationCity.length.toString()}, url: Api.getLocationCityApi).then((value) { + List updatedResults = (state as LocationCityFetchSuccess).locationCity; + updatedResults.addAll((value[DATA] as List).map((e) => LocationCityModel.fromJson(e)).toList()); + emit(LocationCityFetchSuccess(locationCity: updatedResults, totalLocations: value[TOTAL], hasMoreFetchError: false, hasMore: updatedResults.length < value[TOTAL])); + }); + } catch (e) { + emit(LocationCityFetchSuccess( + locationCity: (state as LocationCityFetchSuccess).locationCity, + totalLocations: (state as LocationCityFetchSuccess).totalLocations, + hasMoreFetchError: true, + hasMore: (state as LocationCityFetchSuccess).hasMore)); + } + } + } + + bool hasMoreLocation() { + return (state is LocationCityFetchSuccess) ? (state as LocationCityFetchSuccess).hasMore : false; + } + + List getLocationCityList() { + return (state is LocationCityFetchSuccess) ? (state as LocationCityFetchSuccess).locationCity : []; + } + + int getLocationIndex({required String locationName}) { + return (state is LocationCityFetchSuccess) ? (state as LocationCityFetchSuccess).locationCity.indexWhere((element) => element.locationName == locationName) : 0; + } +} diff --git a/news-app/lib/cubits/otherPagesCubit.dart b/news-app/lib/cubits/otherPagesCubit.dart new file mode 100644 index 00000000..ff0409ec --- /dev/null +++ b/news-app/lib/cubits/otherPagesCubit.dart @@ -0,0 +1,37 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/OtherPageModel.dart'; +import 'package:news/data/repositories/OtherPages/otherPagesRepository.dart'; + +abstract class OtherPageState {} + +class OtherPageInitial extends OtherPageState {} + +class OtherPageFetchInProgress extends OtherPageState {} + +class OtherPageFetchSuccess extends OtherPageState { + final List otherPage; + + OtherPageFetchSuccess({required this.otherPage}); +} + +class OtherPageFetchFailure extends OtherPageState { + final String errorMessage; + + OtherPageFetchFailure(this.errorMessage); +} + +class OtherPageCubit extends Cubit { + final OtherPageRepository _otherPageRepository; + + OtherPageCubit(this._otherPageRepository) : super(OtherPageInitial()); + + void getOtherPage({required String langId}) async { + emit(OtherPageFetchInProgress()); + try { + final result = await _otherPageRepository.getOtherPage(langId: langId); + emit(OtherPageFetchSuccess(otherPage: result['OtherPage'])); + } catch (e) { + emit(OtherPageFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/privacyTermsCubit.dart b/news-app/lib/cubits/privacyTermsCubit.dart new file mode 100644 index 00000000..951ad3b0 --- /dev/null +++ b/news-app/lib/cubits/privacyTermsCubit.dart @@ -0,0 +1,38 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/OtherPageModel.dart'; +import 'package:news/data/repositories/OtherPages/otherPagesRepository.dart'; + +abstract class PrivacyTermsState {} + +class PrivacyTermsInitial extends PrivacyTermsState {} + +class PrivacyTermsFetchInProgress extends PrivacyTermsState {} + +class PrivacyTermsFetchSuccess extends PrivacyTermsState { + final OtherPageModel termsPolicy; + final OtherPageModel privacyPolicy; + + PrivacyTermsFetchSuccess({required this.termsPolicy, required this.privacyPolicy}); +} + +class PrivacyTermsFetchFailure extends PrivacyTermsState { + final String errorMessage; + + PrivacyTermsFetchFailure(this.errorMessage); +} + +class PrivacyTermsCubit extends Cubit { + final OtherPageRepository _otherPageRepository; + + PrivacyTermsCubit(this._otherPageRepository) : super(PrivacyTermsInitial()); + + void getPrivacyTerms({required String langId}) async { + emit(PrivacyTermsFetchInProgress()); + try { + final Map result = await _otherPageRepository.getPrivacyTermsPage(langId: langId); + emit(PrivacyTermsFetchSuccess(privacyPolicy: OtherPageModel.fromPrivacyTermsJson(result['privacy_policy']), termsPolicy: OtherPageModel.fromPrivacyTermsJson(result['terms_policy']))); + } catch (e) { + emit(PrivacyTermsFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/relatedNewsCubit.dart b/news-app/lib/cubits/relatedNewsCubit.dart new file mode 100644 index 00000000..3b6a7355 --- /dev/null +++ b/news-app/lib/cubits/relatedNewsCubit.dart @@ -0,0 +1,77 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/RelatedNews/relatedNewsRepository.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +abstract class RelatedNewsState {} + +class RelatedNewsInitial extends RelatedNewsState {} + +class RelatedNewsFetchInProgress extends RelatedNewsState {} + +class RelatedNewsFetchSuccess extends RelatedNewsState { + final List relatedNews; + final int totalRelatedNewsCount; + final bool hasMoreFetchError; + final bool hasMore; + + RelatedNewsFetchSuccess({required this.relatedNews, required this.totalRelatedNewsCount, required this.hasMoreFetchError, required this.hasMore}); +} + +class RelatedNewsFetchFailure extends RelatedNewsState { + final String errorMessage; + + RelatedNewsFetchFailure(this.errorMessage); +} + +class RelatedNewsCubit extends Cubit { + final RelatedNewsRepository _relatedNewsRepository; + + RelatedNewsCubit(this._relatedNewsRepository) : super(RelatedNewsInitial()); + + void getRelatedNews({required String langId, String? catId, String? subCatId}) async { + try { + emit(RelatedNewsFetchInProgress()); + final result = await _relatedNewsRepository.getRelatedNews(perPage: limitOfAPIData.toString(), offset: "0", langId: langId, catId: catId, subCatId: subCatId); + emit(RelatedNewsFetchSuccess( + relatedNews: result['RelatedNews'], totalRelatedNewsCount: result[TOTAL], hasMoreFetchError: false, hasMore: (result['RelatedNews'] as List).length < result[TOTAL])); + } catch (e) { + emit(RelatedNewsFetchFailure(e.toString())); + } + } + + bool hasMoreRelatedNews() { + return (state is RelatedNewsFetchSuccess) ? (state as RelatedNewsFetchSuccess).hasMore : false; + } + + void getMoreRelatedNews({required String langId, String? catId, String? subCatId, String? latitude, String? longitude}) async { + if (state is RelatedNewsFetchSuccess) { + try { + final result = await _relatedNewsRepository.getRelatedNews( + perPage: limitOfAPIData.toString(), + offset: (state as RelatedNewsFetchSuccess).relatedNews.length.toString(), + langId: langId, + subCatId: subCatId, + catId: catId, + latitude: latitude, + longitude: longitude); + List updatedResults = (state as RelatedNewsFetchSuccess).relatedNews; + updatedResults.addAll(result['RelatedNews'] as List); + emit(RelatedNewsFetchSuccess( + relatedNews: updatedResults, + totalRelatedNewsCount: result[TOTAL], + hasMoreFetchError: false, + hasMore: updatedResults.length < result[TOTAL], + )); + } catch (e) { + emit(RelatedNewsFetchSuccess( + relatedNews: (state as RelatedNewsFetchSuccess).relatedNews, + hasMoreFetchError: true, + totalRelatedNewsCount: (state as RelatedNewsFetchSuccess).totalRelatedNewsCount, + hasMore: (state as RelatedNewsFetchSuccess).hasMore, + )); + } + } + } +} diff --git a/news-app/lib/cubits/rssFeedCubit.dart b/news-app/lib/cubits/rssFeedCubit.dart new file mode 100644 index 00000000..6f1307bc --- /dev/null +++ b/news-app/lib/cubits/rssFeedCubit.dart @@ -0,0 +1,71 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/RSSFeedModel.dart'; +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +abstract class RSSFeedState {} + +class RSSFeedInitial extends RSSFeedState {} + +class RSSFeedFetchInProgress extends RSSFeedState {} + +class RSSFeedFetchSuccess extends RSSFeedState { + final List RSSFeed; + final int totalRSSFeedCount; + final bool hasMoreFetchError; + final bool hasMore; + + RSSFeedFetchSuccess({required this.RSSFeed, required this.totalRSSFeedCount, required this.hasMoreFetchError, required this.hasMore}); +} + +class RSSFeedFetchFailure extends RSSFeedState { + final String errorMessage; + + RSSFeedFetchFailure(this.errorMessage); +} + +class RSSFeedCubit extends Cubit { + RSSFeedCubit() : super(RSSFeedInitial()); + int limit = 10; + + void getRSSFeed({required String langId, String? categoryId, String? subCategoryId}) async { + try { + emit(RSSFeedFetchInProgress()); + + final result = await Api.sendApiRequest( + body: {LANGUAGE_ID: langId, if (categoryId != null) CATEGORY_ID: categoryId, if (subCategoryId != null) SUBCAT_ID: subCategoryId, LIMIT: limit, OFFSET: 0}, url: Api.rssFeedApi); + + (!result[ERROR]) + ? emit(RSSFeedFetchSuccess( + RSSFeed: (result[DATA] as List).map((e) => RSSFeedModel.fromJson(e)).toList(), totalRSSFeedCount: result[TOTAL], hasMoreFetchError: false, hasMore: result.length < result[TOTAL])) + : emit(RSSFeedFetchFailure(result[MESSAGE])); + } catch (e) { + emit(RSSFeedFetchFailure(e.toString())); + } + } + + bool hasMoreRSSFeed() { + return (state is RSSFeedFetchSuccess) ? (state as RSSFeedFetchSuccess).hasMore : false; + } + + void getMoreRSSFeed({required String langId}) async { + if (state is RSSFeedFetchSuccess) { + try { + final result = await Api.sendApiRequest(body: {LANGUAGE_ID: langId, LIMIT: limit, OFFSET: (state as RSSFeedFetchSuccess).RSSFeed.length}, url: Api.rssFeedApi); + if (!result[ERROR]) { + List updatedResults = (state as RSSFeedFetchSuccess).RSSFeed; + updatedResults.addAll((result[DATA] as List).map((e) => RSSFeedModel.fromJson(e)).toList()); + emit(RSSFeedFetchSuccess(RSSFeed: updatedResults, totalRSSFeedCount: result[TOTAL], hasMoreFetchError: false, hasMore: updatedResults.length < result[TOTAL])); + } else { + emit(RSSFeedFetchFailure(result[MESSAGE])); + } + } catch (e) { + emit(RSSFeedFetchSuccess( + RSSFeed: (state as RSSFeedFetchSuccess).RSSFeed, + hasMoreFetchError: true, + totalRSSFeedCount: (state as RSSFeedFetchSuccess).totalRSSFeedCount, + hasMore: (state as RSSFeedFetchSuccess).hasMore)); + } + } + } +} diff --git a/news-app/lib/cubits/sectionByIdCubit.dart b/news-app/lib/cubits/sectionByIdCubit.dart new file mode 100644 index 00000000..30b0edae --- /dev/null +++ b/news-app/lib/cubits/sectionByIdCubit.dart @@ -0,0 +1,132 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/SectionById/sectionByIdRepository.dart'; +import 'package:news/utils/strings.dart'; + +abstract class SectionByIdState {} + +class SectionByIdInitial extends SectionByIdState {} + +class SectionByIdFetchInProgress extends SectionByIdState {} + +class SectionByIdFetchSuccess extends SectionByIdState { + final List newsModel; + final List breakNewsModel; + final int totalCount; + final String type; + final bool hasMoreFetchError; + final bool hasMore; + + SectionByIdFetchSuccess({required this.newsModel, required this.breakNewsModel, required this.totalCount, required this.type, required this.hasMore, required this.hasMoreFetchError}); +} + +class SectionByIdFetchFailure extends SectionByIdState { + final String errorMessage; + + SectionByIdFetchFailure(this.errorMessage); +} + +class SectionByIdCubit extends Cubit { + final SectionByIdRepository _sectionByIdRepository; + final int limitOfFeaturedSectionData = 10; + + SectionByIdCubit(this._sectionByIdRepository) : super(SectionByIdInitial()); + + void getSectionById({required String langId, required String sectionId, String? latitude, String? longitude, String? limit, String? offset}) async { + try { + emit(SectionByIdFetchInProgress()); + final result = await _sectionByIdRepository.getSectionById( + offset: offset ?? "0", limit: limit ?? limitOfFeaturedSectionData.toString(), langId: langId, sectionId: sectionId, latitude: latitude, longitude: longitude); + if (!result[ERROR]) { + int totalSections = (result[DATA][0].newsType == "news" || result[DATA][0].newsType == "user_choice") + ? result[DATA][0].newsTotal! + : result[DATA][0].newsType == "breaking_news" + ? result[DATA][0].breakNewsTotal! + : result[DATA][0].videosTotal!; + List newsSection = (result[DATA][0].newsType == "news" || result[DATA][0].newsType == "user_choice") + ? result[DATA][0].news! + : result[DATA][0].videosType == "news" + ? result[DATA][0].videos! + : []; + List brNewsSection = result[DATA][0].newsType == "breaking_news" + ? result[DATA][0].breakNews! + : result[DATA][0].videosType == "breaking_news" + ? result[DATA][0].breakVideos! + : []; + + emit(SectionByIdFetchSuccess( + newsModel: newsSection, + breakNewsModel: brNewsSection, + totalCount: totalSections, + type: result[DATA][0].newsType!, + hasMore: ((result[DATA][0].newsType! == NEWS) ? (newsSection.length < totalSections) : (brNewsSection.length < totalSections)), + hasMoreFetchError: false)); + } else { + emit(SectionByIdFetchFailure(result[MESSAGE])); + } + } catch (e) { + if (!isClosed) emit(SectionByIdFetchFailure(e.toString())); //isClosed checked to resolve Bad state issue of Bloc + } + } + + bool hasMoreSections() { + return (state is SectionByIdFetchSuccess) ? (state as SectionByIdFetchSuccess).hasMore : false; + } + + void getMoreSectionById({required String langId, required String sectionId, String? latitude, String? longitude, String? limit, String? offset}) async { + if (state is SectionByIdFetchSuccess) { + try { + final result = await _sectionByIdRepository.getSectionById( + sectionId: sectionId, + latitude: latitude, + longitude: longitude, + limit: limit ?? limitOfFeaturedSectionData.toString(), + offset: (state as SectionByIdFetchSuccess).newsModel.length.toString(), + langId: langId); + List updatedResults = []; + List updatedBrResults = []; + + if (result[DATA][0].newsType! == NEWS || result[DATA][0].videosType == NEWS) { + updatedResults = (state as SectionByIdFetchSuccess).newsModel; + List newValues = (result[DATA][0].newsType == "news" || result[DATA][0].newsType == "user_choice") + ? result[DATA][0].news ?? [] + : result[DATA][0].videosType == "news" + ? result[DATA][0].videos ?? [] + : []; + updatedResults.addAll(newValues); + } else { + updatedBrResults = (state as SectionByIdFetchSuccess).breakNewsModel; + List newValues = result[DATA][0].newsType == "breaking_news" + ? result[DATA][0].breakNews! + : result[DATA][0].videosType == "breaking_news" + ? result[DATA][0].breakVideos! + : []; + updatedBrResults.addAll(newValues); + } + + int totalCount = (result[DATA][0].newsType == "news" || result[DATA][0].newsType == "user_choice") + ? result[DATA][0].newsTotal! + : result[DATA][0].newsType == "breaking_news" + ? result[DATA][0].breakNewsTotal! + : result[DATA][0].videosTotal!; + + emit(SectionByIdFetchSuccess( + newsModel: updatedResults, + breakNewsModel: updatedBrResults, + type: result[DATA][0].newsType!, + totalCount: totalCount, + hasMoreFetchError: false, + hasMore: (result[DATA][0].newsType! == NEWS || result[DATA][0].videosType == NEWS) ? (updatedResults.length < totalCount) : (updatedBrResults.length < totalCount))); + } catch (e) { + emit(SectionByIdFetchSuccess( + type: (state as SectionByIdFetchSuccess).type, + breakNewsModel: (state as SectionByIdFetchSuccess).breakNewsModel, + newsModel: (state as SectionByIdFetchSuccess).newsModel, + hasMoreFetchError: (e.toString() == "No Data Found") ? false : true, + totalCount: (state as SectionByIdFetchSuccess).totalCount, + hasMore: (state as SectionByIdFetchSuccess).hasMore)); + } + } + } +} diff --git a/news-app/lib/cubits/setNewsViewsCubit.dart b/news-app/lib/cubits/setNewsViewsCubit.dart new file mode 100644 index 00000000..f5979111 --- /dev/null +++ b/news-app/lib/cubits/setNewsViewsCubit.dart @@ -0,0 +1,37 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/SetNewsViews/setNewsViewsRepository.dart'; +import 'package:news/utils/api.dart'; + +abstract class SetNewsViewsState {} + +class SetNewsViewsInitial extends SetNewsViewsState {} + +class SetNewsViewsInProgress extends SetNewsViewsState {} + +class SetNewsViewsSuccess extends SetNewsViewsState { + final String message; + + SetNewsViewsSuccess(this.message); +} + +class SetNewsViewsFailure extends SetNewsViewsState { + final String errorMessage; + + SetNewsViewsFailure(this.errorMessage); +} + +class SetNewsViewsCubit extends Cubit { + final SetNewsViewsRepository setNewsViewsRepository; + + SetNewsViewsCubit(this.setNewsViewsRepository) : super(SetNewsViewsInitial()); + + void setNewsViews({required String newsId, required bool isBreakingNews}) { + emit(SetNewsViewsInProgress()); + setNewsViewsRepository.setNewsViews(newsId: newsId, isBreakingNews: isBreakingNews).then((value) { + emit(SetNewsViewsSuccess(value["message"])); + }).catchError((e) { + ApiMessageAndCodeException apiMessageAndCodeException = e; + emit(SetNewsViewsFailure(apiMessageAndCodeException.errorMessage.toString())); + }); + } +} diff --git a/news-app/lib/cubits/setSurveyAnswerCubit.dart b/news-app/lib/cubits/setSurveyAnswerCubit.dart new file mode 100644 index 00000000..6d6d9d0d --- /dev/null +++ b/news-app/lib/cubits/setSurveyAnswerCubit.dart @@ -0,0 +1,37 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/SetSurveyAnswer/setSurveyAnsRepository.dart'; + +abstract class SetSurveyAnsState {} + +class SetSurveyAnsInitial extends SetSurveyAnsState {} + +class SetSurveyAnsFetchInProgress extends SetSurveyAnsState {} + +class SetSurveyAnsFetchSuccess extends SetSurveyAnsState { + var setSurveyAns; + + SetSurveyAnsFetchSuccess({required this.setSurveyAns}); +} + +class SetSurveyAnsFetchFailure extends SetSurveyAnsState { + final String errorMessage; + + SetSurveyAnsFetchFailure(this.errorMessage); +} + +class SetSurveyAnsCubit extends Cubit { + final SetSurveyAnsRepository _setSurveyAnsRepository; + + SetSurveyAnsCubit(this._setSurveyAnsRepository) : super(SetSurveyAnsInitial()); + + void setSurveyAns({required String queId, required String optId}) async { + try { + emit(SetSurveyAnsFetchInProgress()); + final result = await _setSurveyAnsRepository.setSurveyAns(queId: queId, optId: optId); + + emit(SetSurveyAnsFetchSuccess(setSurveyAns: result['SetSurveyAns'])); + } catch (e) { + emit(SetSurveyAnsFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/settingCubit.dart b/news-app/lib/cubits/settingCubit.dart new file mode 100644 index 00000000..bd91fbe0 --- /dev/null +++ b/news-app/lib/cubits/settingCubit.dart @@ -0,0 +1,41 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/SettingsModel.dart'; +import 'package:news/data/repositories/Settings/settingRepository.dart'; + +class SettingsState { + final SettingsModel? settingsModel; + + SettingsState({this.settingsModel}); +} + +class SettingsCubit extends Cubit { + final SettingsRepository _settingsRepository; + + SettingsCubit(this._settingsRepository) : super(SettingsState()) { + _getCurrentSettings(); + } + + void _getCurrentSettings() { + emit(SettingsState(settingsModel: SettingsModel.fromJson(_settingsRepository.getCurrentSettings()))); + } + + SettingsModel getSettings() { + return state.settingsModel!; + } + + void changeShowIntroSlider(bool value) { + _settingsRepository.changeIntroSlider(value); + emit(SettingsState(settingsModel: state.settingsModel!.copyWith(showIntroSlider: value))); + } + + void changeFcmToken(String value) { + _settingsRepository.changeFcmToken(value); + emit(SettingsState(settingsModel: state.settingsModel!.copyWith(token: value))); + } + + void changeNotification(bool value) { + //set HiveBoxKey value for Enabled Notifications + _settingsRepository.changeNotification(value); + emit(SettingsState(settingsModel: state.settingsModel!.copyWith(notification: value))); + } +} diff --git a/news-app/lib/cubits/slugCheckCubit.dart b/news-app/lib/cubits/slugCheckCubit.dart new file mode 100644 index 00000000..377e7882 --- /dev/null +++ b/news-app/lib/cubits/slugCheckCubit.dart @@ -0,0 +1,38 @@ +import 'package:news/utils/api.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +abstract class SlugCheckState {} + +class SlugCheckInitial extends SlugCheckState {} + +class SlugCheckFetchInProgress extends SlugCheckState {} + +class SlugCheckFetchSuccess extends SlugCheckState { + String message; + SlugCheckFetchSuccess({required this.message}); +} + +class SlugCheckFetchFailure extends SlugCheckState { + final String errorMessage; + + SlugCheckFetchFailure(this.errorMessage); +} + +class SlugCheckCubit extends Cubit { + SlugCheckCubit() : super(SlugCheckInitial()); + + Future checkSlugAvailability({required String slug, required String langId}) async { + try { + final result = await Api.sendApiRequest(body: {SLUG: slug, LANGUAGE_ID: langId, LIMIT: limitOfAPIData, OFFSET: 0}, url: Api.slugCheckApi); + if (result[ERROR] == true) { + emit(SlugCheckFetchFailure(result[MESSAGE].toString())); + } else { + emit(SlugCheckFetchSuccess(message: result[MESSAGE])); + } + } catch (e) { + emit(SlugCheckFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/slugNewsCubit.dart b/news-app/lib/cubits/slugNewsCubit.dart new file mode 100644 index 00000000..200640bf --- /dev/null +++ b/news-app/lib/cubits/slugNewsCubit.dart @@ -0,0 +1,49 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +abstract class SlugNewsState {} + +class SlugNewsInitial extends SlugNewsState {} + +class SlugNewsFetchInProgress extends SlugNewsState {} + +class SlugNewsFetchSuccess extends SlugNewsState { + final List generalNews; + final int totalCount; + + SlugNewsFetchSuccess({required this.totalCount, required this.generalNews}); +} + +class SlugNewsFetchFailure extends SlugNewsState { + final String errorMessage; + + SlugNewsFetchFailure(this.errorMessage); +} + +class SlugNewsCubit extends Cubit { + SlugNewsCubit() : super(SlugNewsInitial()); + + Future getSlugNews({required String langId, String? latitude, String? longitude, int? offset, String? newsSlug}) async { + emit(SlugNewsInitial()); + try { + final body = {LANGUAGE_ID: langId, LIMIT: 20}; + + if (latitude != null && latitude != "null") body[LATITUDE] = latitude; + if (longitude != null && longitude != "null") body[LONGITUDE] = longitude; + if (offset != null) body[OFFSET] = offset; + if (newsSlug != null && newsSlug != "null" && newsSlug.trim().isNotEmpty) body[SLUG] = newsSlug; // used in case of native link , details screen redirection + + final result = await Api.sendApiRequest(body: body, url: Api.getNewsApi); + if (!result[ERROR]) { + emit(SlugNewsFetchSuccess(generalNews: (result[DATA] as List).map((e) => NewsModel.fromJson(e)).toList(), totalCount: result[TOTAL])); + } else { + emit(SlugNewsFetchFailure(result[MESSAGE])); + } + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/cubits/subCatNewsCubit.dart b/news-app/lib/cubits/subCatNewsCubit.dart new file mode 100644 index 00000000..7d3d6f88 --- /dev/null +++ b/news-app/lib/cubits/subCatNewsCubit.dart @@ -0,0 +1,81 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/SubCatNews/subCatRepository.dart'; +import 'package:news/utils/ErrorMessageKeys.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +abstract class SubCatNewsState {} + +class SubCatNewsInitial extends SubCatNewsState {} + +class SubCatNewsFetchInProgress extends SubCatNewsState {} + +class SubCatNewsFetchSuccess extends SubCatNewsState { + final List subCatNews; + final int totalSubCatNewsCount; + final bool hasMoreFetchError; + final bool hasMore; + final bool isFirst; + + SubCatNewsFetchSuccess({required this.subCatNews, required this.totalSubCatNewsCount, required this.hasMoreFetchError, required this.hasMore, required this.isFirst}); +} + +class SubCatNewsFetchFailure extends SubCatNewsState { + final String errorMessage; + + SubCatNewsFetchFailure(this.errorMessage); +} + +class SubCatNewsCubit extends Cubit { + final SubCatNewsRepository _subCatNewsRepository; + + SubCatNewsCubit(this._subCatNewsRepository) : super(SubCatNewsInitial()); + + void getSubCatNews({String? catId, String? subCatId, String? latitude, String? longitude, required String langId}) async { + try { + emit(SubCatNewsFetchInProgress()); + final result = + await _subCatNewsRepository.getSubCatNews(limit: limitOfAPIData.toString(), offset: "0", subCatId: subCatId, langId: langId, catId: catId, latitude: latitude, longitude: longitude); + (!result[ERROR]) + ? emit(SubCatNewsFetchSuccess( + subCatNews: result['SubCatNews'], + totalSubCatNewsCount: result[TOTAL], + hasMoreFetchError: false, + hasMore: (result['SubCatNews'] as List).length < result[TOTAL], + isFirst: true)) + : emit(SubCatNewsFetchFailure(ErrorMessageKeys.noDataMessage)); + } catch (e) { + if (!isClosed) emit(SubCatNewsFetchFailure(e.toString())); + } + } + + bool hasMoreSubCatNews() { + return (state is SubCatNewsFetchSuccess) ? (state as SubCatNewsFetchSuccess).hasMore : false; + } + + void getMoreSubCatNews({String? catId, String? subCatId, String? latitude, String? longitude, required String langId}) async { + if (state is SubCatNewsFetchSuccess) { + try { + final result = await _subCatNewsRepository.getSubCatNews( + limit: limitOfAPIData.toString(), + offset: (state as SubCatNewsFetchSuccess).subCatNews.length.toString(), + langId: langId, + catId: catId, + subCatId: subCatId, + latitude: latitude, + longitude: longitude); + List updatedResults = (state as SubCatNewsFetchSuccess).subCatNews; + updatedResults.addAll(result['SubCatNews'] as List); + emit(SubCatNewsFetchSuccess(subCatNews: updatedResults, totalSubCatNewsCount: result[TOTAL], hasMoreFetchError: false, hasMore: updatedResults.length < result[TOTAL], isFirst: false)); + } catch (e) { + emit(SubCatNewsFetchSuccess( + subCatNews: (state as SubCatNewsFetchSuccess).subCatNews, + hasMoreFetchError: true, + totalSubCatNewsCount: (state as SubCatNewsFetchSuccess).totalSubCatNewsCount, + hasMore: (state as SubCatNewsFetchSuccess).hasMore, + isFirst: false)); + } + } + } +} diff --git a/news-app/lib/cubits/subCategoryCubit.dart b/news-app/lib/cubits/subCategoryCubit.dart new file mode 100644 index 00000000..4705a482 --- /dev/null +++ b/news-app/lib/cubits/subCategoryCubit.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:news/data/models/CategoryModel.dart'; +import 'package:news/data/repositories/SubCategory/subCatRepository.dart'; + +abstract class SubCategoryState {} + +class SubCategoryInitial extends SubCategoryState {} + +class SubCategoryFetchInProgress extends SubCategoryState {} + +class SubCategoryFetchSuccess extends SubCategoryState { + final List subCategory; + + SubCategoryFetchSuccess({required this.subCategory}); +} + +class SubCategoryFetchFailure extends SubCategoryState { + final String errorMessage; + + SubCategoryFetchFailure(this.errorMessage); +} + +class SubCategoryCubit extends Cubit { + final SubCategoryRepository _subCategoryRepository; + + SubCategoryCubit(this._subCategoryRepository) : super(SubCategoryInitial()); + + void getSubCategory({required BuildContext context, required String catId, required String langId}) async { + try { + emit(SubCategoryFetchInProgress()); + final result = await _subCategoryRepository.getSubCategory(context: context, catId: catId, langId: langId); + + emit(SubCategoryFetchSuccess(subCategory: result['SubCategory'])); + } catch (e) { + emit(SubCategoryFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/surveyQuestionCubit.dart b/news-app/lib/cubits/surveyQuestionCubit.dart new file mode 100644 index 00000000..bda105df --- /dev/null +++ b/news-app/lib/cubits/surveyQuestionCubit.dart @@ -0,0 +1,53 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/SurveyQuestion/surveyQueRepository.dart'; + +abstract class SurveyQuestionState {} + +class SurveyQuestionInitial extends SurveyQuestionState {} + +class SurveyQuestionFetchInProgress extends SurveyQuestionState {} + +class SurveyQuestionFetchSuccess extends SurveyQuestionState { + final List surveyQuestion; + + SurveyQuestionFetchSuccess({required this.surveyQuestion}); +} + +class SurveyQuestionFetchFailure extends SurveyQuestionState { + final String errorMessage; + + SurveyQuestionFetchFailure(this.errorMessage); +} + +class SurveyQuestionCubit extends Cubit { + final SurveyQuestionRepository _surveyQuestionRepository; + + SurveyQuestionCubit(this._surveyQuestionRepository) : super(SurveyQuestionInitial()); + + Future getSurveyQuestion({required String langId}) async { + try { + emit(SurveyQuestionFetchInProgress()); + await _surveyQuestionRepository.getSurveyQuestion(langId: langId).then((value) { + emit(SurveyQuestionFetchSuccess(surveyQuestion: value['SurveyQuestion'])); + }); + } catch (e) { + emit(SurveyQuestionFetchFailure(e.toString())); + } + } + + void removeQuestion(String index) { + if (state is SurveyQuestionFetchSuccess) { + List queList = List.from((state as SurveyQuestionFetchSuccess).surveyQuestion)..removeWhere((element) => element.id! == index); + emit(SurveyQuestionFetchSuccess(surveyQuestion: queList)); + } + } + + List surveyList() { + return (state is SurveyQuestionFetchSuccess) ? (state as SurveyQuestionFetchSuccess).surveyQuestion : []; + } + + String getSurveyQuestionIndex({required String questionTitle}) { + return (state is! SurveyQuestionFetchSuccess) ? "0" : (state as SurveyQuestionFetchSuccess).surveyQuestion.where((element) => element.question == questionTitle).first.id!; + } +} diff --git a/news-app/lib/cubits/tagCubit.dart b/news-app/lib/cubits/tagCubit.dart new file mode 100644 index 00000000..a3d3d23a --- /dev/null +++ b/news-app/lib/cubits/tagCubit.dart @@ -0,0 +1,92 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/TagModel.dart'; +import 'package:news/data/repositories/Tag/tagRepository.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +abstract class TagState {} + +class TagInitial extends TagState {} + +class TagFetchInProgress extends TagState {} + +class TagFetchSuccess extends TagState { + final List tag; + final int total; + final bool hasMoreFetchError; + final bool hasMore; + + TagFetchSuccess( + {required this.tag, + required this.total, + required this.hasMoreFetchError, + required this.hasMore}); +} + +class TagFetchFailure extends TagState { + final String errorMessage; + + TagFetchFailure(this.errorMessage); +} + +class TagCubit extends Cubit { + final TagRepository _tagRepository; + + TagCubit(this._tagRepository) : super(TagInitial()); + + void getTags({required String langId}) async { + try { + emit(TagFetchInProgress()); + final result = await _tagRepository.getTag( + langId: langId, limit: limitOfAPIData.toString(), offset: "0"); + + emit(TagFetchSuccess( + tag: result['Tag'], + total: result[TOTAL], + hasMoreFetchError: false, + hasMore: ((result['Tag'] as List).length < result[TOTAL]))); + } catch (e) { + emit(TagFetchFailure(e.toString())); + } + } + + // Load tags if not already loaded or failed + void loadIfFailed({required String langId}) { + if (state is TagInitial || state is TagFetchFailure) { + getTags(langId: langId); + } + } + + void getMoreTags({required String langId}) async { + if (state is TagFetchSuccess) { + try { + await _tagRepository + .getTag( + langId: langId, + limit: limitOfAPIData.toString(), + offset: (state as TagFetchSuccess).tag.length.toString()) + .then((value) { + List updatedResults = (state as TagFetchSuccess).tag; + updatedResults.addAll(value['Tag']); + emit(TagFetchSuccess( + tag: updatedResults, + total: value[TOTAL], + hasMoreFetchError: false, + hasMore: (updatedResults.length < value[TOTAL]))); + }); + } catch (e) { + emit(TagFetchSuccess( + tag: (state as TagFetchSuccess).tag, + total: (state as TagFetchSuccess).total, + hasMoreFetchError: true, + hasMore: (state as TagFetchSuccess).hasMore)); + } + } + } + + bool hasMoreTags() { + return (state is TagFetchSuccess) + ? (state as TagFetchSuccess).hasMore + : false; + } +} diff --git a/news-app/lib/cubits/tagNewsCubit.dart b/news-app/lib/cubits/tagNewsCubit.dart new file mode 100644 index 00000000..bd70a3b7 --- /dev/null +++ b/news-app/lib/cubits/tagNewsCubit.dart @@ -0,0 +1,37 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/TagNews/tagNewsRepository.dart'; + +abstract class TagNewsState {} + +class TagNewsInitial extends TagNewsState {} + +class TagNewsFetchInProgress extends TagNewsState {} + +class TagNewsFetchSuccess extends TagNewsState { + final List tagNews; + + TagNewsFetchSuccess({required this.tagNews}); +} + +class TagNewsFetchFailure extends TagNewsState { + final String errorMessage; + + TagNewsFetchFailure(this.errorMessage); +} + +class TagNewsCubit extends Cubit { + final TagNewsRepository _tagNewsRepository; + + TagNewsCubit(this._tagNewsRepository) : super(TagNewsInitial()); + + void getTagNews({required String tagId, required String langId, String? latitude, String? longitude}) async { + try { + emit(TagNewsFetchInProgress()); + final result = await _tagNewsRepository.getTagNews(langId: langId, tagId: tagId, latitude: latitude, longitude: longitude); + emit(TagNewsFetchSuccess(tagNews: result['TagNews'])); + } catch (e) { + emit(TagNewsFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/cubits/themeCubit.dart b/news-app/lib/cubits/themeCubit.dart new file mode 100644 index 00000000..3fbc8739 --- /dev/null +++ b/news-app/lib/cubits/themeCubit.dart @@ -0,0 +1,21 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; + +import 'package:news/ui/styles/appTheme.dart'; + +import 'package:news/utils/uiUtils.dart'; + +class ThemeState { + final AppTheme appTheme; + ThemeState(this.appTheme); +} + +class ThemeCubit extends Cubit { + SettingsLocalDataRepository settingsRepository; + ThemeCubit(this.settingsRepository) : super(ThemeState(UiUtils.getAppThemeFromLabel(settingsRepository.getCurrentTheme()))); + + void changeTheme(AppTheme appTheme) { + settingsRepository.setCurrentTheme(UiUtils.getThemeLabelFromAppTheme(appTheme)); + emit(ThemeState(appTheme)); + } +} diff --git a/news-app/lib/cubits/updateBottomsheetContentCubit.dart b/news-app/lib/cubits/updateBottomsheetContentCubit.dart new file mode 100644 index 00000000..5514fa0a --- /dev/null +++ b/news-app/lib/cubits/updateBottomsheetContentCubit.dart @@ -0,0 +1,63 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/CategoryModel.dart'; +import 'package:news/data/models/TagModel.dart'; +import 'package:news/data/models/appLanguageModel.dart'; +import 'package:news/data/models/locationCityModel.dart'; + +abstract class BottomSheetEvent {} + +class UpdateBottomSheetContent extends BottomSheetEvent { + final List newData; + final List newTagsData; + final List newLanguagesData; + + UpdateBottomSheetContent(this.newData, this.newTagsData, this.newLanguagesData); +} + +// Define the state for your bottom sheet Cubit +class BottomSheetState { + final List locationData; + final List tagsData; + final List languageData; + final List categoryData; + + BottomSheetState(this.locationData, this.tagsData, this.languageData, this.categoryData); +} + +// Define the Cubit itself +class BottomSheetCubit extends Cubit { + BottomSheetCubit() : super(BottomSheetState([], [], [], [])); + // Access the data field within the cubit + + List currentLocationData = []; + List currentTagData = []; + List currentLanguageData = []; + List currentCategoryData = []; + + getAllLatestContent({required bool isTag, required bool isLocation, required bool isLanguage, required bool isCategory}) { + if (!isLocation) currentLocationData = state.locationData; + if (!isTag) currentTagData = state.tagsData; + if (!isLanguage) currentLanguageData = state.languageData; + if (!isCategory) currentCategoryData = state.categoryData; + } + + void updateLocationContent(List newData) { + getAllLatestContent(isTag: false, isLocation: true, isLanguage: false, isCategory: false); + emit(BottomSheetState(newData, currentTagData, currentLanguageData, currentCategoryData)); + } + + void updateTagsContent(List newTagsData) { + getAllLatestContent(isTag: true, isLocation: false, isLanguage: false, isCategory: false); + emit(BottomSheetState(currentLocationData, newTagsData, currentLanguageData, currentCategoryData)); + } + + void updateLanguageContent(List newLanguagesData) { + getAllLatestContent(isTag: false, isLocation: false, isLanguage: true, isCategory: false); + emit(BottomSheetState(currentLocationData, currentTagData, newLanguagesData, currentCategoryData)); + } + + void updateCategoryContent(List newCategoryData) { + getAllLatestContent(isTag: false, isLocation: false, isLanguage: false, isCategory: true); + emit(BottomSheetState(currentLocationData, currentTagData, currentLanguageData, newCategoryData)); + } +} diff --git a/news-app/lib/cubits/videosCubit.dart b/news-app/lib/cubits/videosCubit.dart new file mode 100644 index 00000000..bc07372c --- /dev/null +++ b/news-app/lib/cubits/videosCubit.dart @@ -0,0 +1,63 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/Videos/videosRepository.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +abstract class VideoState {} + +class VideoInitial extends VideoState {} + +class VideoFetchInProgress extends VideoState {} + +class VideoFetchSuccess extends VideoState { + final List video; + final int totalVideoCount; + final bool hasMoreFetchError; + final bool hasMore; + + VideoFetchSuccess({required this.video, required this.totalVideoCount, required this.hasMoreFetchError, required this.hasMore}); +} + +class VideoFetchFailure extends VideoState { + final String errorMessage; + + VideoFetchFailure(this.errorMessage); +} + +class VideoCubit extends Cubit { + final VideoRepository _videoRepository; + + VideoCubit(this._videoRepository) : super(VideoInitial()); + + void getVideo({required String langId, String? latitude, String? longitude}) async { + try { + emit(VideoFetchInProgress()); + final result = await _videoRepository.getVideo(limit: limitOfAPIData.toString(), offset: "0", langId: langId, latitude: latitude, longitude: longitude); + (!result[ERROR]) + ? emit(VideoFetchSuccess(video: result['Video'], totalVideoCount: result[TOTAL], hasMoreFetchError: false, hasMore: (result['Video'] as List).length < result[TOTAL])) + : emit(VideoFetchFailure(result[MESSAGE])); + } catch (e) { + emit(VideoFetchFailure(e.toString())); + } + } + + bool hasMoreVideo() { + return (state is VideoFetchSuccess) ? (state as VideoFetchSuccess).hasMore : false; + } + + void getMoreVideo({required String langId, String? latitude, String? longitude}) async { + if (state is VideoFetchSuccess) { + try { + final result = + await _videoRepository.getVideo(langId: langId, limit: limitOfAPIData.toString(), offset: (state as VideoFetchSuccess).video.length.toString(), latitude: latitude, longitude: longitude); + List updatedResults = (state as VideoFetchSuccess).video; + updatedResults.addAll(result['Video'] as List); + emit(VideoFetchSuccess(video: updatedResults, totalVideoCount: result[TOTAL], hasMoreFetchError: false, hasMore: updatedResults.length < result[TOTAL])); + } catch (e) { + emit(VideoFetchSuccess( + video: (state as VideoFetchSuccess).video, hasMoreFetchError: true, totalVideoCount: (state as VideoFetchSuccess).totalVideoCount, hasMore: (state as VideoFetchSuccess).hasMore)); + } + } + } +} diff --git a/news-app/lib/cubits/weatherCubit.dart b/news-app/lib/cubits/weatherCubit.dart new file mode 100644 index 00000000..3efc32a7 --- /dev/null +++ b/news-app/lib/cubits/weatherCubit.dart @@ -0,0 +1,41 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/WeatherData.dart'; +import 'package:news/utils/ErrorMessageKeys.dart'; + +abstract class WeatherState {} + +class WeatherInitial extends WeatherState {} + +class WeatherFetchInProgress extends WeatherState {} + +class WeatherFetchSuccess extends WeatherState { + final WeatherDetails weatherData; + + WeatherFetchSuccess({required this.weatherData}); +} + +class WeatherFetchFailure extends WeatherState { + final String errorMessage; + + WeatherFetchFailure(this.errorMessage); +} + +class WeatherCubit extends Cubit { + WeatherCubit() : super(WeatherInitial()); + + void getWeatherDetails({required String langId, String? lat, String? lon}) async { + try { + emit(WeatherFetchInProgress()); + + final weatherResponse = await Dio().get('https://api.weatherapi.com/v1/forecast.json?key=d0f2f4dbecc043e78d6123135212408&q=${lat.toString()},${lon.toString()}&days=1&alerts=no&lang=$langId'); + if (weatherResponse.statusCode == 200) { + emit(WeatherFetchSuccess(weatherData: WeatherDetails.fromJson(Map.from(weatherResponse.data)))); + } else { + emit(WeatherFetchFailure(weatherResponse.statusMessage ?? ErrorMessageKeys.defaultErrorMessage)); + } + } catch (e) { + emit(WeatherFetchFailure(e.toString())); + } + } +} diff --git a/news-app/lib/data/models/AppSystemSettingModel.dart b/news-app/lib/data/models/AppSystemSettingModel.dart new file mode 100644 index 00000000..3795a69b --- /dev/null +++ b/news-app/lib/data/models/AppSystemSettingModel.dart @@ -0,0 +1,124 @@ +import 'package:news/data/models/appLanguageModel.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +class AppSystemSettingModel { + String? breakNewsMode, liveStreamMode, catMode, subCatMode, commentMode, inAppAdsMode, iosInAppAdsMode, adsType, iosAdsType; + String? goRewardedId, goInterId, goBannerId, goNativeId; + String? goIOSRewardedId, goIOSInterId, goIOSBannerId, goIOSNativeId; + String? gameId, iosGameId; + String? unityRewardedId, unityInterId, unityBannerId, unityIOSRewardedId, unityIOSInterId, unityIOSBannerId; + String? locationWiseNewsMode, weatherMode, maintenanceMode, forceUpdateMode; + LanguageModel? defaultLangDataModel; + String? rssFeedMode, mobileLoginMode, countryCode, shareAppText, appstoreId, androidAppLink, iosAppLink; + VideoViewType? videoTypePreference; + String? androidAppVersion, iosAppVersion, googleGeminiApiKey; + + AppSystemSettingModel( + {this.breakNewsMode, + this.liveStreamMode, + this.catMode, + this.subCatMode, + this.commentMode, + this.inAppAdsMode, + this.iosInAppAdsMode, + this.adsType, + this.iosAdsType, + this.goRewardedId, + this.goBannerId, + this.goInterId, + this.goNativeId, + this.goIOSBannerId, + this.goIOSInterId, + this.goIOSNativeId, + this.goIOSRewardedId, + this.gameId, + this.iosGameId, + this.unityRewardedId, + this.unityInterId, + this.unityBannerId, + this.unityIOSRewardedId, + this.unityIOSInterId, + this.unityIOSBannerId, + this.defaultLangDataModel, + this.locationWiseNewsMode, + this.weatherMode, + this.maintenanceMode, + this.rssFeedMode, + this.mobileLoginMode, + this.countryCode, + this.shareAppText, + this.appstoreId, + this.androidAppLink, + this.iosAppLink, + this.videoTypePreference, + this.forceUpdateMode, + this.androidAppVersion, + this.iosAppVersion, + this.googleGeminiApiKey}); + + factory AppSystemSettingModel.fromJson(Map json) { + var defaultList = (json[DEFAULT_LANG]); + + LanguageModel defaultLangData; + if (defaultList == null && defaultList.isEmpty) { + defaultLangData = LanguageModel(); + } else { + defaultLangData = LanguageModel.fromJson(defaultList); + } + + return AppSystemSettingModel( + breakNewsMode: json[BREAK_NEWS_MODE], + liveStreamMode: json[LIVE_STREAM_MODE], + catMode: json[CATEGORY_MODE], + subCatMode: json[SUBCAT_MODE], + commentMode: json[COMM_MODE], + inAppAdsMode: json[ADS_MODE], + iosInAppAdsMode: json[IOS_ADS_MODE], + adsType: json[ADS_TYPE], + iosAdsType: json[IOS_ADS_TYPE], + goRewardedId: json[GO_REWARDED_ID], + goInterId: json[GO_INTER_ID], + goBannerId: json[GO_BANNER_ID], + goNativeId: json[GO_NATIVE_ID], + goIOSRewardedId: json[IOS_GO_REWARDED_ID], + goIOSNativeId: json[IOS_GO_NATIVE_ID], + goIOSInterId: json[IOS_GO_INTER_ID], + goIOSBannerId: json[IOS_GO_BANNER_ID], + gameId: json[U_AND_GAME_ID], + iosGameId: json[IOS_U_GAME_ID], + unityRewardedId: json[U_REWARDED_ID], + unityInterId: json[U_INTER_ID], + unityBannerId: json[U_BANNER_ID], + unityIOSRewardedId: json[IOS_U_REWARDED_ID], + unityIOSInterId: json[IOS_U_INTER_ID], + unityIOSBannerId: json[IOS_U_BANNER_ID], + defaultLangDataModel: defaultLangData, + locationWiseNewsMode: json[LOCATION_WISE_NEWS_MODE], + weatherMode: json[WEATHER_MODE], + maintenanceMode: json[MAINTENANCE_MODE], + rssFeedMode: json[RSS_FEED_MODE], + mobileLoginMode: json[MOBILE_LOGIN_MODE], + countryCode: json[COUNTRY_CODE], + shareAppText: json[SHARE_APP_TEXT], + appstoreId: json[APPSTORE_ID], + androidAppLink: json[WEB_SETTING][ANDROID_APP_LINK], + iosAppLink: json[WEB_SETTING][IOS_APP_LINK], + videoTypePreference: fromVideoTypeJson(json[VIDEO_TYPE_PREFERENCE]), + forceUpdateMode: json[FORCE_UPDT_APP_MODE] ?? "0", + androidAppVersion: json[ANDROID_APP_VERSION] ?? '', + iosAppVersion: json[IOS_APP_VERSION] ?? '', + googleGeminiApiKey: json[GEMINI_API_KEY]); + } + + static VideoViewType? fromVideoTypeJson(String? value) { + switch (value) { + case 'normal_style': + return VideoViewType.normal; + case 'page_style': + return VideoViewType.page; + default: + return null; + } + } +} diff --git a/news-app/lib/data/models/AuthModel.dart b/news-app/lib/data/models/AuthModel.dart new file mode 100644 index 00000000..9ae786ae --- /dev/null +++ b/news-app/lib/data/models/AuthModel.dart @@ -0,0 +1,34 @@ +import 'package:news/data/models/authorModel.dart'; +import 'package:news/utils/strings.dart'; + +class AuthModel { + String? id; + String? name; + String? email; + String? mobile; + String? profile; + String? type; + String? status; + String? isFirstLogin; // 0 - new user, 1 - existing user + String? role; + String? jwtToken; + int? isAuthor; + Author? authorDetails; + + AuthModel({this.id, this.name, this.email, this.mobile, this.profile, this.type, this.status, this.isFirstLogin, this.role, this.jwtToken, this.authorDetails, this.isAuthor = 0}); + + AuthModel.fromJson(Map json) { + id = json[ID].toString(); + name = json[NAME] ?? ""; + email = json[EMAIL] ?? ""; + mobile = json[MOBILE] ?? ""; + profile = json[PROFILE] ?? ""; + type = json[TYPE] ?? ""; + status = json[STATUS].toString(); + isFirstLogin = (json[IS_LOGIN] != null) ? json[IS_LOGIN].toString() : ""; + role = json[ROLE].toString(); + jwtToken = json[TOKEN] ?? ""; + isAuthor = json[IS_AUTHOR]; + authorDetails = (isAuthor == 1 && json[AUTHOR] != null) ? Author.fromJson(json[AUTHOR]) : null; + } +} diff --git a/news-app/lib/data/models/BreakingNewsModel.dart b/news-app/lib/data/models/BreakingNewsModel.dart new file mode 100644 index 00000000..28cc7243 --- /dev/null +++ b/news-app/lib/data/models/BreakingNewsModel.dart @@ -0,0 +1,19 @@ +import 'package:news/utils/strings.dart'; + +class BreakingNewsModel { + String? id, image, title, desc, contentType, contentValue, totalViews, slug; + + BreakingNewsModel({this.id, this.image, this.title, this.desc, this.contentValue, this.contentType, this.totalViews, this.slug}); + + factory BreakingNewsModel.fromJson(Map json) { + return BreakingNewsModel( + id: json[ID].toString(), + image: json[IMAGE], + title: json[TITLE], + desc: json[DESCRIPTION] ?? '', + contentValue: json[CONTENT_VALUE], + contentType: json[CONTENT_TYPE], + totalViews: json[TOTAL_VIEWS].toString(), + slug: json[SLUG]); + } +} diff --git a/news-app/lib/data/models/CategoryModel.dart b/news-app/lib/data/models/CategoryModel.dart new file mode 100644 index 00000000..e041031a --- /dev/null +++ b/news-app/lib/data/models/CategoryModel.dart @@ -0,0 +1,26 @@ +import 'package:news/utils/strings.dart'; + +class CategoryModel { + String? id, image, categoryName; + List? subData; + + CategoryModel({this.id, this.image, this.categoryName, this.subData}); + + factory CategoryModel.fromJson(Map json) { + var subList = (json.containsKey(SUBCATEGORIES)) ? (json[SUBCATEGORIES] as List) : []; + List subCatData = []; + subCatData = (subList.isEmpty) ? [] : subList.map((data) => SubCategoryModel.fromJson(data)).toList(); + + return CategoryModel(id: json[ID].toString(), image: json[IMAGE] ?? "", categoryName: json[CATEGORY_NAME], subData: subCatData); + } +} + +class SubCategoryModel { + String? id, categoryId, subCatName; + + SubCategoryModel({this.id, this.categoryId, this.subCatName}); + + factory SubCategoryModel.fromJson(Map json) { + return SubCategoryModel(id: json[ID].toString(), categoryId: json[CATEGORY_ID].toString(), subCatName: json[SUBCAT_NAME] ?? json[SUBCATEGORY]); + } +} diff --git a/news-app/lib/data/models/CommentModel.dart b/news-app/lib/data/models/CommentModel.dart new file mode 100644 index 00000000..fca56d3c --- /dev/null +++ b/news-app/lib/data/models/CommentModel.dart @@ -0,0 +1,60 @@ +import 'package:news/data/models/AuthModel.dart'; +import 'package:news/utils/strings.dart'; + +class CommentModel { + String? id, message, profile, date, name, status, like, dislike, totalLikes, totalDislikes, userId; + List? replyComList; + + CommentModel({this.id, this.message, this.profile, this.date, this.name, this.replyComList, this.status, this.like, this.dislike, this.totalLikes, this.totalDislikes, this.userId}); + + factory CommentModel.fromJson(Map json) { + var replyList = (json[REPLY] as List); + List replyData = []; + if (replyList.isEmpty) { + replyList = []; + } else { + replyData = replyList.map((data) => ReplyModel.fromJson(data)).toList(); + } + + var userDetails = AuthModel.fromJson(json[USER]); + + return CommentModel( + id: json[ID].toString(), + message: json[MESSAGE], + profile: userDetails.profile, + name: userDetails.name, + date: json[DATE], + status: json[STATUS].toString(), + replyComList: replyData, + like: json[LIKE].toString(), + dislike: json[DISLIKE].toString(), + totalDislikes: json[TOTAL_DISLIKE].toString(), + totalLikes: json[TOTAL_LIKE].toString(), + userId: json[USER_ID].toString()); + } +} + +class ReplyModel { + String? id, message, profile, date, name, userId, parentId, newsId, status, like, dislike, totalLikes, totalDislikes; + + ReplyModel({this.id, this.message, this.profile, this.date, this.name, this.userId, this.parentId, this.status, this.newsId, this.like, this.dislike, this.totalLikes, this.totalDislikes}); + + factory ReplyModel.fromJson(Map json) { + var userDetails = AuthModel.fromJson(json[USER]); + + return ReplyModel( + id: json[ID].toString(), + message: json[MESSAGE], + profile: userDetails.profile, + name: userDetails.name, + date: json[DATE], + userId: json[USER_ID].toString(), + parentId: json[PARENT_ID].toString(), + newsId: json[NEWS_ID].toString(), + status: json[STATUS].toString(), + like: json[LIKE].toString(), + dislike: json[DISLIKE].toString(), + totalDislikes: json[TOTAL_DISLIKE].toString(), + totalLikes: json[TOTAL_LIKE].toString()); + } +} diff --git a/news-app/lib/data/models/FeatureSectionModel.dart b/news-app/lib/data/models/FeatureSectionModel.dart new file mode 100644 index 00000000..ea77f8ce --- /dev/null +++ b/news-app/lib/data/models/FeatureSectionModel.dart @@ -0,0 +1,97 @@ +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/models/adSpaceModel.dart'; +import 'package:news/utils/strings.dart'; + +class FeatureSectionModel { + String? id, languageId, title, shortDescription, newsType, videosType, categoryIds, subcategoryIds, newsIds, styleApp, createdAt, status, summarizedDesc; + int? newsTotal, breakNewsTotal, videosTotal; + List? news; + List? breakNews, breakVideos; + List? videos; + AdSpaceModel? adSpaceDetails; + + FeatureSectionModel( + {this.id, + this.languageId, + this.title, + this.shortDescription, + this.newsType, + this.videosType, + this.categoryIds, + this.subcategoryIds, + this.newsIds, + this.styleApp, + this.createdAt, + this.newsTotal, + this.breakNewsTotal, + this.videosTotal, + this.news, + this.breakNews, + this.videos, + this.breakVideos, + this.adSpaceDetails, + this.summarizedDesc}); + + factory FeatureSectionModel.fromJson(Map json) { + List newsData = []; + if (json.containsKey(NEWS)) { + var newsList = (json[NEWS] as List); + if (newsList.isEmpty) { + newsList = []; + } else { + newsData = newsList.map((data) => NewsModel.fromJson(data)).toList(); + } + } + + List breakNewsData = []; + if (json.containsKey(BREAKING_NEWS)) { + var breakNewsList = (json[BREAKING_NEWS] as List); + if (breakNewsList.isEmpty) { + breakNewsList = []; + } else { + breakNewsData = breakNewsList.map((data) => BreakingNewsModel.fromJson(data)).toList(); + } + } + + List videosData = []; + List breakVideosData = []; + if (json.containsKey(VIDEOS)) { + var videosList = (json[VIDEOS] as List); + if (videosList.isEmpty) { + videosList = []; + } else { + if (json[VIDEOS_TYPE] == 'news') { + videosData = videosList.map((data) => NewsModel.fromVideos(data)).toList(); + } else { + breakVideosData = videosList.map((data) => BreakingNewsModel.fromJson(data)).toList(); + } + } + } + AdSpaceModel? adSpaceData; + if (json.containsKey(AD_SPACES)) { + adSpaceData = AdSpaceModel.fromJson(json[AD_SPACES]); + } + + return FeatureSectionModel( + id: json[ID].toString(), + languageId: json[LANGUAGE_ID].toString(), + title: json[TITLE], + shortDescription: json[SHORT_DESC], + newsType: json[NEWS_TYPE], + videosType: json[VIDEOS_TYPE], + categoryIds: json[CAT_IDS], + subcategoryIds: json[SUBCAT_IDS], + newsIds: json[NEWS_IDS], + styleApp: json[STYLE_APP], + newsTotal: json[NEWS_TOTAL], + breakNewsTotal: json[BREAK_NEWS_TOTAL], + videosTotal: json[VIDEOS_TOTAL], + summarizedDesc: json[SUMM_DESCRIPTION], + news: newsData, + breakNews: breakNewsData, + videos: videosData, + breakVideos: breakVideosData, + adSpaceDetails: adSpaceData); + } +} diff --git a/news-app/lib/data/models/LiveStreamingModel.dart b/news-app/lib/data/models/LiveStreamingModel.dart new file mode 100644 index 00000000..33d5000f --- /dev/null +++ b/news-app/lib/data/models/LiveStreamingModel.dart @@ -0,0 +1,11 @@ +import 'package:news/utils/strings.dart'; + +class LiveStreamingModel { + String? id, image, title, type, url, updatedDate; + + LiveStreamingModel({this.id, this.image, this.title, this.type, this.url, this.updatedDate}); + + factory LiveStreamingModel.fromJson(Map json) { + return LiveStreamingModel(id: json[ID].toString(), image: json[IMAGE], title: json[TITLE], type: json[TYPE], url: json[URL], updatedDate: json[UPDATED_DATE]); + } +} diff --git a/news-app/lib/data/models/NewsModel.dart b/news-app/lib/data/models/NewsModel.dart new file mode 100644 index 00000000..b6380d90 --- /dev/null +++ b/news-app/lib/data/models/NewsModel.dart @@ -0,0 +1,196 @@ +import 'package:news/data/models/CategoryModel.dart'; +import 'package:news/data/models/authorModel.dart'; +import 'package:news/data/models/locationCityModel.dart'; +import 'package:news/utils/strings.dart'; +import 'package:news/data/models/OptionModel.dart'; + +class NewsModel { + String? id, userId, newsId, categoryId, title, date, contentType, contentValue, image, desc, categoryName, dateSent, totalLikes, like, shortDesc; + String? bookmark, keyName, tagId, tagName, subCatId, img, subCatName, showTill, langId, totalViews, locationId, locationName, metaKeyword, metaTitle, metaDescription, slug, publishDate; + + List? imageDataList; + + bool? history = false; + String? question, status, type, sourceType; + List? optionDataList; + int? from, isExpired, isCommentEnabled; + Author? authorDetails; + UserAuthorModel? userAthorDetails; + + NewsModel( + {this.id, + this.userId, + this.newsId, + this.categoryId, + this.title, + this.date, + this.contentType, + this.contentValue, + this.image, + this.desc, + this.categoryName, + this.dateSent, + this.imageDataList, + this.totalLikes, + this.like, + this.keyName, + this.tagName, + this.subCatId, + this.tagId, + this.history, + this.optionDataList, + this.question, + this.status, + this.type, + this.from, + this.img, + this.subCatName, + this.showTill, + this.bookmark, + this.langId, + this.totalViews, + this.locationId, + this.locationName, + this.metaTitle, + this.metaDescription, + this.metaKeyword, + this.slug, + this.publishDate, + this.isExpired, + this.isCommentEnabled, + this.sourceType, + this.shortDesc, + this.authorDetails, + this.userAthorDetails}); + + factory NewsModel.history(String history) { + return NewsModel(title: history, history: true); + } + + factory NewsModel.fromSurvey(Map json) { + List optionList = (json[OPTION] as List).map((data) => OptionModel.fromJson(data)).toList(); + + return NewsModel(id: json[ID].toString(), question: json[QUESTION], status: json[STATUS].toString(), optionDataList: optionList, type: "survey", from: 1); + } + + factory NewsModel.fromVideos(Map json) { + String? tagName; + + tagName = (json[TAG] == null) ? "" : json[TAG]; + return NewsModel( + id: json[ID].toString(), + newsId: json[ID].toString(), + //for bookmark get/set + desc: json[DESCRIPTION] ?? '', + date: json[DATE], + image: json[IMAGE], + title: json[TITLE], + contentType: json[CONTENT_TYPE], + contentValue: json[CONTENT_VALUE], + tagId: json[TAG_ID], + tagName: tagName, + categoryName: json[CATEGORY_NAME] ?? '', + sourceType: json[SOURCE_TYPE], + shortDesc: json[SUMM_DESCRIPTION] ?? ''); + } + + factory NewsModel.fromJson(Map json) { + bool isAuthor = (json[USER] == null) ? false : (json[USER][IS_AUTHOR] == 1); + + String? tagName; + + tagName = (json[TAG] == null) ? "" : json[TAG]; + + List imageData = []; + var imageList = (json.containsKey(IMAGES)) + ? json[IMAGES] as List + : (json.containsKey(IMAGE_DATA)) + ? json[IMAGE_DATA] as List + : []; + imageList = (imageList.isEmpty) ? [] : imageList.map((data) => ImageDataModel.fromJson(data)).toList(); + if (imageList.isNotEmpty) imageData = imageList as List; + var categoryName = ''; + try { + categoryName = (json.containsKey(CATEGORY_NAME)) + ? json[CATEGORY_NAME] + : (json.containsKey(CATEGORY)) + ? CategoryModel.fromJson(json[CATEGORY]).categoryName + : ''; + } catch (e) {} + + var subcategoryName = + (json.containsKey(SUBCAT_NAME)) ? json[SUBCAT_NAME] : ((json.containsKey(SUBCATEGORY) && json[SUBCATEGORY] != null) ? SubCategoryModel.fromJson(json[SUBCATEGORY]).subCatName : ''); + + return NewsModel( + id: json[ID].toString(), + userId: json[USER_ID].toString(), + newsId: (json[NEWS_ID] != null && json[NEWS_ID].toString().trim().isNotEmpty) ? json[NEWS_ID].toString() : json[ID].toString(), + //incase of null newsId in Response + categoryId: json[CATEGORY_ID].toString(), + title: json[TITLE], + date: json[DATE], + contentType: json[CONTENT_TYPE], + contentValue: json[CONTENT_VALUE], + image: json[IMAGE], + desc: json[DESCRIPTION] ?? '', + categoryName: categoryName, + dateSent: json[DATE_SENT], + imageDataList: imageData, + totalLikes: json[TOTAL_LIKE].toString(), + like: json[LIKE].toString(), + bookmark: json[BOOKMARK].toString(), + tagId: json[TAG_ID], + tagName: tagName, + subCatId: json[SUBCAT_ID].toString(), + history: false, + type: "news", + img: "", + subCatName: subcategoryName, + showTill: json[SHOW_TILL], + langId: json[LANGUAGE_ID].toString(), + totalViews: json[TOTAL_VIEWS].toString(), + locationId: (json.containsKey(LOCATION) && json[LOCATION] != null) ? LocationCityModel.fromJson(json[LOCATION]).id.toString() : json[LOCATION_ID].toString(), + locationName: (json.containsKey(LOCATION) && json[LOCATION] != null) ? LocationCityModel.fromJson(json[LOCATION]).locationName : json[LOCATION_NAME], + metaKeyword: json[META_KEYWORD], + metaTitle: json[META_TITLE], + metaDescription: json[META_DESC], + slug: json[SLUG], + publishDate: json[PUBLISHED_DATE], + isExpired: json[IS_EXPIRED] ?? 0, + status: json[STATUS].toString(), + isCommentEnabled: json[IS_COMMENT_ENABLED] ?? 1, + shortDesc: json[SUMM_DESCRIPTION] ?? '', + userAthorDetails: (json[USER] == null) ? null : UserAuthorModel.fromJson(json[USER]), + authorDetails: (isAuthor && json[AUTHOR] != null) ? Author.fromJson(json[AUTHOR]) : null); + } +} + +class ImageDataModel { + String? id; + String? otherImage; + + ImageDataModel({this.otherImage, this.id}); + + factory ImageDataModel.fromJson(Map json) { + return ImageDataModel(otherImage: json[OTHER_IMAGE], id: json[ID].toString()); + } +} + +class UserAuthorModel { + String? id; + String? name; + String? profile; + int? isAuthor; + Author? authorData; + + UserAuthorModel({this.id, this.name, this.profile, this.isAuthor, this.authorData}); + + factory UserAuthorModel.fromJson(Map json) { + return UserAuthorModel( + id: json[ID].toString(), + name: json[NAME], + profile: json[PROFILE].toString(), + isAuthor: json[IS_AUTHOR] ?? 0, + authorData: (json.containsKey(AUTHOR) && json[AUTHOR] != null) ? Author.fromJson(json[AUTHOR]) : null); + } +} diff --git a/news-app/lib/data/models/OptionModel.dart b/news-app/lib/data/models/OptionModel.dart new file mode 100644 index 00000000..4759d666 --- /dev/null +++ b/news-app/lib/data/models/OptionModel.dart @@ -0,0 +1,20 @@ +import 'package:news/utils/strings.dart'; + +class OptionModel { + String? id; + String? options; + String? counter; + double? percentage; + String? questionId; + + OptionModel({this.id, this.options, this.counter, this.percentage, this.questionId}); + + factory OptionModel.fromJson(Map json) { + return OptionModel( + id: json[ID].toString(), + options: json[OPTIONS], + counter: json[COUNTER].toString(), + percentage: (json[PERCENTAGE].runtimeType == int) ? double.parse(json[PERCENTAGE].toString()) : json[PERCENTAGE], + questionId: json[QUESTION_ID].toString()); + } +} diff --git a/news-app/lib/data/models/OtherPageModel.dart b/news-app/lib/data/models/OtherPageModel.dart new file mode 100644 index 00000000..ce6e7914 --- /dev/null +++ b/news-app/lib/data/models/OtherPageModel.dart @@ -0,0 +1,15 @@ +import 'package:news/utils/strings.dart'; + +class OtherPageModel { + String? id, pageContent, title, image; +//slug,meta_description,meta_keywords not in use. + OtherPageModel({this.id, this.pageContent, this.title, this.image}); + + factory OtherPageModel.fromJson(Map json) { + return OtherPageModel(id: json[ID].toString(), pageContent: json[PAGE_CONTENT], title: json[TITLE], image: json[PAGE_ICON]); + } + + factory OtherPageModel.fromPrivacyTermsJson(Map json) { + return OtherPageModel(id: json[ID].toString().toString(), pageContent: json[PAGE_CONTENT], title: json[TITLE]); + } +} diff --git a/news-app/lib/data/models/RSSFeedModel.dart b/news-app/lib/data/models/RSSFeedModel.dart new file mode 100644 index 00000000..9680933a --- /dev/null +++ b/news-app/lib/data/models/RSSFeedModel.dart @@ -0,0 +1,23 @@ +import 'package:news/data/models/CategoryModel.dart'; +import 'package:news/utils/strings.dart'; + +class RSSFeedModel { + String? id, feedName, feedUrl, categoryId, categoryName, subCatName, tagName; + + RSSFeedModel({this.id, this.feedName, this.feedUrl, this.categoryId, this.tagName, this.categoryName, this.subCatName}); + + factory RSSFeedModel.fromJson(Map json) { + String? tagName; + + tagName = (json[TAG] == null) ? "" : json[TAG]; + var categoryName = (json.containsKey(CATEGORY_NAME)) + ? json[CATEGORY_NAME] + : (json.containsKey(CATEGORY) && (json[CATEGORY] != null)) + ? CategoryModel.fromJson(json[CATEGORY]).categoryName + : ''; + var subcategoryName = + (json.containsKey(SUBCAT_NAME)) ? json[SUBCAT_NAME] : ((json.containsKey(SUBCATEGORY) && json[SUBCATEGORY] != null) ? SubCategoryModel.fromJson(json[SUBCATEGORY]).subCatName : ''); + + return RSSFeedModel(id: json[ID].toString(), feedName: json[FEED_NAME].toString(), feedUrl: json[FEED_URL], tagName: tagName, categoryName: categoryName, subCatName: subcategoryName); + } +} diff --git a/news-app/lib/data/models/SettingsModel.dart b/news-app/lib/data/models/SettingsModel.dart new file mode 100644 index 00000000..e2b4bd03 --- /dev/null +++ b/news-app/lib/data/models/SettingsModel.dart @@ -0,0 +1,42 @@ +class SettingsModel { + bool showIntroSlider; + bool notification; + String languageCode; + String theme; + String token; + + SettingsModel({ + required this.languageCode, + required this.showIntroSlider, + required this.theme, + required this.notification, + required this.token, + }); + + static SettingsModel fromJson(var settingsJson) { + //to see the json response go to getCurrentSettings() function in settingsRepository + return SettingsModel( + theme: settingsJson['theme'], + showIntroSlider: settingsJson['showIntroSlider'], + notification: settingsJson['notification'], + languageCode: settingsJson['languageCode'], + token: settingsJson['token'], + ); + } + + SettingsModel copyWith({ + String? theme, + bool? showIntroSlider, + bool? notification, + String? languageCode, + String? token, + }) { + return SettingsModel( + theme: theme ?? this.theme, + notification: notification ?? this.notification, + showIntroSlider: showIntroSlider ?? this.showIntroSlider, + languageCode: languageCode ?? this.languageCode, + token: token ?? this.token, + ); + } +} diff --git a/news-app/lib/data/models/TagModel.dart b/news-app/lib/data/models/TagModel.dart new file mode 100644 index 00000000..3fec8755 --- /dev/null +++ b/news-app/lib/data/models/TagModel.dart @@ -0,0 +1,11 @@ +import 'package:news/utils/strings.dart'; + +class TagModel { + String? id, tagName; + + TagModel({this.id, this.tagName}); + + factory TagModel.fromJson(Map json) { + return TagModel(id: json[ID].toString(), tagName: json[TAGNAME]); + } +} diff --git a/news-app/lib/data/models/WeatherData.dart b/news-app/lib/data/models/WeatherData.dart new file mode 100644 index 00000000..096db8dd --- /dev/null +++ b/news-app/lib/data/models/WeatherData.dart @@ -0,0 +1,24 @@ +class WeatherDetails { + String? name; + String? region; + double? tempC; + String? text; + String? icon; + double? minTempC; + double? maxTempC; + String? country; + + WeatherDetails({this.name, this.region, this.tempC, this.text, this.icon, this.maxTempC, this.minTempC, this.country}); + + factory WeatherDetails.fromJson(Map json) { + return WeatherDetails( + name: json["location"]["name"], + region: json["location"]["region"], + country: json["location"]["country"], + tempC: json["current"]["temp_c"], + text: json["current"]["condition"]["text"], + icon: json["current"]["condition"]["icon"], + maxTempC: json["forecast"]["forecastday"][0]["day"]["maxtemp_c"], + minTempC: json["forecast"]["forecastday"][0]["day"]["mintemp_c"]); + } +} diff --git a/news-app/lib/data/models/adSpaceModel.dart b/news-app/lib/data/models/adSpaceModel.dart new file mode 100644 index 00000000..c1dd6123 --- /dev/null +++ b/news-app/lib/data/models/adSpaceModel.dart @@ -0,0 +1,13 @@ +import 'package:news/utils/strings.dart'; + +class AdSpaceModel { + String? id, adSpace, adFeaturedSectionId, adImage, adUrl; + + AdSpaceModel({this.id, this.adSpace, this.adFeaturedSectionId, this.adImage, this.adUrl}); + +//place Ad just above mentioned adFeaturedSectionId + + factory AdSpaceModel.fromJson(Map json) { + return AdSpaceModel(id: json[ID].toString(), adSpace: json[AD_SPACE], adFeaturedSectionId: json[AD_FEATURED_SECTION_ID].toString(), adImage: json[AD_IMAGE], adUrl: json[AD_URL]); + } +} diff --git a/news-app/lib/data/models/appLanguageModel.dart b/news-app/lib/data/models/appLanguageModel.dart new file mode 100644 index 00000000..ca4fab03 --- /dev/null +++ b/news-app/lib/data/models/appLanguageModel.dart @@ -0,0 +1,16 @@ +import 'package:news/utils/strings.dart'; + +class LanguageModel { + String? id, language, languageDisplayName, image, code; + int? isRTL; + LanguageModel({this.id, this.image, this.language, this.languageDisplayName, this.code, this.isRTL}); + factory LanguageModel.fromJson(Map json) { + return LanguageModel( + id: json[ID].toString(), + image: json[IMAGE], + language: json[LANGUAGE], + languageDisplayName: (json[DISPLAY_NAME_LANG] != "") ? json[DISPLAY_NAME_LANG] : json[LANGUAGE], + code: json[CODE], + isRTL: (json[ISRTL].runtimeType == int) ? json[ISRTL] : int.tryParse(json[ISRTL])); + } +} diff --git a/news-app/lib/data/models/authorModel.dart b/news-app/lib/data/models/authorModel.dart new file mode 100644 index 00000000..6a811503 --- /dev/null +++ b/news-app/lib/data/models/authorModel.dart @@ -0,0 +1,49 @@ +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +class Author { + final int? id; + final int? userId; + final String? bio; + final String? telegramLink; + final String? linkedinLink; + final String? facebookLink; + final String? whatsappLink; + final AuthorStatus? status; + + Author({ + this.id, + this.userId, + this.bio, + this.telegramLink, + this.linkedinLink, + this.facebookLink, + this.whatsappLink, + this.status, + }); + + factory Author.fromJson(Map json) { + return Author( + id: json['id'] as int?, + userId: json['user_id'] as int?, + bio: json['bio'] ?? "", + telegramLink: json['telegram_link'] ?? "", + linkedinLink: json['linkedin_link'] ?? "", + facebookLink: json['facebook_link'] ?? "", + whatsappLink: json['whatsapp_link'] ?? "", + status: fromAuthorStatus(json[STATUS])); + } +} + +AuthorStatus? fromAuthorStatus(String? value) { + switch (value) { + case 'pending': + return AuthorStatus.pending; + case 'approved': + return AuthorStatus.approved; + case 'rejected': + return AuthorStatus.rejected; + default: + return null; + } +} diff --git a/news-app/lib/data/models/locationCityModel.dart b/news-app/lib/data/models/locationCityModel.dart new file mode 100644 index 00000000..2bfc1789 --- /dev/null +++ b/news-app/lib/data/models/locationCityModel.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/strings.dart'; + +class LocationCityModel { + final String id; + final String locationName; + final String latitude; + final String longitude; + + LocationCityModel({required this.latitude, required this.longitude, required this.id, required this.locationName}); + + factory LocationCityModel.fromJson(Map json) { + return LocationCityModel(id: json[ID].toString(), locationName: json[LOCATION_NAME], latitude: json[LATITUDE].toString(), longitude: json[LONGITUDE].toString()); + } +} diff --git a/news-app/lib/data/repositories/AddNews/addNewsRemoteDataSource.dart b/news-app/lib/data/repositories/AddNews/addNewsRemoteDataSource.dart new file mode 100644 index 00000000..cacfc287 --- /dev/null +++ b/news-app/lib/data/repositories/AddNews/addNewsRemoteDataSource.dart @@ -0,0 +1,78 @@ +import 'dart:io'; +import 'package:dio/dio.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class AddNewsRemoteDataSource { + //to update fcmId of user's + Future addNewsData( + {required BuildContext context, + required String actionType, + required String catId, + required String title, + required String conTypeId, + required String conType, + required String langId, + File? image, + String? newsId, + String? subCatId, + String? showTill, + String? tagId, + String? url, + String? desc, + String? summDescription, + String? locationId, + File? videoUpload, + List? otherImage, + String? publishDate, + required String metaTitle, + required String metaDescription, + required String metaKeyword, + required String slug, + required int isDraft}) async { + try { + Map body = { + ACTION_TYPE: actionType, + CATEGORY_ID: catId, + TITLE: title, + CONTENT_TYPE: conTypeId, + LANGUAGE_ID: langId, + META_TITLE: metaTitle, + META_DESC: metaDescription, + META_KEYWORD: metaKeyword, + SLUG: slug, + SUMM_DESCRIPTION: summDescription, + IS_DRAFT_KEY: isDraft + }; + Map result = {}; + + if (image != null) body[IMAGE] = await MultipartFile.fromFile(image.path); + if (newsId != null) body[NEWS_ID] = newsId; // in case of update news only + if (subCatId != null) body[SUBCAT_ID] = subCatId; + if (showTill != null) body[SHOW_TILL] = showTill; + if (tagId != null) body[TAG_ID] = tagId; + if (desc != null) body[DESCRIPTION] = desc; + if (locationId != null) body[LOCATION_ID] = locationId; + if (publishDate != null) body[PUBLISHED_DATE] = publishDate; + + if (url != null && (conType == UiUtils.getTranslatedLabel(context, 'videoOtherUrlLbl') || conType == UiUtils.getTranslatedLabel(context, 'videoYoutubeLbl'))) { + body[CONTENT_DATA] = url; + } else if (conType == UiUtils.getTranslatedLabel(context, 'videoUploadLbl')) { + if (videoUpload != null) body[CONTENT_DATA] = await MultipartFile.fromFile(videoUpload.path); + } + + if (otherImage!.isNotEmpty) { + for (var i = 0; i < otherImage.length; i++) { + body["ofile[$i]"] = await MultipartFile.fromFile(otherImage[i].path); + } + } + + result = await Api.sendApiRequest(body: body, url: Api.setNewsApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/AddNews/addNewsRepository.dart b/news-app/lib/data/repositories/AddNews/addNewsRepository.dart new file mode 100644 index 00000000..b276ae8e --- /dev/null +++ b/news-app/lib/data/repositories/AddNews/addNewsRepository.dart @@ -0,0 +1,69 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:news/data/repositories/AddNews/addNewsRemoteDataSource.dart'; + +class AddNewsRepository { + static final AddNewsRepository _addNewsRepository = AddNewsRepository._internal(); + + late AddNewsRemoteDataSource _addNewsRemoteDataSource; + + factory AddNewsRepository() { + _addNewsRepository._addNewsRemoteDataSource = AddNewsRemoteDataSource(); + return _addNewsRepository; + } + + AddNewsRepository._internal(); + + Future> addNews( + {required BuildContext context, + required String actionType, + required String catId, + required String title, + required String conTypeId, + required String conType, + required String langId, + File? image, + String? newsId, + String? subCatId, + String? showTill, + String? tagId, + String? url, + String? desc, + String? summDescription, + String? locationId, + File? videoUpload, + List? otherImage, + String? publishDate, + required String metaTitle, + required String metaDescription, + required String metaKeyword, + required String slug, + required int isDraft}) async { + final result = await _addNewsRemoteDataSource.addNewsData( + context: context, + actionType: actionType, + newsId: newsId, + catId: catId, + langId: langId, + conType: conType, + conTypeId: conTypeId, + image: image, + title: title, + subCatId: subCatId, + showTill: showTill, + desc: desc, + otherImage: otherImage, + tagId: tagId, + url: url, + videoUpload: videoUpload, + locationId: locationId, + metaTitle: metaTitle, + metaDescription: metaDescription, + metaKeyword: metaKeyword, + slug: slug, + publishDate: publishDate ?? null, + summDescription: summDescription, + isDraft: isDraft); + return result; + } +} diff --git a/news-app/lib/data/repositories/AppSystemSetting/systemRepository.dart b/news-app/lib/data/repositories/AppSystemSetting/systemRepository.dart new file mode 100644 index 00000000..7d5caa08 --- /dev/null +++ b/news-app/lib/data/repositories/AppSystemSetting/systemRepository.dart @@ -0,0 +1,13 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class SystemRepository { + Future fetchSettings() async { + try { + final result = await Api.sendApiRequest(url: Api.getSettingApi, body: null, isGet: true); + return result[DATA]; + } catch (e) { + throw ApiException(e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/Auth/authLocalDataSource.dart b/news-app/lib/data/repositories/Auth/authLocalDataSource.dart new file mode 100644 index 00000000..a14b03a3 --- /dev/null +++ b/news-app/lib/data/repositories/Auth/authLocalDataSource.dart @@ -0,0 +1,119 @@ +import 'package:hive/hive.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/hiveBoxKeys.dart'; + +//AuthLocalDataSource will communicate with local database (hive) +class AuthLocalDataSource { + bool? checkIsAuth() { + return Hive.box(authBoxKey).get(isLogInKey, defaultValue: false) ?? false; + } + + String? getId() { + return Hive.box(authBoxKey).get(userIdKey, defaultValue: "0"); + } + + Future setId(String? id) async { + Hive.box(authBoxKey).put(userIdKey, id); + } + + String? getName() { + return Hive.box(authBoxKey).get(userNameKey, defaultValue: ""); + } + + Future setName(String? name) async { + Hive.box(authBoxKey).put(userNameKey, name); + } + + String? getEmail() { + return Hive.box(authBoxKey).get(userEmailKey, defaultValue: ""); + } + + Future setEmail(String? email) async { + Hive.box(authBoxKey).put(userEmailKey, email); + } + + String? getMobile() { + return Hive.box(authBoxKey).get(userMobKey, defaultValue: ""); + } + + Future setMobile(String? mobile) async { + Hive.box(authBoxKey).put(userMobKey, mobile); + } + + String? getType() { + return Hive.box(authBoxKey).get(userTypeKey, defaultValue: ""); + } + + Future setType(String? type) async { + Hive.box(authBoxKey).put(userTypeKey, type); + } + + String? getProfile() { + return Hive.box(authBoxKey).get(userProfileKey, defaultValue: ""); + } + + Future setProfile(String? image) async { + Hive.box(authBoxKey).put(userProfileKey, image); + } + + String? getStatus() { + return Hive.box(authBoxKey).get(userStatusKey, defaultValue: ""); + } + + Future setStatus(String? status) async { + Hive.box(authBoxKey).put(userStatusKey, status); + } + + String getJWTtoken() { + return Hive.box(authBoxKey).get(jwtTokenKey, defaultValue: ""); + } + + Future setJWTtoken(String? jwtToken) async { + Hive.box(authBoxKey).put(jwtTokenKey, jwtToken); + } + + Future changeAuthStatus(bool? authStatus) async { + Hive.box(authBoxKey).put(isLogInKey, authStatus); + } + + String? getAuthorWhatsappLink() { + return Hive.box(authBoxKey).get(authorWhatsappLinkKey, defaultValue: ""); + } + + String? getAuthorFacebookLink() { + return Hive.box(authBoxKey).get(authorfacebookLinkKey, defaultValue: ""); + } + + String? getAuthorTelegramLink() { + return Hive.box(authBoxKey).get(authorTelegramLinkKey, defaultValue: ""); + } + + String? getAuthorLinkedInLink() { + return Hive.box(authBoxKey).get(authorLinkedInLinkKey, defaultValue: ""); + } + + Future setSocialMediaLinks(String? whatsappLink, facebookLink, telegramLink, linkedInLink) async { + Hive.box(authBoxKey).put(authorWhatsappLinkKey, whatsappLink); + Hive.box(authBoxKey).put(authorfacebookLinkKey, facebookLink); + Hive.box(authBoxKey).put(authorTelegramLinkKey, telegramLink); + Hive.box(authBoxKey).put(authorLinkedInLinkKey, linkedInLink); + } + + String getAuthorBio() { + return Hive.box(authBoxKey).get(authorBioKey, defaultValue: ""); + } + + Future setAuthorBio(String? authorBio) async { + Hive.box(authBoxKey).put(authorBioKey, authorBio); + } + + AuthorStatus getAuthorStatus() { + final value = Hive.box(authBoxKey).get(authorStatusKey, defaultValue: AuthorStatus.rejected.name); + + return AuthorStatus.values.byName(value); + } + + Future setAuthorStatus(AuthorStatus authorStatus) async { + Hive.box(authBoxKey).put(authorStatusKey, authorStatus.name); + } +} diff --git a/news-app/lib/data/repositories/Auth/authRemoteDataSource.dart b/news-app/lib/data/repositories/Auth/authRemoteDataSource.dart new file mode 100644 index 00000000..fd7ecb3c --- /dev/null +++ b/news-app/lib/data/repositories/Auth/authRemoteDataSource.dart @@ -0,0 +1,299 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:dio/dio.dart'; +import 'package:crypto/crypto.dart'; +import 'package:flutter/material.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter_login_facebook/flutter_login_facebook.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:google_sign_in/google_sign_in.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; +import 'package:sign_in_with_apple/sign_in_with_apple.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class AuthRemoteDataSource { + final FirebaseAuth _firebaseAuth = FirebaseAuth.instance; + final GoogleSignIn _googleSignIn = GoogleSignIn(scopes: ["profile", "email"]); + final _facebookSignin = FacebookLogin(); + + Future loginAuth({ + required String firebaseId, + required String name, + required String email, + required String type, + required String profile, + required String mobile, + }) async { + try { + final Map body = {FIREBASE_ID: firebaseId, NAME: name, TYPE: type, EMAIL: email}; + if (profile != "") body[PROFILE] = profile; + if (mobile != "") body[MOBILE] = mobile; + + var result = await Api.sendApiRequest(body: body, url: Api.getUserSignUpApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } + + Future deleteUserAcc() async { + try { + final result = await Api.sendApiRequest(body: {}, url: Api.userDeleteApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } + + //to update fcmId of user's + Future updateUserData( + {String? name, String? mobile, String? email, String? filePath, String? authorBio, String? whatsappLink, String? facebookLink, String? telegramLink, String? linkedInLink}) async { + try { + Map body = {}; + Map result = {}; + + if (name != null) body[NAME] = name; + if (mobile != null) body[MOBILE] = mobile; + if (email != null) body[EMAIL] = email; + if (filePath != null && filePath.isNotEmpty) body[PROFILE] = await MultipartFile.fromFile(filePath); + if (authorBio != null) body[AUTHOR_BIO] = authorBio; + if (whatsappLink != null) body[AUTHOR_WHATSAPP_LINK] = whatsappLink; + if (telegramLink != null) body[AUTHOR_TELEGRAM_LINK] = telegramLink; + if (facebookLink != null) body[AUTHOR_FACEBOOK_LINK] = facebookLink; + if (linkedInLink != null) body[AUTHOR_LINKEDIN_LINK] = linkedInLink; + result = await Api.sendApiRequest(body: body, url: Api.setUpdateProfileApi); + + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } + + Future registerToken({required String fcmId, required BuildContext context}) async { + try { + final body = {TOKEN: fcmId, LANGUAGE_ID: context.read().state.id}; //Pass languageId for specific language Notifications + + String latitude = SettingsLocalDataRepository().getLocationCityValues().first; + String longitude = SettingsLocalDataRepository().getLocationCityValues().last; + + if (latitude != '' && latitude != "null") body[LATITUDE] = latitude; + if (longitude != '' && longitude != "null") body[LONGITUDE] = longitude; + + final result = await Api.sendApiRequest(body: body, url: Api.setRegisterToken); + return result; + } on SocketException catch (e) { + throw SocketException(e.toString()); + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } + + Future> socialSignInUser({required AuthProviders authProvider, required BuildContext context, String? email, String? password, String? otp, String? verifiedId}) async { + Map result = {}; + + try { + switch (authProvider) { + case AuthProviders.gmail: + UserCredential? userCredential = await signInWithGoogle(context); + if (userCredential != null) { + result['user'] = userCredential.user!; + return result; + } else { + throw ApiMessageAndCodeException(errorMessage: UiUtils.getTranslatedLabel(context, 'somethingMSg')); + } + + case AuthProviders.mobile: + UserCredential? userCredential = await signInWithPhone(context: context, otp: otp!, verifiedId: verifiedId!); + if (userCredential != null) { + result['user'] = userCredential.user!; + return result; + } else { + throw ApiMessageAndCodeException(errorMessage: UiUtils.getTranslatedLabel(context, 'somethingMSg')); + } + case AuthProviders.fb: + final faceBookAuthResult = await signInWithFacebook(); + if (faceBookAuthResult != null) { + result['user'] = faceBookAuthResult.user!; + return result; + } else { + throw ApiMessageAndCodeException(errorMessage: UiUtils.getTranslatedLabel(context, 'somethingMSg')); + } + case AuthProviders.apple: + UserCredential? userCredential = await signInWithApple(context); + if (userCredential != null) { + result['user'] = userCredential.user!; + return result; + } else { + throw ApiMessageAndCodeException(errorMessage: UiUtils.getTranslatedLabel(context, 'somethingMSg')); + } + case AuthProviders.email: + final userCredential = await signInWithEmailPassword(email: email!, password: password!, context: context); + if (userCredential != null) { + result['user'] = userCredential.user!; + return result; + } else { + return {}; + } + } + } on SocketException catch (_) { + throw ApiMessageAndCodeException(errorMessage: UiUtils.getTranslatedLabel(context, 'internetmsg')); + } on FirebaseAuthException catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } + + Future signInWithPhone({required BuildContext context, required String otp, required String verifiedId}) async { + String code = otp.trim(); + + if (code.length == 6) { + //As OTP is of Fixed length 6 + try { + final PhoneAuthCredential credential = PhoneAuthProvider.credential(verificationId: verifiedId, smsCode: otp); + final UserCredential authResult = await _firebaseAuth.signInWithCredential(credential); + final User? user = authResult.user; + if (user != null) { + assert(!user.isAnonymous); + + final User? currentUser = _firebaseAuth.currentUser; + assert(user.uid == currentUser?.uid); + showSnackBar(UiUtils.getTranslatedLabel(context, 'otpMsg'), context); + return authResult; + } else { + showSnackBar(UiUtils.getTranslatedLabel(context, 'otpError'), context); + return null; + } + } on FirebaseAuthException catch (authError) { + if (authError.code == 'invalidVerificationCode') { + showSnackBar(UiUtils.getTranslatedLabel(context, 'invalidVerificationCode'), context); + return null; + } else { + showSnackBar(authError.message.toString(), context); + return null; + } + } on FirebaseException catch (e) { + showSnackBar(e.message.toString(), context); + return null; + } catch (e) { + showSnackBar(e.toString(), context); + return null; + } + } else { + showSnackBar(UiUtils.getTranslatedLabel(context, 'enterOtpTxt'), context); + return null; + } + } + + //sign in with email and password in firebase + Future signInWithEmailPassword({required String email, required String password, required BuildContext context}) async { + try { + final UserCredential userCredential = await _firebaseAuth.signInWithEmailAndPassword(email: email, password: password); + + return userCredential; + } on FirebaseAuthException catch (authError) { + if (authError.code == 'userNotFound') { + showSnackBar(UiUtils.getTranslatedLabel(context, 'userNotFound'), context); + } else if (authError.code == 'wrongPassword') { + showSnackBar(UiUtils.getTranslatedLabel(context, 'wrongPassword'), context); + } else { + throw ApiMessageAndCodeException(errorMessage: authError.message!); + } + } on FirebaseException catch (e) { + showSnackBar(e.toString(), context); + } catch (e) { + String errorMessage = e.toString(); + showSnackBar(errorMessage, context); + } + return null; + } + + //signIn using google account + Future signInWithGoogle(BuildContext context) async { + final GoogleSignInAccount? googleUser = await _googleSignIn.signIn(); + if (googleUser == null) { + ApiMessageAndCodeException(errorMessage: UiUtils.getTranslatedLabel(context, 'somethingMSg')); + return null; + } + final GoogleSignInAuthentication googleAuth = await googleUser.authentication; + + final AuthCredential credential = GoogleAuthProvider.credential(accessToken: googleAuth.accessToken, idToken: googleAuth.idToken); + final UserCredential userCredential = await _firebaseAuth.signInWithCredential(credential); + return userCredential; + } + + Future signInWithFacebook() async { + final res = await _facebookSignin.logIn(permissions: [FacebookPermission.publicProfile, FacebookPermission.email]); + +// Check result status + switch (res.status) { + case FacebookLoginStatus.success: + // Send access token to server for validation and auth + final FacebookAccessToken? accessToken = res.accessToken; + AuthCredential authCredential = FacebookAuthProvider.credential(accessToken!.token); + final UserCredential userCredential = await _firebaseAuth.signInWithCredential(authCredential); + return userCredential; + case FacebookLoginStatus.cancel: + return null; + + case FacebookLoginStatus.error: + return null; + } + } + + String sha256ofString(String input) { + final bytes = utf8.encode(input); + final digest = sha256.convert(bytes); + return digest.toString(); + } + + Future signInWithApple(BuildContext context) async { + try { + final rawNonce = generateNonce(); + final nonce = sha256ofString(rawNonce); + + final appleCredential = await SignInWithApple.getAppleIDCredential(scopes: [AppleIDAuthorizationScopes.email, AppleIDAuthorizationScopes.fullName], nonce: nonce); + + final oauthCredential = OAuthProvider("apple.com").credential(idToken: appleCredential.identityToken, rawNonce: rawNonce, accessToken: appleCredential.authorizationCode); + + final UserCredential authResult = await FirebaseAuth.instance.signInWithCredential(oauthCredential); + + return authResult; + } on FirebaseAuthException catch (authError) { + showSnackBar(authError.message!, context); + return null; + } on FirebaseException catch (e) { + showSnackBar(e.toString(), context); + return null; + } catch (e) { + String errorMessage = e.toString(); + + if (errorMessage == "Null check operator used on a null value") { + //if user goes back from selecting Account + //in case of User gmail not selected & back to Login screen + showSnackBar(UiUtils.getTranslatedLabel(context, 'cancelLogin'), context); + return null; + } else { + showSnackBar(errorMessage, context); + return null; + } + } + } + + Future signOut(AuthProviders? authProvider) async { + _firebaseAuth.signOut(); + if (authProvider == AuthProviders.gmail) { + _googleSignIn.signOut(); + } else if (authProvider == AuthProviders.fb) { + _facebookSignin.logOut(); + } else { + _firebaseAuth.signOut(); + } + } +} diff --git a/news-app/lib/data/repositories/Auth/authRepository.dart b/news-app/lib/data/repositories/Auth/authRepository.dart new file mode 100644 index 00000000..ec0f7968 --- /dev/null +++ b/news-app/lib/data/repositories/Auth/authRepository.dart @@ -0,0 +1,171 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; +import 'package:news/data/models/authorModel.dart'; +import 'package:news/utils/api.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/hiveBoxKeys.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/utils/strings.dart'; +import 'package:news/data/repositories/Auth/authLocalDataSource.dart'; +import 'package:news/data/repositories/Auth/authRemoteDataSource.dart'; + +class AuthRepository { + static final AuthRepository _authRepository = AuthRepository._internal(); + late AuthLocalDataSource _authLocalDataSource; + late AuthRemoteDataSource _authRemoteDataSource; + + factory AuthRepository() { + _authRepository._authLocalDataSource = AuthLocalDataSource(); + _authRepository._authRemoteDataSource = AuthRemoteDataSource(); + return _authRepository; + } + + AuthRepository._internal(); + + AuthLocalDataSource get authLocalDataSource => _authLocalDataSource; + + //to get auth detials stored in hive box + getLocalAuthDetails() { + return { + "isLogIn": _authLocalDataSource.checkIsAuth(), + ID: _authLocalDataSource.getId(), + NAME: _authLocalDataSource.getName(), + EMAIL: _authLocalDataSource.getEmail(), + MOBILE: _authLocalDataSource.getMobile(), + TYPE: _authLocalDataSource.getType(), + PROFILE: _authLocalDataSource.getProfile(), + STATUS: _authLocalDataSource.getStatus(), + TOKEN: _authLocalDataSource.getJWTtoken(), + AUTHOR_BIO: _authLocalDataSource.getAuthorBio(), + AUTHOR_STATUS: _authLocalDataSource.getAuthorStatus().name, + AUTHOR_WHATSAPP_LINK: _authLocalDataSource.getAuthorWhatsappLink(), + AUTHOR_FACEBOOK_LINK: _authLocalDataSource.getAuthorFacebookLink(), + AUTHOR_TELEGRAM_LINK: _authLocalDataSource.getAuthorTelegramLink(), + AUTHOR_LINKEDIN_LINK: _authLocalDataSource.getAuthorLinkedInLink() + }; + } + + setLocalAuthDetails( + {required bool authStatus, + required String id, + required String name, + required String email, + required String mobile, + required String type, + required String profile, + required String status, + required String role, + required String jwtToken, + required int isAuthor, + Author? authorDetails}) { + _authLocalDataSource.changeAuthStatus(authStatus); + _authLocalDataSource.setId(id); + _authLocalDataSource.setName(name); + _authLocalDataSource.setEmail(email); + _authLocalDataSource.setMobile(mobile); + _authLocalDataSource.setType(type); + _authLocalDataSource.setProfile(profile); + _authLocalDataSource.setStatus(status); + _authLocalDataSource.setJWTtoken(jwtToken); + _authLocalDataSource.setAuthorStatus((authorDetails != null) ? authorDetails.status! : AuthorStatus.rejected); + _authLocalDataSource.setAuthorBio((authorDetails != null) ? authorDetails.bio : ""); + _authLocalDataSource.setSocialMediaLinks((authorDetails != null) ? authorDetails.whatsappLink : "", (authorDetails != null) ? authorDetails.facebookLink : "", + (authorDetails != null) ? authorDetails.telegramLink : "", (authorDetails != null) ? authorDetails.linkedinLink : ""); + } + + //First we signin user with given provider then add user details + Future> signInUser({required BuildContext context, required AuthProviders authProvider, String? email, String? password, String? otp, String? verifiedId}) async { + try { + final result = await _authRemoteDataSource.socialSignInUser(context: context, authProvider: authProvider, email: email, password: password, verifiedId: verifiedId, otp: otp); + final user = result['user'] as User; + var providerData = user.providerData[0]; + if (authProvider == AuthProviders.email && !user.emailVerified) { + throw ApiException(UiUtils.getTranslatedLabel(context, 'verifyEmailMsg')); + } + + Map userDataTest = await _authRemoteDataSource.loginAuth( + mobile: providerData.phoneNumber ?? "", + email: providerData.email ?? "", + firebaseId: user.uid, + name: providerData.displayName ?? "", + profile: providerData.photoURL ?? "", + type: authProvider.name); + if (!userDataTest[ERROR]) { + if (userDataTest[DATA][STATUS].toString() != "0") { + setLocalAuthDetails( + type: userDataTest[DATA][TYPE] ?? "", + profile: userDataTest[DATA][PROFILE] ?? "", + name: userDataTest[DATA][NAME] ?? "", + email: userDataTest[DATA][EMAIL] ?? "", + authStatus: true, + id: (userDataTest[DATA][ID].toString() != "") ? userDataTest[DATA][ID].toString() : "0", + mobile: userDataTest[DATA][MOBILE] ?? "", + role: userDataTest[DATA][ROLE].toString(), + status: userDataTest[DATA][STATUS].toString(), + jwtToken: userDataTest[DATA][TOKEN] ?? "", + isAuthor: userDataTest[DATA][IS_AUTHOR] ?? 0, + authorDetails: (userDataTest[DATA][IS_AUTHOR] == 1 && userDataTest[DATA][AUTHOR] != null) ? Author.fromJson(userDataTest[DATA][AUTHOR]) : null); + } + return userDataTest; + } else { + signOut(authProvider); + throw ApiMessageAndCodeException(errorMessage: userDataTest[MESSAGE]); + } + } catch (e) { + signOut(authProvider); + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } + + Future updateUserData( + {String? name, String? mobile, String? email, String? filePath, String? authorBio, String? whatsappLink, String? facebookLink, String? telegramLink, String? linkedInLink}) async { + final result = await _authRemoteDataSource.updateUserData( + email: email, + name: name, + mobile: mobile, + filePath: filePath, + authorBio: authorBio, + whatsappLink: whatsappLink, + facebookLink: facebookLink, + telegramLink: telegramLink, + linkedInLink: linkedInLink); + if (name != null) _authLocalDataSource.setName(name); + if (mobile != null) _authLocalDataSource.setMobile(mobile); + if (email != null) _authLocalDataSource.setEmail(email); + if (filePath != null && filePath.isNotEmpty) _authLocalDataSource.setProfile(result[PROFILE]); + if (authorBio != null) _authLocalDataSource.setAuthorBio(authorBio); + if (whatsappLink != null || facebookLink != null || telegramLink != null || linkedInLink != null) _authLocalDataSource.setSocialMediaLinks(whatsappLink, facebookLink, telegramLink, linkedInLink); + + return result; + } + + Future> registerToken({required String fcmId, required BuildContext context}) async { + final result = await _authRemoteDataSource.registerToken(fcmId: fcmId, context: context); + return result; + } + + //to delete my account + Future deleteUser() async { + final result = await _authRemoteDataSource.deleteUserAcc(); + return result; + } + + Future signOut(AuthProviders authProvider) async { + _authRemoteDataSource.signOut(authProvider); + await _authLocalDataSource.changeAuthStatus(false); + await _authLocalDataSource.setId("0"); + await _authLocalDataSource.setName(""); + await _authLocalDataSource.setEmail(""); + await _authLocalDataSource.setMobile(""); + await _authLocalDataSource.setType(""); + await _authLocalDataSource.setProfile(""); + await _authLocalDataSource.setStatus(""); + await _authLocalDataSource.setJWTtoken(""); + await _authLocalDataSource.setAuthorBio(""); + await _authLocalDataSource.setAuthorStatus(AuthorStatus.rejected); + await _authLocalDataSource.setSocialMediaLinks("", "", "", ""); + await Hive.box(authBoxKey).clear(); + } +} diff --git a/news-app/lib/data/repositories/Bookmark/bookmarkRemoteDataSource.dart b/news-app/lib/data/repositories/Bookmark/bookmarkRemoteDataSource.dart new file mode 100644 index 00000000..92de512c --- /dev/null +++ b/news-app/lib/data/repositories/Bookmark/bookmarkRemoteDataSource.dart @@ -0,0 +1,24 @@ +import 'package:news/utils/strings.dart'; +import 'package:news/utils/api.dart'; + +class BookmarkRemoteDataSource { + Future getBookmark({required String langId, required String offset, required String perPage}) async { + try { + final body = {LANGUAGE_ID: langId, OFFSET: offset, LIMIT: perPage}; + final result = await Api.sendApiRequest(body: body, url: Api.getBookmarkApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } + + Future addBookmark({required String newsId, required String status}) async { + try { + final body = {NEWS_ID: newsId, STATUS: status}; + final result = await Api.sendApiRequest(body: body, url: Api.setBookmarkApi); + return result[DATA]; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/Bookmark/bookmarkRepository.dart b/news-app/lib/data/repositories/Bookmark/bookmarkRepository.dart new file mode 100644 index 00000000..5e064499 --- /dev/null +++ b/news-app/lib/data/repositories/Bookmark/bookmarkRepository.dart @@ -0,0 +1,27 @@ +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/Bookmark/bookmarkRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class BookmarkRepository { + static final BookmarkRepository _bookmarkRepository = BookmarkRepository._internal(); + late BookmarkRemoteDataSource _bookmarkRemoteDataSource; + + factory BookmarkRepository() { + _bookmarkRepository._bookmarkRemoteDataSource = BookmarkRemoteDataSource(); + return _bookmarkRepository; + } + + BookmarkRepository._internal(); + + Future> getBookmark({required String offset, required String limit, required String langId}) async { + final result = await _bookmarkRemoteDataSource.getBookmark(perPage: limit, offset: offset, langId: langId); + return (result[ERROR]) + ? {ERROR: result[ERROR], MESSAGE: result[MESSAGE]} + : {ERROR: result[ERROR], "total": result[TOTAL], "Bookmark": (result[DATA] as List).map((e) => NewsModel.fromJson(e)).toList()}; + } + + Future setBookmark({required String newsId, required String status}) async { + final result = await _bookmarkRemoteDataSource.addBookmark(status: status, newsId: newsId); + return result; + } +} diff --git a/news-app/lib/data/repositories/BreakingNews/breakNewsRemoteDataSource.dart b/news-app/lib/data/repositories/BreakingNews/breakNewsRemoteDataSource.dart new file mode 100644 index 00000000..b772355f --- /dev/null +++ b/news-app/lib/data/repositories/BreakingNews/breakNewsRemoteDataSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class BreakingNewsRemoteDataSource { + Future getBreakingNews({required String langId}) async { + try { + final body = {LANGUAGE_ID: langId}; + final result = await Api.sendApiRequest(body: body, url: Api.getBreakingNewsApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/BreakingNews/breakNewsRepository.dart b/news-app/lib/data/repositories/BreakingNews/breakNewsRepository.dart new file mode 100644 index 00000000..a0d33ed9 --- /dev/null +++ b/news-app/lib/data/repositories/BreakingNews/breakNewsRepository.dart @@ -0,0 +1,22 @@ +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/data/repositories/BreakingNews/breakNewsRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class BreakingNewsRepository { + static final BreakingNewsRepository _breakingNewsRepository = BreakingNewsRepository._internal(); + + late BreakingNewsRemoteDataSource _breakingNewsRemoteDataSource; + + factory BreakingNewsRepository() { + _breakingNewsRepository._breakingNewsRemoteDataSource = BreakingNewsRemoteDataSource(); + return _breakingNewsRepository; + } + + BreakingNewsRepository._internal(); + + Future getBreakingNews({required String langId}) async { + final result = await _breakingNewsRemoteDataSource.getBreakingNews(langId: langId); + + return (result[ERROR]) ? {ERROR: result[ERROR], MESSAGE: result[MESSAGE]} : {ERROR: result[ERROR], "BreakingNews": (result[DATA] as List).map((e) => BreakingNewsModel.fromJson(e)).toList()}; + } +} diff --git a/news-app/lib/data/repositories/Category/categoryRemoteDataSource.dart b/news-app/lib/data/repositories/Category/categoryRemoteDataSource.dart new file mode 100644 index 00000000..6a8070d7 --- /dev/null +++ b/news-app/lib/data/repositories/Category/categoryRemoteDataSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class CategoryRemoteDataSource { + Future getCategory({required String limit, required String offset, required String langId}) async { + try { + final body = {LIMIT: limit, OFFSET: offset, LANGUAGE_ID: langId}; + final result = await Api.sendApiRequest(body: body, url: Api.getCatApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/Category/categoryRepository.dart b/news-app/lib/data/repositories/Category/categoryRepository.dart new file mode 100644 index 00000000..548ffe34 --- /dev/null +++ b/news-app/lib/data/repositories/Category/categoryRepository.dart @@ -0,0 +1,24 @@ +import 'package:news/data/models/CategoryModel.dart'; +import 'package:news/data/repositories/Category/categoryRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class CategoryRepository { + static final CategoryRepository _notificationRepository = CategoryRepository._internal(); + + late CategoryRemoteDataSource _notificationRemoteDataSource; + + factory CategoryRepository() { + _notificationRepository._notificationRemoteDataSource = CategoryRemoteDataSource(); + return _notificationRepository; + } + + CategoryRepository._internal(); + + Future> getCategory({required String offset, required String limit, required String langId}) async { + final result = await _notificationRemoteDataSource.getCategory(limit: limit, offset: offset, langId: langId); + + return (result[ERROR]) + ? {ERROR: result[ERROR], MESSAGE: result[MESSAGE]} + : {ERROR: result[ERROR], "total": result[TOTAL], "Category": (result[DATA] as List).map((e) => CategoryModel.fromJson(e)).toList()}; + } +} diff --git a/news-app/lib/data/repositories/CommentNews/commNewsRemoteDataSource.dart b/news-app/lib/data/repositories/CommentNews/commNewsRemoteDataSource.dart new file mode 100644 index 00000000..c0c2f447 --- /dev/null +++ b/news-app/lib/data/repositories/CommentNews/commNewsRemoteDataSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class CommentNewsRemoteDataSource { + Future getCommentNews({required String limit, required String offset, required String newsId}) async { + try { + final body = {LIMIT: limit, OFFSET: offset, NEWS_ID: newsId}; + final result = await Api.sendApiRequest(body: body, url: Api.getCommentByNewsApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/CommentNews/commNewsRepository.dart b/news-app/lib/data/repositories/CommentNews/commNewsRepository.dart new file mode 100644 index 00000000..72eb78fe --- /dev/null +++ b/news-app/lib/data/repositories/CommentNews/commNewsRepository.dart @@ -0,0 +1,30 @@ +import 'package:news/data/models/CommentModel.dart'; +import 'package:news/data/repositories/CommentNews/commNewsRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class CommentNewsRepository { + static final CommentNewsRepository _commentNewsRepository = CommentNewsRepository._internal(); + + late CommentNewsRemoteDataSource _commentNewsRemoteDataSource; + + factory CommentNewsRepository() { + _commentNewsRepository._commentNewsRemoteDataSource = CommentNewsRemoteDataSource(); + return _commentNewsRepository; + } + + CommentNewsRepository._internal(); + + Future> getCommentNews({required String offset, required String limit, required String newsId}) async { + final result = await _commentNewsRemoteDataSource.getCommentNews(limit: limit, offset: offset, newsId: newsId); + if (result[ERROR]) { + return {ERROR: result[ERROR], MESSAGE: result[MESSAGE]}; + } else { + final List commentsList = (result[DATA] as List).map((e) => CommentModel.fromJson(e)).toList(); + final List replyList = []; + for (var i in commentsList) { + replyList.addAll(i.replyComList as List); + } + return {ERROR: result[ERROR], "total": result[TOTAL], "CommentNews": commentsList}; + } + } +} diff --git a/news-app/lib/data/repositories/DeleteImageId/deleteImageIdRemoteDataSource.dart b/news-app/lib/data/repositories/DeleteImageId/deleteImageIdRemoteDataSource.dart new file mode 100644 index 00000000..9e56c5ae --- /dev/null +++ b/news-app/lib/data/repositories/DeleteImageId/deleteImageIdRemoteDataSource.dart @@ -0,0 +1,19 @@ +import '../../../utils/api.dart'; +import '../../../utils/strings.dart'; + +class DeleteImageRemoteDataSource { + Future deleteImage({required String imageId}) async { + try { + final body = { + ID: imageId, + }; + final result = await Api.sendApiRequest( + body: body, + url: Api.setDeleteImageApi, + ); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/DeleteImageId/deleteImageRepository.dart b/news-app/lib/data/repositories/DeleteImageId/deleteImageRepository.dart new file mode 100644 index 00000000..410365d3 --- /dev/null +++ b/news-app/lib/data/repositories/DeleteImageId/deleteImageRepository.dart @@ -0,0 +1,20 @@ +import 'deleteImageIdRemoteDataSource.dart'; + +class DeleteImageRepository { + static final DeleteImageRepository _deleteImageRepository = DeleteImageRepository._internal(); + late DeleteImageRemoteDataSource _deleteImageRemoteDataSource; + + factory DeleteImageRepository() { + _deleteImageRepository._deleteImageRemoteDataSource = DeleteImageRemoteDataSource(); + return _deleteImageRepository; + } + + DeleteImageRepository._internal(); + + Future setDeleteImage({ + required String imageId, + }) async { + final result = await _deleteImageRemoteDataSource.deleteImage(imageId: imageId); + return result; + } +} diff --git a/news-app/lib/data/repositories/DeleteUserNews/deleteUserNewsRemoteDataSource.dart b/news-app/lib/data/repositories/DeleteUserNews/deleteUserNewsRemoteDataSource.dart new file mode 100644 index 00000000..00b091ef --- /dev/null +++ b/news-app/lib/data/repositories/DeleteUserNews/deleteUserNewsRemoteDataSource.dart @@ -0,0 +1,19 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class DeleteUserNewsRemoteDataSource { + Future deleteUserNews({required String newsId}) async { + try { + final body = { + ID: newsId, + }; + final result = await Api.sendApiRequest( + body: body, + url: Api.setDeleteNewsApi, + ); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/DeleteUserNews/deleteUserNewsRepository.dart b/news-app/lib/data/repositories/DeleteUserNews/deleteUserNewsRepository.dart new file mode 100644 index 00000000..4282544f --- /dev/null +++ b/news-app/lib/data/repositories/DeleteUserNews/deleteUserNewsRepository.dart @@ -0,0 +1,20 @@ +import 'deleteUserNewsRemoteDataSource.dart'; + +class DeleteUserNewsRepository { + static final DeleteUserNewsRepository _deleteUserNewsRepository = DeleteUserNewsRepository._internal(); + late DeleteUserNewsRemoteDataSource _deleteUserNewsRemoteDataSource; + + factory DeleteUserNewsRepository() { + _deleteUserNewsRepository._deleteUserNewsRemoteDataSource = DeleteUserNewsRemoteDataSource(); + return _deleteUserNewsRepository; + } + + DeleteUserNewsRepository._internal(); + + Future setDeleteUserNews({ + required String newsId, + }) async { + final result = await _deleteUserNewsRemoteDataSource.deleteUserNews(newsId: newsId); + return result; + } +} diff --git a/news-app/lib/data/repositories/DeleteUserNotification/deleteUserNotiRemoteDataSource.dart b/news-app/lib/data/repositories/DeleteUserNotification/deleteUserNotiRemoteDataSource.dart new file mode 100644 index 00000000..52133dfe --- /dev/null +++ b/news-app/lib/data/repositories/DeleteUserNotification/deleteUserNotiRemoteDataSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class DeleteUserNotiRemoteDataSource { + Future deleteUserNotification({required String id}) async { + try { + final body = {ID: id}; + final result = await Api.sendApiRequest(body: body, url: Api.deleteUserNotiApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/DeleteUserNotification/deleteUserNotiRepository.dart b/news-app/lib/data/repositories/DeleteUserNotification/deleteUserNotiRepository.dart new file mode 100644 index 00000000..7d7e2afc --- /dev/null +++ b/news-app/lib/data/repositories/DeleteUserNotification/deleteUserNotiRepository.dart @@ -0,0 +1,18 @@ +import 'package:news/data/repositories/DeleteUserNotification/deleteUserNotiRemoteDataSource.dart'; + +class DeleteUserNotiRepository { + static final DeleteUserNotiRepository _deleteUserNotiRepository = DeleteUserNotiRepository._internal(); + late DeleteUserNotiRemoteDataSource _deleteUserNotiRemoteDataSource; + + factory DeleteUserNotiRepository() { + _deleteUserNotiRepository._deleteUserNotiRemoteDataSource = DeleteUserNotiRemoteDataSource(); + return _deleteUserNotiRepository; + } + + DeleteUserNotiRepository._internal(); + + Future deleteUserNotification({required String id}) async { + final result = await _deleteUserNotiRemoteDataSource.deleteUserNotification(id: id); + return result; + } +} diff --git a/news-app/lib/data/repositories/FeatureSection/sectionRemoteDataSource.dart b/news-app/lib/data/repositories/FeatureSection/sectionRemoteDataSource.dart new file mode 100644 index 00000000..3f11cae8 --- /dev/null +++ b/news-app/lib/data/repositories/FeatureSection/sectionRemoteDataSource.dart @@ -0,0 +1,19 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; + +class SectionRemoteDataSource { + Future getSections({required String langId, String? latitude, String? longitude, String? limit, String? offset, String? sectionOffset}) async { + try { + final body = {LANGUAGE_ID: langId, LIMIT: limit, OFFSET: offset, SECTION_LIMIT: limitOfSectionsData, SECTION_OFFSET: sectionOffset}; + + if (latitude != null && latitude != "null") body[LATITUDE] = latitude; + if (longitude != null && longitude != "null") body[LONGITUDE] = longitude; + + final result = await Api.sendApiRequest(body: body, url: Api.getFeatureSectionApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/FeatureSection/sectionRepository.dart b/news-app/lib/data/repositories/FeatureSection/sectionRepository.dart new file mode 100644 index 00000000..81aebd15 --- /dev/null +++ b/news-app/lib/data/repositories/FeatureSection/sectionRepository.dart @@ -0,0 +1,24 @@ +import 'package:news/data/models/FeatureSectionModel.dart'; +import 'package:news/data/repositories/FeatureSection/sectionRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class SectionRepository { + static final SectionRepository _sectionRepository = SectionRepository._internal(); + + late SectionRemoteDataSource _sectionRemoteDataSource; + + factory SectionRepository() { + _sectionRepository._sectionRemoteDataSource = SectionRemoteDataSource(); + return _sectionRepository; + } + + SectionRepository._internal(); + + Future> getSection({required String langId, String? latitude, String? longitude, String? limit, String? offset, String? sectionOffset}) async { + final result = await _sectionRemoteDataSource.getSections(langId: langId, latitude: latitude, longitude: longitude, limit: limit, offset: offset, sectionOffset: sectionOffset); + + return (result[ERROR]) + ? {ERROR: result[ERROR], MESSAGE: result[MESSAGE]} + : {ERROR: result[ERROR], "Section": (result[DATA] as List).map((e) => FeatureSectionModel.fromJson(e)).toList(), TOTAL: result[TOTAL]}; + } +} diff --git a/news-app/lib/data/repositories/GetSurveyAnswer/getSurveyAnsRemoteDataSource.dart b/news-app/lib/data/repositories/GetSurveyAnswer/getSurveyAnsRemoteDataSource.dart new file mode 100644 index 00000000..559c00f8 --- /dev/null +++ b/news-app/lib/data/repositories/GetSurveyAnswer/getSurveyAnsRemoteDataSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/strings.dart'; +import 'package:news/utils/api.dart'; + +class GetSurveyAnsRemoteDataSource { + Future getSurveyAns({required String langId}) async { + try { + final body = {LANGUAGE_ID: langId}; + final result = await Api.sendApiRequest(body: body, url: Api.getQueResultApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/GetSurveyAnswer/getSurveyAnsRepository.dart b/news-app/lib/data/repositories/GetSurveyAnswer/getSurveyAnsRepository.dart new file mode 100644 index 00000000..47b53755 --- /dev/null +++ b/news-app/lib/data/repositories/GetSurveyAnswer/getSurveyAnsRepository.dart @@ -0,0 +1,20 @@ +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/GetSurveyAnswer/getSurveyAnsRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class GetSurveyAnsRepository { + static final GetSurveyAnsRepository _getSurveyAnsRepository = GetSurveyAnsRepository._internal(); + + late GetSurveyAnsRemoteDataSource _getSurveyAnsRemoteDataSource; + + factory GetSurveyAnsRepository() { + _getSurveyAnsRepository._getSurveyAnsRemoteDataSource = GetSurveyAnsRemoteDataSource(); + return _getSurveyAnsRepository; + } + + GetSurveyAnsRepository._internal(); + Future> getSurveyAns({required String langId}) async { + final result = await _getSurveyAnsRemoteDataSource.getSurveyAns(langId: langId); + return {"GetSurveyAns": (result[DATA] as List).map((e) => NewsModel.fromSurvey(e)).toList()}; + } +} diff --git a/news-app/lib/data/repositories/GetUserById/getUserByIdDataSource.dart b/news-app/lib/data/repositories/GetUserById/getUserByIdDataSource.dart new file mode 100644 index 00000000..b49cb59f --- /dev/null +++ b/news-app/lib/data/repositories/GetUserById/getUserByIdDataSource.dart @@ -0,0 +1,12 @@ +import 'package:news/utils/api.dart'; + +class GetUserByIdRemoteDataSource { + Future getUserById() async { + try { + final result = await Api.sendApiRequest(body: {}, url: Api.getUserByIdApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/GetUserById/getUserByIdRepository.dart b/news-app/lib/data/repositories/GetUserById/getUserByIdRepository.dart new file mode 100644 index 00000000..9824555e --- /dev/null +++ b/news-app/lib/data/repositories/GetUserById/getUserByIdRepository.dart @@ -0,0 +1,20 @@ +import 'package:news/data/repositories/GetUserById/getUserByIdDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class GetUserByIdRepository { + static final GetUserByIdRepository _getUserByIdRepository = GetUserByIdRepository._internal(); + + late GetUserByIdRemoteDataSource _getUserByIdRemoteDataSource; + + factory GetUserByIdRepository() { + _getUserByIdRepository._getUserByIdRemoteDataSource = GetUserByIdRemoteDataSource(); + return _getUserByIdRepository; + } + + GetUserByIdRepository._internal(); + + Future> getUserById() async { + final result = await _getUserByIdRemoteDataSource.getUserById(); + return result[DATA]; + } +} diff --git a/news-app/lib/data/repositories/GetUserNews/getUserNewsRemoteDataSource.dart b/news-app/lib/data/repositories/GetUserNews/getUserNewsRemoteDataSource.dart new file mode 100644 index 00000000..b60bc614 --- /dev/null +++ b/news-app/lib/data/repositories/GetUserNews/getUserNewsRemoteDataSource.dart @@ -0,0 +1,16 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class GetUserNewsRemoteDataSource { + Future getGetUserNews({required String limit, required String offset, String? latitude, String? longitude}) async { + try { + final body = {LIMIT: limit, OFFSET: offset, USER_NEWS: 1}; + if (latitude != null && latitude != "null") body[LATITUDE] = latitude; + if (longitude != null && longitude != "null") body[LONGITUDE] = longitude; + final result = await Api.sendApiRequest(body: body, url: Api.getNewsApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/GetUserNews/getUserNewsRepository.dart b/news-app/lib/data/repositories/GetUserNews/getUserNewsRepository.dart new file mode 100644 index 00000000..e5556a5b --- /dev/null +++ b/news-app/lib/data/repositories/GetUserNews/getUserNewsRepository.dart @@ -0,0 +1,24 @@ +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/GetUserNews/getUserNewsRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class GetUserNewsRepository { + static final GetUserNewsRepository _getUserNewsRepository = GetUserNewsRepository._internal(); + + late GetUserNewsRemoteDataSource _getUserNewsRemoteDataSource; + + factory GetUserNewsRepository() { + _getUserNewsRepository._getUserNewsRemoteDataSource = GetUserNewsRemoteDataSource(); + return _getUserNewsRepository; + } + + GetUserNewsRepository._internal(); + + Future getGetUserNews({required String offset, required String limit, String? latitude, String? longitude}) async { + final result = await _getUserNewsRemoteDataSource.getGetUserNews(limit: limit, offset: offset, latitude: latitude, longitude: longitude); + + return (result[ERROR]) + ? {ERROR: result[ERROR], MESSAGE: result[MESSAGE]} + : {ERROR: result[ERROR], "total": result[TOTAL], "GetUserNews": (result[DATA] as List).map((e) => NewsModel.fromJson(e)).toList()}; + } +} diff --git a/news-app/lib/data/repositories/LanguageJson/languageJsonRemoteDataRepo.dart b/news-app/lib/data/repositories/LanguageJson/languageJsonRemoteDataRepo.dart new file mode 100644 index 00000000..f9b0a4b6 --- /dev/null +++ b/news-app/lib/data/repositories/LanguageJson/languageJsonRemoteDataRepo.dart @@ -0,0 +1,15 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class LanguageJsonRemoteDataSource { + Future getLanguageJson({required String lanCode}) async { + try { + final body = {CODE: lanCode}; + final result = await Api.sendApiRequest(body: body, url: Api.getLangJsonDataApi); + + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/LanguageJson/languageJsonRepository.dart b/news-app/lib/data/repositories/LanguageJson/languageJsonRepository.dart new file mode 100644 index 00000000..33f10b82 --- /dev/null +++ b/news-app/lib/data/repositories/LanguageJson/languageJsonRepository.dart @@ -0,0 +1,40 @@ +import 'package:hive/hive.dart'; +import 'package:news/utils/api.dart'; +import 'package:news/utils/hiveBoxKeys.dart'; +import 'package:news/utils/strings.dart'; +import 'languageJsonRemoteDataRepo.dart'; + +class LanguageJsonRepository { + static final LanguageJsonRepository _languageRepository = LanguageJsonRepository._internal(); + + late LanguageJsonRemoteDataSource _languageRemoteDataSource; + + factory LanguageJsonRepository() { + _languageRepository._languageRemoteDataSource = LanguageJsonRemoteDataSource(); + return _languageRepository; + } + + LanguageJsonRepository._internal(); + + Future getLanguageJson({required String lanCode}) async { + try { + final result = await _languageRemoteDataSource.getLanguageJson(lanCode: lanCode); + return result[DATA]; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } + + Future> fetchLanguageLabels(String langCode) async { + try { + Map languageLabelsJson = {}; + await getLanguageJson(lanCode: langCode).then((value) async { + languageLabelsJson = value as Map; + await Hive.box(settingsBoxKey).put(langCode, languageLabelsJson); + }); + return languageLabelsJson; + } catch (e) { + throw ApiException(e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsDataSource.dart b/news-app/lib/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsDataSource.dart new file mode 100644 index 00000000..e442ea80 --- /dev/null +++ b/news-app/lib/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsDataSource.dart @@ -0,0 +1,26 @@ +import 'package:news/utils/strings.dart'; +import 'package:news/utils/api.dart'; + +class LikeAndDisLikeRemoteDataSource { + Future getLike({required String langId, required String offset, required String perPage}) async { + try { + final body = {LANGUAGE_ID: langId, OFFSET: offset, LIMIT: perPage}; + + final result = await Api.sendApiRequest(body: body, url: Api.getLikeNewsApi); + + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } + + Future addAndRemoveLike({required String newsId, required String status}) async { + try { + final body = {NEWS_ID: newsId, STATUS: status}; + final result = await Api.sendApiRequest(body: body, url: Api.setLikesDislikesApi); + return result[DATA]; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsRepository.dart b/news-app/lib/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsRepository.dart new file mode 100644 index 00000000..65218b9f --- /dev/null +++ b/news-app/lib/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsRepository.dart @@ -0,0 +1,26 @@ +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class LikeAndDisLikeRepository { + static final LikeAndDisLikeRepository _LikeAndDisLikeRepository = LikeAndDisLikeRepository._internal(); + late LikeAndDisLikeRemoteDataSource _LikeAndDisLikeRemoteDataSource; + + factory LikeAndDisLikeRepository() { + _LikeAndDisLikeRepository._LikeAndDisLikeRemoteDataSource = LikeAndDisLikeRemoteDataSource(); + return _LikeAndDisLikeRepository; + } + + LikeAndDisLikeRepository._internal(); + + Future> getLike({required String offset, required String limit, required String langId}) async { + final result = await _LikeAndDisLikeRemoteDataSource.getLike(perPage: limit, offset: offset, langId: langId); + + return {"total": result[TOTAL], "LikeAndDisLike": (result[DATA] as List).map((e) => NewsModel.fromJson(e)).toList()}; + } + + Future setLike({required String newsId, required String status}) async { + final result = await _LikeAndDisLikeRemoteDataSource.addAndRemoveLike(status: status, newsId: newsId); + return result; + } +} diff --git a/news-app/lib/data/repositories/LiveStream/liveRemoteDataSource.dart b/news-app/lib/data/repositories/LiveStream/liveRemoteDataSource.dart new file mode 100644 index 00000000..12873197 --- /dev/null +++ b/news-app/lib/data/repositories/LiveStream/liveRemoteDataSource.dart @@ -0,0 +1,15 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class LiveStreamRemoteDataSource { + Future getLiveStreams({required String langId}) async { + try { + final body = {LANGUAGE_ID: langId}; + final result = await Api.sendApiRequest(body: body, url: Api.getLiveStreamingApi); + + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/LiveStream/liveStreamRepository.dart b/news-app/lib/data/repositories/LiveStream/liveStreamRepository.dart new file mode 100644 index 00000000..e1d962a3 --- /dev/null +++ b/news-app/lib/data/repositories/LiveStream/liveStreamRepository.dart @@ -0,0 +1,22 @@ +import 'package:news/data/models/LiveStreamingModel.dart'; +import 'package:news/data/repositories/LiveStream/liveRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class LiveStreamRepository { + static final LiveStreamRepository _liveStreamRepository = LiveStreamRepository._internal(); + + late LiveStreamRemoteDataSource _liveStreamRemoteDataSource; + + factory LiveStreamRepository() { + _liveStreamRepository._liveStreamRemoteDataSource = LiveStreamRemoteDataSource(); + return _liveStreamRepository; + } + + LiveStreamRepository._internal(); + + Future getLiveStream({required String langId}) async { + final result = await _liveStreamRemoteDataSource.getLiveStreams(langId: langId); + + return (result[ERROR]) ? {ERROR: result[ERROR], MESSAGE: result[MESSAGE]} : {ERROR: result[ERROR], "LiveStream": (result[DATA] as List).map((e) => LiveStreamingModel.fromJson(e)).toList()}; + } +} diff --git a/news-app/lib/data/repositories/NewsById/NewsByIdRemoteDataSource.dart b/news-app/lib/data/repositories/NewsById/NewsByIdRemoteDataSource.dart new file mode 100644 index 00000000..fc72a1ce --- /dev/null +++ b/news-app/lib/data/repositories/NewsById/NewsByIdRemoteDataSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class NewsByIdRemoteDataSource { + Future getNewsById({required String newsId, required String langId}) async { + try { + final body = {LANGUAGE_ID: langId, ID: newsId}; + final result = await Api.sendApiRequest(body: body, url: Api.getNewsApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/NewsById/NewsByIdRepository.dart b/news-app/lib/data/repositories/NewsById/NewsByIdRepository.dart new file mode 100644 index 00000000..794652a8 --- /dev/null +++ b/news-app/lib/data/repositories/NewsById/NewsByIdRepository.dart @@ -0,0 +1,22 @@ +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/NewsById/NewsByIdRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class NewsByIdRepository { + static final NewsByIdRepository _newsByIdRepository = NewsByIdRepository._internal(); + + late NewsByIdRemoteDataSource _newsByIdRemoteDataSource; + + factory NewsByIdRepository() { + _newsByIdRepository._newsByIdRemoteDataSource = NewsByIdRemoteDataSource(); + return _newsByIdRepository; + } + + NewsByIdRepository._internal(); + + Future> getNewsById({required String newsId, required String langId}) async { + final result = await _newsByIdRemoteDataSource.getNewsById(newsId: newsId, langId: langId); + + return {"NewsById": (result[DATA] as List).map((e) => NewsModel.fromJson(e)).toList()}; + } +} diff --git a/news-app/lib/data/repositories/NewsComment/DeleteComment/deleteCommDataSource.dart b/news-app/lib/data/repositories/NewsComment/DeleteComment/deleteCommDataSource.dart new file mode 100644 index 00000000..f108bc7f --- /dev/null +++ b/news-app/lib/data/repositories/NewsComment/DeleteComment/deleteCommDataSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class DeleteCommRemoteDataSource { + Future deleteComm({required String commId}) async { + try { + final body = {COMMENT_ID: commId}; + final result = await Api.sendApiRequest(body: body, url: Api.setCommentDeleteApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/NewsComment/DeleteComment/deleteCommRepository.dart b/news-app/lib/data/repositories/NewsComment/DeleteComment/deleteCommRepository.dart new file mode 100644 index 00000000..df815582 --- /dev/null +++ b/news-app/lib/data/repositories/NewsComment/DeleteComment/deleteCommRepository.dart @@ -0,0 +1,18 @@ +import 'package:news/data/repositories/NewsComment/DeleteComment/deleteCommDataSource.dart'; + +class DeleteCommRepository { + static final DeleteCommRepository _deleteCommRepository = DeleteCommRepository._internal(); + late DeleteCommRemoteDataSource _deleteCommRemoteDataSource; + + factory DeleteCommRepository() { + _deleteCommRepository._deleteCommRemoteDataSource = DeleteCommRemoteDataSource(); + return _deleteCommRepository; + } + + DeleteCommRepository._internal(); + + Future setDeleteComm({required String commId}) async { + final result = await _deleteCommRemoteDataSource.deleteComm(commId: commId); + return result; + } +} diff --git a/news-app/lib/data/repositories/NewsComment/FlagComment/flagCommRemoteDataSource.dart b/news-app/lib/data/repositories/NewsComment/FlagComment/flagCommRemoteDataSource.dart new file mode 100644 index 00000000..e9d9497f --- /dev/null +++ b/news-app/lib/data/repositories/NewsComment/FlagComment/flagCommRemoteDataSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class SetFlagRemoteDataSource { + Future setFlag({required String commId, required String newsId, required String message}) async { + try { + final body = {COMMENT_ID: commId, NEWS_ID: newsId, MESSAGE: message}; + final result = await Api.sendApiRequest(body: body, url: Api.setFlagApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/NewsComment/FlagComment/flagCommRepository.dart b/news-app/lib/data/repositories/NewsComment/FlagComment/flagCommRepository.dart new file mode 100644 index 00000000..9d6a77cd --- /dev/null +++ b/news-app/lib/data/repositories/NewsComment/FlagComment/flagCommRepository.dart @@ -0,0 +1,19 @@ +import 'package:news/data/repositories/NewsComment/FlagComment/flagCommRemoteDataSource.dart'; + +class SetFlagRepository { + static final SetFlagRepository _setFlagRepository = SetFlagRepository._internal(); + + late SetFlagRemoteDataSource _setFlagRemoteDataSource; + + factory SetFlagRepository() { + _setFlagRepository._setFlagRemoteDataSource = SetFlagRemoteDataSource(); + return _setFlagRepository; + } + + SetFlagRepository._internal(); + + Future> setFlag({required String commId, required String newsId, required String message}) async { + final result = await _setFlagRemoteDataSource.setFlag(commId: commId, newsId: newsId, message: message); + return result; + } +} diff --git a/news-app/lib/data/repositories/NewsComment/LikeAndDislikeComment/likeAndDislikeCommDataSource.dart b/news-app/lib/data/repositories/NewsComment/LikeAndDislikeComment/likeAndDislikeCommDataSource.dart new file mode 100644 index 00000000..ef0ac1a2 --- /dev/null +++ b/news-app/lib/data/repositories/NewsComment/LikeAndDislikeComment/likeAndDislikeCommDataSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class LikeAndDislikeCommRemoteDataSource { + Future likeAndDislikeComm({required String langId, required String commId, required String status}) async { + try { + final body = {LANGUAGE_ID: langId, COMMENT_ID: commId, STATUS: status}; + final result = await Api.sendApiRequest(body: body, url: Api.setLikeDislikeComApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/NewsComment/LikeAndDislikeComment/likeAndDislikeCommRepository.dart b/news-app/lib/data/repositories/NewsComment/LikeAndDislikeComment/likeAndDislikeCommRepository.dart new file mode 100644 index 00000000..0caea4db --- /dev/null +++ b/news-app/lib/data/repositories/NewsComment/LikeAndDislikeComment/likeAndDislikeCommRepository.dart @@ -0,0 +1,35 @@ +import 'package:news/data/models/CommentModel.dart'; +import 'package:news/data/repositories/NewsComment/LikeAndDislikeComment/likeAndDislikeCommDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class LikeAndDislikeCommRepository { + static final LikeAndDislikeCommRepository _likeAndDislikeCommRepository = LikeAndDislikeCommRepository._internal(); + late LikeAndDislikeCommRemoteDataSource _likeAndDislikeCommRemoteDataSource; + + factory LikeAndDislikeCommRepository() { + _likeAndDislikeCommRepository._likeAndDislikeCommRemoteDataSource = LikeAndDislikeCommRemoteDataSource(); + return _likeAndDislikeCommRepository; + } + + LikeAndDislikeCommRepository._internal(); + + Future> setLikeAndDislikeComm({required String langId, required String commId, required String status}) async { + final result = await _likeAndDislikeCommRemoteDataSource.likeAndDislikeComm(langId: langId, commId: commId, status: status); + + if (result[ERROR]) { + return {ERROR: result[ERROR], MESSAGE: result[MESSAGE]}; + } else { + final List commentsList = (result[DATA] as List).map((e) => CommentModel.fromJson(e)).toList(); + CommentModel? updatedComment; + + commentsList.forEach((element) { + if (element.id! == commId) { + updatedComment = element; + } else if (element.replyComList!.any((sublist) => sublist.id == commId) == true) { + updatedComment = element; + } + }); + return {ERROR: result[ERROR], "total": result[TOTAL], "updatedComment": updatedComment ?? CommentModel.fromJson({})}; + } + } +} diff --git a/news-app/lib/data/repositories/NewsComment/SetComment/setComRemoteDataSource.dart b/news-app/lib/data/repositories/NewsComment/SetComment/setComRemoteDataSource.dart new file mode 100644 index 00000000..70ec853a --- /dev/null +++ b/news-app/lib/data/repositories/NewsComment/SetComment/setComRemoteDataSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class SetCommentRemoteDataSource { + Future setComment({required String parentId, required String newsId, required String message}) async { + try { + final body = {PARENT_ID: parentId, NEWS_ID: newsId, MESSAGE: message}; + final result = await Api.sendApiRequest(body: body, url: Api.setCommentApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/NewsComment/SetComment/setComRepository.dart b/news-app/lib/data/repositories/NewsComment/SetComment/setComRepository.dart new file mode 100644 index 00000000..e6260da2 --- /dev/null +++ b/news-app/lib/data/repositories/NewsComment/SetComment/setComRepository.dart @@ -0,0 +1,19 @@ +import 'package:news/data/repositories/NewsComment/SetComment/setComRemoteDataSource.dart'; +import 'package:news/data/models/CommentModel.dart'; +import 'package:news/utils/strings.dart'; + +class SetCommentRepository { + static final SetCommentRepository _setCommentRepository = SetCommentRepository._internal(); + + late SetCommentRemoteDataSource _setCommentRemoteDataSource; + + factory SetCommentRepository() { + _setCommentRepository._setCommentRemoteDataSource = SetCommentRemoteDataSource(); + return _setCommentRepository; + } + SetCommentRepository._internal(); + Future> setComment({required String parentId, required String newsId, required String message}) async { + final result = await _setCommentRemoteDataSource.setComment(parentId: parentId, newsId: newsId, message: message); + return {"SetComment": (result[DATA] as List).map((e) => CommentModel.fromJson(e)).toList(), "total": result[TOTAL]}; + } +} diff --git a/news-app/lib/data/repositories/OtherPages/otherPageRemoteDataSorce.dart b/news-app/lib/data/repositories/OtherPages/otherPageRemoteDataSorce.dart new file mode 100644 index 00000000..3a173d4d --- /dev/null +++ b/news-app/lib/data/repositories/OtherPages/otherPageRemoteDataSorce.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class OtherPageRemoteDataSource { + Future getOtherPages({required String langId}) async { + try { + final body = {LANGUAGE_ID: langId}; + final result = await Api.sendApiRequest(body: body, url: Api.getPagesApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/OtherPages/otherPagesRepository.dart b/news-app/lib/data/repositories/OtherPages/otherPagesRepository.dart new file mode 100644 index 00000000..4fa1a8df --- /dev/null +++ b/news-app/lib/data/repositories/OtherPages/otherPagesRepository.dart @@ -0,0 +1,34 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; +import 'package:news/data/models/OtherPageModel.dart'; +import 'package:news/data/repositories/OtherPages/otherPageRemoteDataSorce.dart'; + +class OtherPageRepository { + static final OtherPageRepository _otherPageRepository = OtherPageRepository._internal(); + + late OtherPageRemoteDataSource _otherPageRemoteDataSource; + + factory OtherPageRepository() { + _otherPageRepository._otherPageRemoteDataSource = OtherPageRemoteDataSource(); + return _otherPageRepository; + } + + OtherPageRepository._internal(); + + Future> getOtherPage({required String langId}) async { + final result = await _otherPageRemoteDataSource.getOtherPages(langId: langId); + + return {"OtherPage": (result[DATA] as List).map((e) => OtherPageModel.fromJson(e)).toList()}; + } + + //get only privacy policy & Terms Conditions + Future> getPrivacyTermsPage({required String langId}) async { + try { + final body = {LANGUAGE_ID: langId}; + final result = await Api.sendApiRequest(body: body, url: Api.getPolicyPagesApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/RelatedNews/relatedNewsDataSource.dart b/news-app/lib/data/repositories/RelatedNews/relatedNewsDataSource.dart new file mode 100644 index 00000000..f68c2bc4 --- /dev/null +++ b/news-app/lib/data/repositories/RelatedNews/relatedNewsDataSource.dart @@ -0,0 +1,18 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class RelatedNewsRemoteDataSource { + Future getRelatedNews({required String langId, String? catId, String? subCatId, String? latitude, String? longitude, required String offset, required String perPage}) async { + try { + final body = {LANGUAGE_ID: langId, OFFSET: offset, LIMIT: perPage, MERGE_TAG: 1}; //merge_tag used to get related news according to tagId along with cat or Subcat Id + (subCatId != "0" && subCatId != '') ? body[SUBCAT_ID] = subCatId! : body[CATEGORY_ID] = catId!; + if (latitude != null && latitude != "null") body[LATITUDE] = latitude; + if (longitude != null && longitude != "null") body[LONGITUDE] = longitude; + + final result = await Api.sendApiRequest(body: body, url: Api.getNewsApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/RelatedNews/relatedNewsRepository.dart b/news-app/lib/data/repositories/RelatedNews/relatedNewsRepository.dart new file mode 100644 index 00000000..035c42f9 --- /dev/null +++ b/news-app/lib/data/repositories/RelatedNews/relatedNewsRepository.dart @@ -0,0 +1,22 @@ +import 'package:news/data/repositories/RelatedNews/relatedNewsDataSource.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/utils/strings.dart'; + +class RelatedNewsRepository { + static final RelatedNewsRepository _relatedNewsRepository = RelatedNewsRepository._internal(); + + late RelatedNewsRemoteDataSource _relatedNewsRemoteDataSource; + + factory RelatedNewsRepository() { + _relatedNewsRepository._relatedNewsRemoteDataSource = RelatedNewsRemoteDataSource(); + return _relatedNewsRepository; + } + + RelatedNewsRepository._internal(); + + Future> getRelatedNews({required String langId, required String offset, required String perPage, String? catId, String? subCatId, String? latitude, String? longitude}) async { + final result = await _relatedNewsRemoteDataSource.getRelatedNews(langId: langId, catId: catId, subCatId: subCatId, latitude: latitude, longitude: longitude, offset: offset, perPage: perPage); + + return {"RelatedNews": (result[DATA] as List).map((e) => NewsModel.fromJson(e)).toList(), "total": result[TOTAL]}; + } +} diff --git a/news-app/lib/data/repositories/SectionById/sectionByIdRemoteDataSource.dart b/news-app/lib/data/repositories/SectionById/sectionByIdRemoteDataSource.dart new file mode 100644 index 00000000..8586bb1a --- /dev/null +++ b/news-app/lib/data/repositories/SectionById/sectionByIdRemoteDataSource.dart @@ -0,0 +1,22 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class SectionByIdRemoteDataSource { + Future getSectionById({required String langId, required String limit, required String offset, required String sectionId, String? latitude, String? longitude}) async { + try { + final body = {LANGUAGE_ID: langId, SECTION_ID: sectionId}; + if (latitude != null && latitude != "null") body[LATITUDE] = latitude; + if (longitude != null && longitude != "null") body[LONGITUDE] = longitude; + + if (sectionId.isNotEmpty) { + body[SECTION_ID] = sectionId; + } + body[LIMIT] = limit; + body[OFFSET] = offset; + final result = await Api.sendApiRequest(body: body, url: Api.getFeatureSectionApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/SectionById/sectionByIdRepository.dart b/news-app/lib/data/repositories/SectionById/sectionByIdRepository.dart new file mode 100644 index 00000000..6fe7222d --- /dev/null +++ b/news-app/lib/data/repositories/SectionById/sectionByIdRepository.dart @@ -0,0 +1,25 @@ +import 'package:news/data/models/FeatureSectionModel.dart'; +import 'package:news/data/repositories/SectionById/sectionByIdRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class SectionByIdRepository { + static final SectionByIdRepository _sectionByIdRepository = SectionByIdRepository._internal(); + + late SectionByIdRemoteDataSource _sectionByIdRemoteDataSource; + + factory SectionByIdRepository() { + _sectionByIdRepository._sectionByIdRemoteDataSource = SectionByIdRemoteDataSource(); + return _sectionByIdRepository; + } + + SectionByIdRepository._internal(); + Future> getSectionById({required String langId, required String sectionId, required String limit, required String offset, String? latitude, String? longitude}) async { + final result = await _sectionByIdRemoteDataSource.getSectionById(langId: langId, sectionId: sectionId, latitude: latitude, longitude: longitude, limit: limit, offset: offset); + + if ((result[ERROR])) { + return {ERROR: result[ERROR], MESSAGE: result[MESSAGE]}; + } else { + return {ERROR: result[ERROR], DATA: (result[DATA] as List).map((e) => FeatureSectionModel.fromJson(e)).toList()}; + } + } +} diff --git a/news-app/lib/data/repositories/SetNewsViews/setNewsViewsDataRemoteSource.dart b/news-app/lib/data/repositories/SetNewsViews/setNewsViewsDataRemoteSource.dart new file mode 100644 index 00000000..aaf73efe --- /dev/null +++ b/news-app/lib/data/repositories/SetNewsViews/setNewsViewsDataRemoteSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class SetNewsViewsDataRemoteDataSource { + Future setNewsViews({required String newsId, required bool isBreakingNews}) async { + try { + final body = {if (isBreakingNews) BR_NEWS_ID: newsId, if (!isBreakingNews) NEWS_ID: newsId}; + final result = await Api.sendApiRequest(body: body, url: (isBreakingNews) ? Api.setBreakingNewsViewApi : Api.setNewsViewApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/SetNewsViews/setNewsViewsRepository.dart b/news-app/lib/data/repositories/SetNewsViews/setNewsViewsRepository.dart new file mode 100644 index 00000000..4d24abeb --- /dev/null +++ b/news-app/lib/data/repositories/SetNewsViews/setNewsViewsRepository.dart @@ -0,0 +1,19 @@ +import 'package:news/data/repositories/SetNewsViews/setNewsViewsDataRemoteSource.dart'; + +class SetNewsViewsRepository { + static final SetNewsViewsRepository setNewsViewsRepository = SetNewsViewsRepository._internal(); + + late SetNewsViewsDataRemoteDataSource setNewsViewsDataRemoteDataSource; + + factory SetNewsViewsRepository() { + setNewsViewsRepository.setNewsViewsDataRemoteDataSource = SetNewsViewsDataRemoteDataSource(); + return setNewsViewsRepository; + } + + SetNewsViewsRepository._internal(); + + Future> setNewsViews({required String newsId, required bool isBreakingNews}) async { + final result = await setNewsViewsDataRemoteDataSource.setNewsViews(newsId: newsId, isBreakingNews: isBreakingNews); + return result; + } +} diff --git a/news-app/lib/data/repositories/SetSurveyAnswer/setSurveyAnsDataRemoteSource.dart b/news-app/lib/data/repositories/SetSurveyAnswer/setSurveyAnsDataRemoteSource.dart new file mode 100644 index 00000000..31cdc070 --- /dev/null +++ b/news-app/lib/data/repositories/SetSurveyAnswer/setSurveyAnsDataRemoteSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class SetSurveyAnsRemoteDataSource { + Future setSurveyAns({required String queId, required String optId}) async { + try { + final body = {QUESTION_ID: queId, OPTION_ID: optId}; + final result = await Api.sendApiRequest(body: body, url: Api.setQueResultApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/SetSurveyAnswer/setSurveyAnsRepository.dart b/news-app/lib/data/repositories/SetSurveyAnswer/setSurveyAnsRepository.dart new file mode 100644 index 00000000..6c75b4eb --- /dev/null +++ b/news-app/lib/data/repositories/SetSurveyAnswer/setSurveyAnsRepository.dart @@ -0,0 +1,21 @@ +import 'package:news/data/repositories/SetSurveyAnswer/setSurveyAnsDataRemoteSource.dart'; +import 'package:news/utils/strings.dart'; + +class SetSurveyAnsRepository { + static final SetSurveyAnsRepository _setSurveyAnsRepository = SetSurveyAnsRepository._internal(); + + late SetSurveyAnsRemoteDataSource _setSurveyAnsRemoteDataSource; + + factory SetSurveyAnsRepository() { + _setSurveyAnsRepository._setSurveyAnsRemoteDataSource = SetSurveyAnsRemoteDataSource(); + return _setSurveyAnsRepository; + } + + SetSurveyAnsRepository._internal(); + + Future> setSurveyAns({required String queId, required String optId}) async { + final result = await _setSurveyAnsRemoteDataSource.setSurveyAns(optId: optId, queId: queId); + + return {"SetSurveyAns": result[DATA]}; + } +} diff --git a/news-app/lib/data/repositories/SetUserPreferenceCat/setUserPrefCatRemoteDataSource.dart b/news-app/lib/data/repositories/SetUserPreferenceCat/setUserPrefCatRemoteDataSource.dart new file mode 100644 index 00000000..98d9db16 --- /dev/null +++ b/news-app/lib/data/repositories/SetUserPreferenceCat/setUserPrefCatRemoteDataSource.dart @@ -0,0 +1,15 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class SetUserPrefCatRemoteDataSource { + Future setUserPrefCat({required String catId}) async { + try { + final body = {CATEGORY_ID: catId}; + final result = await Api.sendApiRequest(body: body, url: Api.setUserCatApi); + + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/SetUserPreferenceCat/setUserPrefCatRepository.dart b/news-app/lib/data/repositories/SetUserPreferenceCat/setUserPrefCatRepository.dart new file mode 100644 index 00000000..57bf348c --- /dev/null +++ b/news-app/lib/data/repositories/SetUserPreferenceCat/setUserPrefCatRepository.dart @@ -0,0 +1,21 @@ +import 'package:news/data/repositories/SetUserPreferenceCat/setUserPrefCatRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class SetUserPrefCatRepository { + static final SetUserPrefCatRepository _setUserPrefCatRepository = SetUserPrefCatRepository._internal(); + + late SetUserPrefCatRemoteDataSource _setUserPrefCatRemoteDataSource; + + factory SetUserPrefCatRepository() { + _setUserPrefCatRepository._setUserPrefCatRemoteDataSource = SetUserPrefCatRemoteDataSource(); + return _setUserPrefCatRepository; + } + + SetUserPrefCatRepository._internal(); + + Future> setUserPrefCat({required String catId}) async { + final result = await _setUserPrefCatRemoteDataSource.setUserPrefCat(catId: catId); + + return {"SetUserPrefCat": result[DATA]}; + } +} diff --git a/news-app/lib/data/repositories/Settings/settingRepository.dart b/news-app/lib/data/repositories/Settings/settingRepository.dart new file mode 100644 index 00000000..fc84378f --- /dev/null +++ b/news-app/lib/data/repositories/Settings/settingRepository.dart @@ -0,0 +1,29 @@ +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; + +class SettingsRepository { + static final SettingsRepository _settingsRepository = SettingsRepository._internal(); + late SettingsLocalDataRepository _settingsLocalDataSource; + + factory SettingsRepository() { + _settingsRepository._settingsLocalDataSource = SettingsLocalDataRepository(); + return _settingsRepository; + } + + SettingsRepository._internal(); + + Map getCurrentSettings() { + return { + "showIntroSlider": _settingsLocalDataSource.getIntroSlider(), + "languageCode": _settingsLocalDataSource.getCurrentLanguageCode(), + "theme": _settingsLocalDataSource.getCurrentTheme(), + "notification": _settingsLocalDataSource.getNotification(), + "token": _settingsLocalDataSource.getFcmToken() + }; + } + + void changeIntroSlider(bool value) => _settingsLocalDataSource.setIntroSlider(value); + + void changeFcmToken(String value) => _settingsLocalDataSource.setFcmToken(value); + + void changeNotification(bool value) => _settingsLocalDataSource.setNotification(value); +} diff --git a/news-app/lib/data/repositories/Settings/settingsLocalDataRepository.dart b/news-app/lib/data/repositories/Settings/settingsLocalDataRepository.dart new file mode 100644 index 00000000..c2c317c5 --- /dev/null +++ b/news-app/lib/data/repositories/Settings/settingsLocalDataRepository.dart @@ -0,0 +1,75 @@ +import 'package:flutter/src/foundation/change_notifier.dart'; +import 'package:hive_flutter/adapters.dart'; +import 'package:news/utils/hiveBoxKeys.dart'; +import 'package:news/utils/uiUtils.dart'; + +class SettingsLocalDataRepository { + Future setLanguagePreferences({required String code, required String id, required int rtl}) async { + final box = Hive.box(settingsBoxKey); + await Future.wait([ + box.put(currentLanguageCodeKey, code), + box.put(currentLanguageIDKey, id), + box.put(currentLanguageRTLKey, rtl), + ]); + + UiUtils.checkIfValidLocale(langCode: code); // only if you want to validate locale after setting + } + + String getCurrentLanguageCode() { + return Hive.box(settingsBoxKey).get(currentLanguageCodeKey) ?? ""; + } + + String getCurrentLanguageId() { + return Hive.box(settingsBoxKey).get(currentLanguageIDKey) ?? ''; + } + + int getCurrentLanguageRTL() { + return Hive.box(settingsBoxKey).get(currentLanguageRTLKey) ?? 0; + } + + Future setIntroSlider(bool value) async { + Hive.box(settingsBoxKey).put(introSliderKey, value); + } + + bool getIntroSlider() { + return Hive.box(settingsBoxKey).get(introSliderKey) ?? true; + } + + Future setFcmToken(String value) async { + Hive.box(settingsBoxKey).put(tokenKey, value); + } + + String getFcmToken() { + return Hive.box(settingsBoxKey).get(tokenKey) ?? ""; + } + + Future setCurrentTheme(String value) async { + Hive.box(settingsBoxKey).put(currentThemeKey, value); + } + + String getCurrentTheme() { + return Hive.box(settingsBoxKey).get(currentThemeKey) ?? ""; + } + + Future setNotification(bool value) async { + Hive.box(settingsBoxKey).put(notificationKey, value); + } + + bool getNotification() { + return Hive.box(settingsBoxKey).get(notificationKey) ?? true; + } + + Future setLocationCityKeys(double? latitude, double? longitude) async { + Hive.box(locationCityBoxKey).put(latitudeKey, latitude); + Hive.box(locationCityBoxKey).put(longitudeKey, longitude); + } + + Set getLocationCityValues() { + Set locationValues = {Hive.box(locationCityBoxKey).get(latitudeKey).toString(), Hive.box(locationCityBoxKey).get(longitudeKey).toString()}; + return locationValues; + } + + ValueListenable getVideoScreenStyle() { + return Hive.box(videoPreferenceKey).listenable(); + } +} diff --git a/news-app/lib/data/repositories/SubCatNews/subCatNewsRemoteDataSource.dart b/news-app/lib/data/repositories/SubCatNews/subCatNewsRemoteDataSource.dart new file mode 100644 index 00000000..7365236c --- /dev/null +++ b/news-app/lib/data/repositories/SubCatNews/subCatNewsRemoteDataSource.dart @@ -0,0 +1,19 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class SubCatNewsRemoteDataSource { + Future getSubCatNews({required String limit, required String offset, String? catId, String? subCatId, String? latitude, String? longitude, required String langId}) async { + try { + final body = {LIMIT: limit, OFFSET: offset, LANGUAGE_ID: langId}; + if (catId != null) body[CATEGORY_ID] = catId; + if (subCatId != null) body[SUBCAT_ID] = subCatId; + if (latitude != null && latitude != "null") body[LATITUDE] = latitude; + if (longitude != null && longitude != "null") body[LONGITUDE] = longitude; + + final result = await Api.sendApiRequest(body: body, url: Api.getNewsApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/SubCatNews/subCatRepository.dart b/news-app/lib/data/repositories/SubCatNews/subCatRepository.dart new file mode 100644 index 00000000..15eaf2bd --- /dev/null +++ b/news-app/lib/data/repositories/SubCatNews/subCatRepository.dart @@ -0,0 +1,26 @@ +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/SubCatNews/subCatNewsRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class SubCatNewsRepository { + static final SubCatNewsRepository _subCatNewsRepository = SubCatNewsRepository._internal(); + + late SubCatNewsRemoteDataSource _subCatNewsRemoteDataSource; + + factory SubCatNewsRepository() { + _subCatNewsRepository._subCatNewsRemoteDataSource = SubCatNewsRemoteDataSource(); + return _subCatNewsRepository; + } + + SubCatNewsRepository._internal(); + + Future> getSubCatNews({required String offset, required String limit, String? catId, String? subCatId, String? latitude, String? longitude, required String langId}) async { + final result = await _subCatNewsRemoteDataSource.getSubCatNews(limit: limit, offset: offset, langId: langId, subCatId: subCatId, catId: catId, latitude: latitude, longitude: longitude); + + if (result[ERROR]) { + return {ERROR: result[ERROR]}; + } else { + return {ERROR: result[ERROR], "total": result[TOTAL], "SubCatNews": (result[DATA] as List).map((e) => NewsModel.fromJson(e)).toList()}; + } + } +} diff --git a/news-app/lib/data/repositories/SubCategory/subCatRemoteDataSource.dart b/news-app/lib/data/repositories/SubCategory/subCatRemoteDataSource.dart new file mode 100644 index 00000000..3312237f --- /dev/null +++ b/news-app/lib/data/repositories/SubCategory/subCatRemoteDataSource.dart @@ -0,0 +1,15 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class SubCategoryRemoteDataSource { + Future getSubCategory({required String catId, required String langId}) async { + try { + final body = {CATEGORY_ID: catId, LANGUAGE_ID: langId}; + final result = await Api.sendApiRequest(body: body, url: Api.getSubCategoryApi); + + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/SubCategory/subCatRepository.dart b/news-app/lib/data/repositories/SubCategory/subCatRepository.dart new file mode 100644 index 00000000..c1f02817 --- /dev/null +++ b/news-app/lib/data/repositories/SubCategory/subCatRepository.dart @@ -0,0 +1,29 @@ +import 'package:flutter/cupertino.dart'; +import 'package:news/data/repositories/SubCategory/subCatRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/data/models/CategoryModel.dart'; + +class SubCategoryRepository { + static final SubCategoryRepository _subCategoryRepository = SubCategoryRepository._internal(); + + late SubCategoryRemoteDataSource _subCategoryRemoteDataSource; + + factory SubCategoryRepository() { + _subCategoryRepository._subCategoryRemoteDataSource = SubCategoryRemoteDataSource(); + return _subCategoryRepository; + } + + SubCategoryRepository._internal(); + + Future> getSubCategory({required BuildContext context, required String catId, required String langId}) async { + final result = await _subCategoryRemoteDataSource.getSubCategory(langId: langId, catId: catId); + + List subCatList = []; + + subCatList.insert(0, SubCategoryModel(id: "0", subCatName: UiUtils.getTranslatedLabel(context, 'allLbl'))); + + subCatList.addAll((result[DATA] as List).map((e) => SubCategoryModel.fromJson(e)).toList()); + return {"SubCategory": subCatList}; + } +} diff --git a/news-app/lib/data/repositories/SurveyQuestion/surveyQueRemoteDataSource.dart b/news-app/lib/data/repositories/SurveyQuestion/surveyQueRemoteDataSource.dart new file mode 100644 index 00000000..b25bf609 --- /dev/null +++ b/news-app/lib/data/repositories/SurveyQuestion/surveyQueRemoteDataSource.dart @@ -0,0 +1,15 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class SurveyQuestionRemoteDataSource { + Future getSurveyQuestions({required String langId}) async { + try { + final body = {LANGUAGE_ID: langId}; + final result = await Api.sendApiRequest(body: body, url: Api.getQueApi); + + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/SurveyQuestion/surveyQueRepository.dart b/news-app/lib/data/repositories/SurveyQuestion/surveyQueRepository.dart new file mode 100644 index 00000000..7fbe3d30 --- /dev/null +++ b/news-app/lib/data/repositories/SurveyQuestion/surveyQueRepository.dart @@ -0,0 +1,22 @@ +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/SurveyQuestion/surveyQueRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class SurveyQuestionRepository { + static final SurveyQuestionRepository _surveyQuestionRepository = SurveyQuestionRepository._internal(); + + late SurveyQuestionRemoteDataSource _surveyQuestionRemoteDataSource; + + factory SurveyQuestionRepository() { + _surveyQuestionRepository._surveyQuestionRemoteDataSource = SurveyQuestionRemoteDataSource(); + return _surveyQuestionRepository; + } + + SurveyQuestionRepository._internal(); + + Future> getSurveyQuestion({required String langId}) async { + final result = await _surveyQuestionRemoteDataSource.getSurveyQuestions(langId: langId); + + return {"SurveyQuestion": (result[DATA] as List).map((e) => NewsModel.fromSurvey(e)).toList()}; + } +} diff --git a/news-app/lib/data/repositories/Tag/tagRemoteDataSource.dart b/news-app/lib/data/repositories/Tag/tagRemoteDataSource.dart new file mode 100644 index 00000000..a4e56e5a --- /dev/null +++ b/news-app/lib/data/repositories/Tag/tagRemoteDataSource.dart @@ -0,0 +1,14 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class TagRemoteDataSource { + Future getTag({required String langId, required String offset, required String limit}) async { + try { + final body = {LANGUAGE_ID: langId, LIMIT: limit, OFFSET: offset}; + final result = await Api.sendApiRequest(body: body, url: Api.getTagsApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/Tag/tagRepository.dart b/news-app/lib/data/repositories/Tag/tagRepository.dart new file mode 100644 index 00000000..73f66da9 --- /dev/null +++ b/news-app/lib/data/repositories/Tag/tagRepository.dart @@ -0,0 +1,22 @@ +import 'package:news/data/models/TagModel.dart'; +import 'package:news/utils/strings.dart'; +import 'package:news/data/repositories/Tag/tagRemoteDataSource.dart'; + +class TagRepository { + static final TagRepository _tagRepository = TagRepository._internal(); + + late TagRemoteDataSource _tagRemoteDataSource; + + factory TagRepository() { + _tagRepository._tagRemoteDataSource = TagRemoteDataSource(); + return _tagRepository; + } + + TagRepository._internal(); + + Future> getTag({required String langId, required String offset, required String limit}) async { + final result = await _tagRemoteDataSource.getTag(langId: langId, limit: limit, offset: offset); + + return {"Tag": (result[DATA] as List).map((e) => TagModel.fromJson(e)).toList(), "total": result[TOTAL]}; + } +} diff --git a/news-app/lib/data/repositories/TagNews/tagNewsDataSource.dart b/news-app/lib/data/repositories/TagNews/tagNewsDataSource.dart new file mode 100644 index 00000000..3fff694e --- /dev/null +++ b/news-app/lib/data/repositories/TagNews/tagNewsDataSource.dart @@ -0,0 +1,17 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class TagNewsRemoteDataSource { + Future getTagNews({required String tagId, required String langId, String? latitude, String? longitude}) async { + try { + final body = {TAG_ID: tagId, LANGUAGE_ID: langId}; + if (latitude != null && latitude != "null") body[LATITUDE] = latitude; + if (longitude != null && longitude != "null") body[LONGITUDE] = longitude; + + final result = await Api.sendApiRequest(body: body, url: Api.getNewsApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/TagNews/tagNewsRepository.dart b/news-app/lib/data/repositories/TagNews/tagNewsRepository.dart new file mode 100644 index 00000000..448a70e0 --- /dev/null +++ b/news-app/lib/data/repositories/TagNews/tagNewsRepository.dart @@ -0,0 +1,22 @@ +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/TagNews/tagNewsDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class TagNewsRepository { + static final TagNewsRepository _tagNewsRepository = TagNewsRepository._internal(); + + late TagNewsRemoteDataSource _tagNewsRemoteDataSource; + + factory TagNewsRepository() { + _tagNewsRepository._tagNewsRemoteDataSource = TagNewsRemoteDataSource(); + return _tagNewsRepository; + } + + TagNewsRepository._internal(); + + Future> getTagNews({required String tagId, required String langId, String? latitude, String? longitude}) async { + final result = await _tagNewsRemoteDataSource.getTagNews(tagId: tagId, langId: langId, latitude: latitude, longitude: longitude); + + return {"TagNews": (result[DATA] as List).map((e) => NewsModel.fromJson(e)).toList()}; + } +} diff --git a/news-app/lib/data/repositories/UserByCategory/userByCatRemoteDataSource.dart b/news-app/lib/data/repositories/UserByCategory/userByCatRemoteDataSource.dart new file mode 100644 index 00000000..8eb6eec1 --- /dev/null +++ b/news-app/lib/data/repositories/UserByCategory/userByCatRemoteDataSource.dart @@ -0,0 +1,12 @@ +import 'package:news/utils/api.dart'; + +class UserByCatRemoteDataSource { + Future getUserById() async { + try { + final result = await Api.sendApiRequest(body: {}, url: Api.getUserByIdApi); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/UserByCategory/userByCatRepository.dart b/news-app/lib/data/repositories/UserByCategory/userByCatRepository.dart new file mode 100644 index 00000000..f4d6b26e --- /dev/null +++ b/news-app/lib/data/repositories/UserByCategory/userByCatRepository.dart @@ -0,0 +1,21 @@ +import 'package:news/data/repositories/UserByCategory/userByCatRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class UserByCatRepository { + static final UserByCatRepository _userByCatRepository = UserByCatRepository._internal(); + + late UserByCatRemoteDataSource _userByCatRemoteDataSource; + + factory UserByCatRepository() { + _userByCatRepository._userByCatRemoteDataSource = UserByCatRemoteDataSource(); + return _userByCatRepository; + } + + UserByCatRepository._internal(); + + Future> getUserById() async { + final result = await _userByCatRemoteDataSource.getUserById(); + + return {"UserByCat": result[DATA]}; + } +} diff --git a/news-app/lib/data/repositories/Videos/videoRemoteDataSource.dart b/news-app/lib/data/repositories/Videos/videoRemoteDataSource.dart new file mode 100644 index 00000000..d00dceab --- /dev/null +++ b/news-app/lib/data/repositories/Videos/videoRemoteDataSource.dart @@ -0,0 +1,18 @@ +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; + +class VideoRemoteDataSource { + Future getVideos({required String limit, required String offset, required String langId, String? latitude, String? longitude}) async { + try { + final body = {LIMIT: limit, OFFSET: offset, LANGUAGE_ID: langId}; + if (latitude != null && latitude != "null") body[LATITUDE] = latitude; + if (longitude != null && longitude != "null") body[LONGITUDE] = longitude; + + final result = await Api.sendApiRequest(body: body, url: Api.getVideosApi); + + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/Videos/videosRepository.dart b/news-app/lib/data/repositories/Videos/videosRepository.dart new file mode 100644 index 00000000..cde40824 --- /dev/null +++ b/news-app/lib/data/repositories/Videos/videosRepository.dart @@ -0,0 +1,23 @@ +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/utils/strings.dart'; +import 'package:news/data/repositories/Videos/videoRemoteDataSource.dart'; + +class VideoRepository { + static final VideoRepository _videoRepository = VideoRepository._internal(); + + late VideoRemoteDataSource _videoRemoteDataSource; + + factory VideoRepository() { + _videoRepository._videoRemoteDataSource = VideoRemoteDataSource(); + return _videoRepository; + } + + VideoRepository._internal(); + + Future> getVideo({required String offset, required String limit, required String langId, String? latitude, String? longitude}) async { + final result = await _videoRemoteDataSource.getVideos(limit: limit, offset: offset, langId: langId, latitude: latitude, longitude: longitude); + return (result[ERROR]) + ? {ERROR: result[ERROR], MESSAGE: result[MESSAGE]} + : {ERROR: result[ERROR], "total": result[TOTAL], "Video": (result[DATA] as List).map((e) => NewsModel.fromVideos(e)).toList()}; + } +} diff --git a/news-app/lib/data/repositories/language/languageRemoteDataSource.dart b/news-app/lib/data/repositories/language/languageRemoteDataSource.dart new file mode 100644 index 00000000..4db0ebbb --- /dev/null +++ b/news-app/lib/data/repositories/language/languageRemoteDataSource.dart @@ -0,0 +1,12 @@ +import 'package:news/utils/api.dart'; + +class LanguageRemoteDataSource { + Future getLanguages() async { + try { + final result = await Api.sendApiRequest(url: Api.getLanguagesApi, body: {}); + return result; + } catch (e) { + throw ApiMessageAndCodeException(errorMessage: e.toString()); + } + } +} diff --git a/news-app/lib/data/repositories/language/languageRepository.dart b/news-app/lib/data/repositories/language/languageRepository.dart new file mode 100644 index 00000000..9c2311a6 --- /dev/null +++ b/news-app/lib/data/repositories/language/languageRepository.dart @@ -0,0 +1,22 @@ +import 'package:news/data/models/appLanguageModel.dart'; +import 'package:news/data/repositories/language/languageRemoteDataSource.dart'; +import 'package:news/utils/strings.dart'; + +class LanguageRepository { + static final LanguageRepository _languageRepository = LanguageRepository._internal(); + + late LanguageRemoteDataSource _languageRemoteDataSource; + + factory LanguageRepository() { + _languageRepository._languageRemoteDataSource = LanguageRemoteDataSource(); + return _languageRepository; + } + + LanguageRepository._internal(); + + Future> getLanguage() async { + final result = await _languageRemoteDataSource.getLanguages(); + + return {"Language": (result[DATA] as List).map((e) => LanguageModel.fromJson(e)).toList()}; + } +} diff --git a/news-app/lib/main.dart b/news-app/lib/main.dart new file mode 100644 index 00000000..7c965efd --- /dev/null +++ b/news-app/lib/main.dart @@ -0,0 +1,5 @@ +import 'app/app.dart'; + +Future main() async { + await initializeApp(); +} diff --git a/news-app/lib/ui/screens/AddEditNews/AddNews.dart b/news-app/lib/ui/screens/AddEditNews/AddNews.dart new file mode 100644 index 00000000..33c00392 --- /dev/null +++ b/news-app/lib/ui/screens/AddEditNews/AddNews.dart @@ -0,0 +1,1428 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:flutter/cupertino.dart'; +import 'package:intl/intl.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:news/cubits/AddNewsCubit.dart'; +import 'package:news/cubits/GetUserDraftedNewsCubit.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; +import 'package:news/cubits/deleteImageId.dart'; +import 'package:news/cubits/getUserNewsCubit.dart'; +import 'package:news/cubits/languageCubit.dart'; +import 'package:news/cubits/locationCityCubit.dart'; +import 'package:news/cubits/slugCheckCubit.dart'; +import 'package:news/cubits/tagCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/categoryCubit.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/updateBottomsheetContentCubit.dart'; +import 'package:news/data/models/TagModel.dart'; +import 'package:news/data/models/CategoryModel.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/models/appLanguageModel.dart'; +import 'package:news/data/models/locationCityModel.dart'; +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; +import 'package:news/ui/screens/AddEditNews/Widgets/customBottomsheet.dart'; +import 'package:news/ui/screens/AddEditNews/Widgets/geminiService.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; +import 'package:news/ui/widgets/customTextBtn.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import 'package:news/ui/screens/AddEditNews/NewsDescription.dart'; +import 'package:news/utils/internetConnectivity.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/utils/validators.dart'; + +class AddNews extends StatefulWidget { + NewsModel? model; + bool isEdit; + String from; + AddNews({super.key, this.model, required this.isEdit, required this.from}); + + @override + _AddNewsState createState() => _AddNewsState(); + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute(builder: (_) => AddNews(model: arguments['model'], isEdit: arguments['isEdit'], from: arguments['from'])); + } +} + +class _AddNewsState extends State { + final GlobalKey _scaffoldKey = GlobalKey(); + final GlobalKey _formkey = GlobalKey(); + String catSel = "", subCatSel = "", conType = "", conTypeId = "standard_post", langId = "", langName = "", locationSel = "", publishDate = ""; + String? title, catSelId, subSelId, showTill, url, desc, summDesc, locationSelId, metaKeyword, metaDescription, metaTitle, slug; + int? catIndex, locationIndex, isDraft; + List tagsName = [], tagsId = []; + Map contentType = {}; + List otherImage = []; + File? image, videoUpload; + bool isNext = false, isDescLoading = true, isMetaKeyword = false, isMetaDescription = false, isMetaTitle = false, isSlug = false; + TextEditingController titleC = TextEditingController(), + urlC = TextEditingController(), + metaTitleC = TextEditingController(), + metaDescriptionC = TextEditingController(), + metaKeywordC = TextEditingController(), + slugC = TextEditingController(); + List categories = []; + List locationCities = []; + + var now = DateTime.now(); + var formatter = DateFormat('yyyy-MM-dd'); + String currentDate = ""; + DateTime? selectedPublishDate; + DateTime? selectedShowTillDate; + + clearText() { + setState(() { + catSel = ""; + subCatSel = ""; + locationSel = ""; + publishDate = ""; + conType = UiUtils.getTranslatedLabel(context, 'stdPostLbl'); + title = catSelId = subSelId = showTill = url = catIndex = locationSelId = locationIndex = image = videoUpload = desc = null; + conTypeId = 'standard_post'; + tagsName = tagsId = []; + otherImage = []; + isNext = false; + titleC.clear(); + urlC.clear(); + metaTitleC.clear(); + metaDescriptionC.clear(); + metaKeywordC.clear(); + slugC.clear(); + }); + } + + setContentType() { + contentType = { + "standard_post": UiUtils.getTranslatedLabel(context, 'stdPostLbl'), + "video_youtube": UiUtils.getTranslatedLabel(context, 'videoYoutubeLbl'), + "video_other": UiUtils.getTranslatedLabel(context, 'videoOtherUrlLbl'), + "video_upload": UiUtils.getTranslatedLabel(context, 'videoUploadLbl'), + }; + } + + addDataFromModel() { + if (widget.model != null) { + Future.delayed(Duration.zero, () { + setState(() { + setContentType(); + title = titleC.text = widget.model!.title!; + catSel = widget.model!.categoryName!; + catSelId = widget.model!.categoryId!; + subCatSel = widget.model!.subCatName!; + subSelId = widget.model!.subCatId!; + langId = widget.model!.langId!; + updateDropdownsForLanguage(); + for (final entry in contentType.entries) { + if (entry.key == widget.model!.contentType!) { + conType = entry.value; + conTypeId = entry.key; + } + } + if (conTypeId == "video_youtube" || conTypeId == "video_other" || conTypeId == "video_upload") urlC.text = widget.model!.contentValue!; + if (widget.model!.tagName! != "") tagsName = widget.model!.tagName!.split(','); + if (widget.model!.tagId! != "") tagsId = (widget.model!.tagId!.contains(",")) ? widget.model!.tagId!.split(",") : [widget.model!.tagId!]; + if (widget.model!.showTill != "0000-00-00" && widget.model!.showTill != null) showTill = widget.model!.showTill!; + if (widget.model!.publishDate != "0000-00-00" || widget.model!.publishDate != "") publishDate = widget.model!.publishDate ?? ""; + desc = widget.model!.desc ?? ""; + summDesc = widget.model!.shortDesc ?? ""; + metaTitleC.text = metaTitle = widget.model!.metaTitle ?? ""; + metaDescriptionC.text = metaDescription = widget.model!.metaDescription ?? ""; + metaKeywordC.text = metaKeyword = widget.model!.metaKeyword ?? ""; + slugC.text = slug = widget.model!.slug!; + locationSelId = widget.model!.locationId; + locationSel = widget.model!.locationName ?? ""; + }); + }); + } + } + + @override + void initState() { + getStandardPostLabel(); + getCategory(); + getTag(); + getLanguageData(); + if (context.read().getLocationWiseNewsMode() == "1") getLocationCities(); + if (widget.isEdit) addDataFromModel(); + currentDate = formatter.format(now); + super.initState(); + } + + @override + void dispose() { + titleC.dispose(); + urlC.dispose(); + metaTitleC.dispose(); + metaDescriptionC.dispose(); + metaKeywordC.dispose(); + slugC.dispose(); + + super.dispose(); + } + + Future getStandardPostLabel() async { + conType = UiUtils.getTranslatedLabel(context, 'stdPostLbl'); + setState(() {}); + } + + Future getLanguageData() async { + Future.delayed(Duration.zero, () { + if (widget.isEdit) { + context.read().getLanguage().then((value) { + for (int i = 0; i < value.length; i++) { + if (widget.model!.langId! == value[i].id) { + setState(() => langName = value[i].language!); + } + } + }); + } else { + context.read().getLanguage(); + } + }); + } + + void getCategory({String? languageId}) { + context.read().getCategory(langId: languageId ?? (context.read().state.id)); + } + + void getTag({String? languageId}) { + Future.delayed(Duration.zero, () { + context.read().getTags(langId: languageId ?? (context.read().state.id)); + }); + } + + void getLocationCities() { + Future.delayed(Duration.zero, () { + context.read().getLocationCity(); + }); + } + + getAppBar() { + if (!isNext) { + return PreferredSize( + preferredSize: const Size(double.infinity, 45), + child: UiUtils.applyBoxShadow( + context: context, + child: AppBar( + centerTitle: false, + backgroundColor: Colors.transparent, + title: Transform( + transform: Matrix4.translationValues(-20.0, 0.0, 0.0), + child: CustomTextLabel( + text: (widget.isEdit) ? 'editNewsLbl' : 'createNewsLbl', + textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600, letterSpacing: 0.5))), + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: InkWell( + onTap: () { + if (!isNext) { + Navigator.of(context).pop(); + } else { + setState(() => isNext = false); + } + }, + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + child: Icon(Icons.arrow_back, color: UiUtils.getColorScheme(context).primaryContainer)), + ), + actions: [ + Container( + padding: const EdgeInsetsDirectional.only(end: 20), + alignment: Alignment.center, + child: CustomTextLabel(text: 'step1Of2Lbl', textStyle: Theme.of(context).textTheme.bodySmall!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6)))) + ], + ), + )); + } + } + + showLanguageModalBottomSheet(BuildContext context) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) { + return CustomBottomsheet( + context: context, + titleTxt: 'chooseLanLbl', + language_id: langId, + listLength: context.read().langList().length, + listViewChild: (context, index) { + return langListItem(index, context.read().langList()); + }); + }); + } + + Widget languageSelName() { + return BlocConsumer( + listener: (context, state) { + if (state is LanguageFetchSuccess) { + context.read().updateLanguageContent(state.language); + } + }, + builder: (context, state) { + if (state is LanguageFetchSuccess && state.language.isNotEmpty) { + if (state.language.length == 1) { + langId = state.language.first.id!; + langName = state.language.first.language!; + } + return Padding( + padding: const EdgeInsets.only(top: 18.0), + child: InkWell( + onTap: () => showLanguageModalBottomSheet(context), + child: UiUtils.setRowWithContainer( + context: context, + firstChild: CustomTextLabel( + text: langName == "" ? 'chooseLanLbl' : langName, + textStyle: Theme.of(context) + .textTheme + .titleMedium! + .copyWith(color: langName == "" ? UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6) : UiUtils.getColorScheme(context).primaryContainer)), + isContentTypeUpload: false)), + ); + } + return const SizedBox.shrink(); + }, + ); + } + + Widget catSelectionName() { + if (langId.isNotEmpty) { + return BlocConsumer( + listener: (context, state) { + if (catSel != "" || (widget.isEdit && widget.model!.categoryName != null)) catIndex = context.read().getCategoryIndex(categoryName: catSel); + }, + builder: (context, state) { + if (state is CategoryFetchSuccess) { + if (state.category.isNotEmpty && state.category.length == 1) catSelId = state.category.first.id; + if (state.category.isNotEmpty && state.category.length == 1) catIndex = 0; + if (widget.isEdit) catIndex = context.read().getCatList().indexWhere((element) => element.id == widget.model!.categoryId!); + + context.read().updateCategoryContent(state.category); + + return Padding( + padding: const EdgeInsets.only(top: 18.0), + child: InkWell( + onTap: () => showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) { + return CustomBottomsheet( + context: context, + titleTxt: 'selCatLbl', + listLength: state.category.length, + language_id: langId, + listViewChild: (context, index) { + return catListItem(index, state.category); + }); + }), + child: UiUtils.setRowWithContainer( + context: context, + firstChild: CustomTextLabel( + text: (state.category.length == 1) + ? catSel = state.category.first.categoryName! + : (catSel == "") + ? 'catLbl' + : catSel, + textStyle: Theme.of(context) + .textTheme + .titleMedium! + .copyWith(color: catSel == "" ? UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6) : UiUtils.getColorScheme(context).primaryContainer)), + isContentTypeUpload: false), + ), + ); + } + return const SizedBox.shrink(); + }, + ); + } else { + return const SizedBox.shrink(); + } + } + + Widget subCatSelectionName() { + if ((subCatSel != "" && widget.isEdit) || + (catIndex != null) && (!catIndex!.isNegative && context.read().getCatList().isNotEmpty) && (context.read().getCatList()[catIndex!].subData!.isNotEmpty)) { + return BlocBuilder( + builder: (context, state) { + return Padding( + padding: const EdgeInsets.only(top: 18.0), + child: InkWell( + onTap: () => showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) { + return CustomBottomsheet( + context: context, + titleTxt: 'selSubCatLbl', + language_id: langId, + listLength: context.read().getCatList()[catIndex!].subData!.length, + listViewChild: (context, index) => subCatListItem(index, context.read().getCatList())); + }), + child: UiUtils.setRowWithContainer( + context: context, + firstChild: CustomTextLabel( + text: (subCatSel == "") ? 'subcatLbl' : subCatSel, + textStyle: Theme.of(context) + .textTheme + .titleMedium! + .copyWith(color: subCatSel == "" ? UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6) : UiUtils.getColorScheme(context).primaryContainer)), + isContentTypeUpload: false), + ), + ); + }, + ); + } else { + return const SizedBox.shrink(); + } + } + + Widget contentTypeSelName() { + return Padding( + padding: const EdgeInsets.only(top: 18.0), + child: InkWell( + onTap: () => contentTypeBottomSheet(), + child: UiUtils.setRowWithContainer( + context: context, + firstChild: CustomTextLabel( + text: conType == "" ? 'contentTypeLbl' : conType, + textStyle: Theme.of(context) + .textTheme + .titleMedium! + .copyWith(color: conType == "" ? UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6) : UiUtils.getColorScheme(context).primaryContainer)), + isContentTypeUpload: false), + ), + ); + } + + Widget contentVideoUpload() { + return conType == UiUtils.getTranslatedLabel(context, 'videoUploadLbl') + ? Padding( + padding: const EdgeInsets.only(top: 18.0), + child: InkWell( + splashColor: Colors.transparent, + onTap: () => _getFromGalleryVideo(), + child: UiUtils.setRowWithContainer( + context: context, + firstChild: Expanded( + child: CustomTextLabel( + text: videoUpload == null ? 'uploadVideoLbl' : videoUpload!.path.split('/').last, + maxLines: 2, + softWrap: true, + textStyle: Theme.of(context).textTheme.titleMedium!.copyWith( + overflow: TextOverflow.ellipsis, + color: videoUpload == null ? UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6) : UiUtils.getColorScheme(context).primaryContainer))), + isContentTypeUpload: true)), + ) + : const SizedBox.shrink(); + } + + Widget contentUrlForVideoUpload() { + if (conTypeId == "video_upload" && videoUpload == null && urlC.text.isNotEmpty) { + return Container( + width: double.maxFinite, + margin: const EdgeInsetsDirectional.only(top: 18), + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: UiUtils.getColorScheme(context).surface), + child: InkWell( + onTap: () async { + //open url in newsVideo screen + Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 1, "model": widget.model, "otherVideos": []}); + }, + child: Wrap( + alignment: WrapAlignment.center, + spacing: 10, + runSpacing: 25, + children: [ + const Icon(Icons.play_circle_fill), + CustomTextLabel( + text: 'previewLbl', + maxLines: 3, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer)) + ], + ), + )); + } else { + return const SizedBox.shrink(); + } + } + + Widget contentUrlSet() { + if (conType == UiUtils.getTranslatedLabel(context, 'videoYoutubeLbl') || conType == UiUtils.getTranslatedLabel(context, 'videoOtherUrlLbl')) { + return Container( + width: double.maxFinite, + margin: const EdgeInsetsDirectional.only(top: 18), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: UiUtils.getColorScheme(context).surface), + child: TextFormField( + textInputAction: TextInputAction.next, + maxLines: 1, + controller: urlC, + style: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer), + validator: (val) => conType == UiUtils.getTranslatedLabel(context, 'videoYoutubeLbl') ? Validators.youtubeUrlValidation(val!, context) : Validators.urlValidation(val!, context), + onChanged: (String value) => setState(() => url = value), + onTapOutside: (val) => FocusScope.of(context).unfocus(), + decoration: InputDecoration( + hintText: conType == UiUtils.getTranslatedLabel(context, 'videoYoutubeLbl') ? UiUtils.getTranslatedLabel(context, 'youtubeUrlLbl') : UiUtils.getTranslatedLabel(context, 'otherUrlLbl'), + hintStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6)), + filled: true, + fillColor: UiUtils.getColorScheme(context).surface, + contentPadding: const EdgeInsets.symmetric(horizontal: 25, vertical: 17), + focusedBorder: OutlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(10.0)), + enabledBorder: OutlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(10.0))), + )); + } else { + return const SizedBox.shrink(); + } + } + + contentTypeBottomSheet() { + showModalBottomSheet( + context: context, + elevation: 3.0, + isScrollControlled: true, + //it will be closed only when user click On Save button & not by clicking anywhere else in screen + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.only(topLeft: Radius.circular(30), topRight: Radius.circular(30))), + enableDrag: false, + builder: (BuildContext context) => Container( + padding: const EdgeInsetsDirectional.only(bottom: 15.0, top: 15.0, start: 20.0, end: 20.0), + decoration: BoxDecoration(borderRadius: const BorderRadius.only(topLeft: Radius.circular(30), topRight: Radius.circular(30)), color: UiUtils.getColorScheme(context).surface), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + CustomTextLabel( + text: 'selContentTypeLbl', textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold, color: UiUtils.getColorScheme(context).primaryContainer)), + Padding( + padding: const EdgeInsetsDirectional.only(top: 10.0, bottom: 15.0), + child: Column( + children: contentType.entries.map((entry) { + return UiUtils.setTopPaddingParent( + childWidget: InkWell( + onTap: () { + if (conType != entry.value || conTypeId != entry.key) { + urlC.clear(); + conType = entry.value; + conTypeId = entry.key; + } + if (widget.isEdit && conTypeId == widget.model!.contentType) { + urlC.text = widget.model!.contentValue!; + } + setState(() {}); + Navigator.pop(context); + }, + child: + UiUtils.setBottomsheetContainer(entryId: entry.value, listItem: entry.value, compareTo: (widget.isEdit) ? widget.model!.contentType! : conType, context: context))); + }).toList())) + ], + ))); + } + + Widget newsTitleName() { + return Container( + width: double.maxFinite, + margin: const EdgeInsetsDirectional.only(top: 7), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: UiUtils.getColorScheme(context).surface), + child: TextFormField( + textInputAction: TextInputAction.next, + maxLines: 1, + controller: titleC, + style: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer), + validator: (val) => Validators.titleValidation(val!, context), + onChanged: (String value) => setState(() => title = value), + onTapOutside: (val) => FocusScope.of(context).unfocus(), + decoration: InputDecoration( + hintText: UiUtils.getTranslatedLabel(context, 'titleLbl'), + hintStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6)), + filled: true, + fillColor: UiUtils.getColorScheme(context).surface, + contentPadding: const EdgeInsets.symmetric(horizontal: 25, vertical: 17), + focusedBorder: OutlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(10.0)), + enabledBorder: OutlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(10.0))))); + } + + Widget tagSelectionName() { + return BlocConsumer(listener: (context, state) { + if (state is TagFetchSuccess) { + context.read().updateTagsContent(state.tag); + } + }, builder: (context, state) { + if ((state is TagFetchSuccess && state.total > 0) || (widget.isEdit && tagsName.isNotEmpty)) { + return Padding( + padding: const EdgeInsets.only(top: 18.0), + child: InkWell( + onTap: () => showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) { + return CustomBottomsheet( + context: context, + titleTxt: 'selTagLbl', + language_id: langId, + listLength: (state as TagFetchSuccess).tag.length, + listViewChild: (context, index) => tagListItem(index, state.tag)); + }), + child: Container( + width: double.maxFinite, + constraints: const BoxConstraints(minHeight: 55), + alignment: Alignment.centerLeft, + padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 7), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: UiUtils.getColorScheme(context).surface), + child: tagsId.isEmpty + ? CustomTextLabel(text: 'tagLbl', textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6))) + : SizedBox( + height: MediaQuery.of(context).size.height * 0.06, + child: ListView.builder( + physics: const AlwaysScrollableScrollPhysics(), + shrinkWrap: true, + scrollDirection: Axis.horizontal, + itemCount: tagsName.length, + itemBuilder: (context, index) { + return Padding( + padding: EdgeInsetsDirectional.only(start: index != 0 ? 10.0 : 0), + child: Stack( + children: [ + Container( + margin: const EdgeInsetsDirectional.only(end: 7.5, top: 7.5), + padding: const EdgeInsetsDirectional.all(7.0), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(5.0), color: UiUtils.getColorScheme(context).primaryContainer), + alignment: Alignment.center, + child: CustomTextLabel(text: tagsName[index], textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: UiUtils.getColorScheme(context).surface)), + ), + Positioned.directional( + textDirection: Directionality.of(context), + end: 0, + child: Container( + height: 15, + width: 15, + alignment: Alignment.center, + margin: const EdgeInsets.all(3.0), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(25.0), color: Theme.of(context).primaryColor), + child: InkWell( + child: Icon(Icons.close, size: 11, color: UiUtils.getColorScheme(context).surface), + onTap: () { + setState(() { + tagsName.remove(tagsName[index]); + tagsId.remove(tagsId[index]); + }); + }, + ))) + ], + ), + ); + }, + ), + ), + ), + ), + ); + } else { + return const SizedBox.shrink(); + } + }); + } + + Widget locationCitySelectionName() { + return BlocConsumer( + listener: (context, state) { + if (locationSel != "" || (widget.isEdit && widget.model!.locationId != null)) locationIndex = context.read().getLocationIndex(locationName: locationSel); + if (state is LocationCityFetchSuccess) { + context.read().updateLocationContent(state.locationCity); + } + }, + builder: (context, state) { + if (state is LocationCityFetchSuccess) { + if (state.locationCity.isNotEmpty && state.locationCity.length == 1) { + locationSelId = state.locationCity.first.id; + locationIndex = 0; + } + return Padding( + padding: const EdgeInsets.only(top: 18.0), + child: InkWell( + onTap: () => showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) { + return CustomBottomsheet( + context: context, + titleTxt: 'selLocationLbl', + language_id: langId, + listLength: state.locationCity.length, + listViewChild: (context, index) { + return locationCityListItem(index, state.locationCity); + }); + }), + child: UiUtils.setRowWithContainer( + context: context, + firstChild: CustomTextLabel( + text: (state.locationCity.length == 1) + ? locationSel = state.locationCity.first.locationName + : (locationSel == "") + ? 'selLocationLbl' + : locationSel, + textStyle: Theme.of(context) + .textTheme + .titleMedium! + .copyWith(color: locationSel == "" ? UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6) : UiUtils.getColorScheme(context).primaryContainer)), + isContentTypeUpload: false), + ), + ); + } + return const SizedBox.shrink(); + }, + ); + } + + Widget showTillSelDate() { + return UiUtils.setTopPaddingParent( + childWidget: InkWell( + onTap: () async { + DateTime? showTillDate = await showDatePicker( + context: context, initialDate: DateTime.now().add(const Duration(days: 1)), firstDate: DateTime.now().subtract(const Duration(days: -1)), lastDate: DateTime(DateTime.now().year + 1)); + + if (showTillDate != null) { + //pickedDate output format => 2021-03-10 00:00:00.000 + String formattedDate = DateFormat('yyyy-MM-dd').format(showTillDate); + setState(() => showTill = formattedDate); + + selectedShowTillDate = showTillDate; + if (selectedPublishDate != null && selectedShowTillDate!.isBefore(selectedPublishDate!)) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'dateConfirmation'), context); + return; + } + } + }, + child: Container( + width: double.maxFinite, + margin: const EdgeInsetsDirectional.only(top: 7), + padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 17), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: UiUtils.getColorScheme(context).surface), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomTextLabel( + text: showTill == null ? 'showTilledDate' : showTill!, + textStyle: Theme.of(context) + .textTheme + .titleMedium! + .copyWith(color: showTill == null ? UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6) : UiUtils.getColorScheme(context).primaryContainer)), + Align(alignment: Alignment.centerRight, child: Icon(Icons.calendar_month_outlined, color: UiUtils.getColorScheme(context).primaryContainer)) + ], + ), + ), + ), + ); + } + + void _showPicker() { + UiUtils().showUploadImageBottomsheet(context: context, onCamera: _getFromCamera, onGallery: _getFromGallery); + } + + _getFromCamera() async { + try { + XFile? pickedFile = await ImagePicker().pickImage(source: ImageSource.camera); + if (pickedFile != null) { + setState(() { + image = File(pickedFile.path); + }); + Navigator.of(context).pop(); //pop dialog + } + } catch (e) {} + } + + _getFromGallery() async { + XFile? pickedFile = await ImagePicker().pickImage( + source: ImageSource.gallery, + maxWidth: 1800, + maxHeight: 1800, + ); + if (pickedFile != null) { + setState(() { + image = File(pickedFile.path); + Navigator.of(context).pop(); + }); + } + } + + _getFromGalleryOther() async { + List? pickedFileList = await ImagePicker().pickMultiImage(maxWidth: 1800, maxHeight: 1800); + for (int i = 0; i < pickedFileList.length; i++) { + otherImage.add(File(pickedFileList[i].path)); + } + setState(() {}); + } + + _getFromGalleryVideo() async { + final XFile? file = await ImagePicker().pickVideo(source: ImageSource.gallery, maxDuration: const Duration(seconds: 10)); + if (file != null) { + setState(() => videoUpload = File(file.path)); + } + } + + Widget uploadMainImage() { + return InkWell( + onTap: () => _showPicker(), + child: (widget.isEdit || image != null) + ? Padding( + padding: const EdgeInsets.only(top: 25), + child: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: (image == null) + ? CustomNetworkImage(networkImageUrl: widget.model!.image!, width: double.maxFinite, height: 125, fit: BoxFit.cover, isVideo: false) + : Image.file(image!, height: 125, width: double.maxFinite, fit: BoxFit.fill)), + ) + : Container( + height: 125, + width: double.maxFinite, + padding: const EdgeInsets.only(top: 25), + child: UiUtils.dottedRRectBorder( + childWidget: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + Icon(Icons.image, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)), + Padding( + padding: const EdgeInsetsDirectional.only(start: 10), + child: CustomTextLabel( + text: 'uploadMainImageLbl', textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5))), + ) + ])), + ), + ); + } + + Widget uploadOtherImage() { + return otherImage.isEmpty + ? InkWell( + onTap: () => _getFromGalleryOther(), + child: Container( + height: 125, + width: double.maxFinite, + padding: const EdgeInsets.only(top: 25), + child: UiUtils.dottedRRectBorder( + childWidget: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + Icon(Icons.image, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)), + Padding( + padding: const EdgeInsetsDirectional.only(start: 10), + child: CustomTextLabel( + text: 'uploadOtherImageLbl', + textAlign: TextAlign.center, + textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5))), + ) + ]))), + ) + : Padding( + padding: const EdgeInsets.only(top: 25), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + onTap: () => _getFromGalleryOther(), + child: SizedBox( + height: 125, + width: 95, + child: UiUtils.dottedRRectBorder( + childWidget: Column(crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ + Icon(Icons.image, size: 15, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)), + CustomTextLabel( + text: 'uploadOtherImageLbl', + textAlign: TextAlign.center, + textStyle: Theme.of(context).textTheme.bodySmall!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5)), + ) + ])), + ), + ), + Expanded( + child: SizedBox( + height: 125, + child: ListView.builder( + physics: const AlwaysScrollableScrollPhysics(), + shrinkWrap: true, + scrollDirection: Axis.horizontal, + itemCount: otherImage.length, + itemBuilder: (context, index) { + return Stack(clipBehavior: Clip.none, children: [ + Padding( + padding: const EdgeInsetsDirectional.only(start: 10), + child: ClipRRect(borderRadius: BorderRadius.circular(10), child: Image.file(otherImage[index], height: 125, width: 95, fit: BoxFit.fill))), + Positioned.directional( + textDirection: Directionality.of(context), + end: 0, + top: 0, + child: Container( + height: 18, + width: 18, + alignment: Alignment.center, + margin: const EdgeInsets.all(3.0), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(25.0), color: Theme.of(context).primaryColor), + child: InkWell( + child: Icon(Icons.close, size: 13, color: UiUtils.getColorScheme(context).surface), + onTap: () { + otherImage.removeAt(index); //remove currently uploaded image + setState(() {}); + }, + ))) + ]); + }, + )), + ) + ], + ), + ); + } + + Widget modelOtherImage() { + return widget.model!.imageDataList!.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(top: 25), + child: SizedBox( + height: 125, + child: ListView.builder( + physics: const AlwaysScrollableScrollPhysics(), + shrinkWrap: true, + scrollDirection: Axis.horizontal, + itemCount: widget.model!.imageDataList!.length, + itemBuilder: (context, index) { + return Container( + margin: EdgeInsets.zero, + child: Stack( + children: [ + Padding( + padding: const EdgeInsetsDirectional.only(top: 10, end: 8), + child: ClipRRect( + borderRadius: BorderRadius.circular(10.0), + child: CustomNetworkImage(networkImageUrl: widget.model!.imageDataList![index].otherImage!, isVideo: false, fit: BoxFit.cover, height: 125, width: 95)), + ), + BlocConsumer( + bloc: context.read(), + listener: (context, state) { + if (state is DeleteImageSuccess) { + context.read().deleteImageId(index); + showSnackBar(state.message, context); + setState(() {}); + } + }, + builder: (context, state) { + return Positioned.directional( + textDirection: Directionality.of(context), + end: 0, + child: Container( + height: 18, + width: 18, + alignment: Alignment.center, + margin: const EdgeInsets.all(3.0), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(25.0), color: Theme.of(context).primaryColor), + child: InkWell( + child: Icon(Icons.close, size: 13, color: UiUtils.getColorScheme(context).surface), + onTap: () { + context.read().setDeleteImage(imageId: widget.model!.imageDataList![index].id!); + setState(() {}); + }, + ))); + }) + ], + ), + ); + }, + ))) + : const SizedBox.shrink(); + } + + Widget publishDateSelection() { + return UiUtils.setTopPaddingParent( + childWidget: InkWell( + onTap: () async { + DateTime? pickedDate = await showDatePicker(context: context, initialDate: DateTime.now(), firstDate: DateTime.now(), lastDate: DateTime(DateTime.now().year + 1)); + if (pickedDate != null) { + setState(() => publishDate = DateFormat('yyyy-MM-dd').format(pickedDate)); + } + selectedPublishDate = pickedDate; + }, + child: Container( + margin: const EdgeInsets.only(top: 7), + padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 17), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: UiUtils.getColorScheme(context).surface), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomTextLabel( + text: publishDate.isNotEmpty ? publishDate : 'publishDate', + textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(publishDate.isEmpty ? 0.6 : 1))), + Icon(Icons.edit_calendar_rounded, color: UiUtils.getColorScheme(context).primaryContainer) + ], + ), + ), + ), + ); + } + + void onSubmitButton() async { + FocusScope.of(context).unfocus(); + final form = _formkey.currentState; + form!.save(); + + //check for the ones, which are not validated in form above + if (langName.isEmpty) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'chooseLanLbl'), context); + return; + } + if (catSelId == null) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'plzSelCatLbl'), context); + return; + } + if (conType == UiUtils.getTranslatedLabel(context, 'videoUploadLbl')) { + if (!widget.isEdit && videoUpload == null) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'plzUploadVideoLbl'), context); + return; + } + } + if ((conType == UiUtils.getTranslatedLabel(context, 'videoYoutubeLbl') || conType == UiUtils.getTranslatedLabel(context, 'videoOtherUrlLbl')) && urlC.text.contains("/shorts")) { + //do not allow to add link of Youtube shorts as of now + showSnackBar(UiUtils.getTranslatedLabel(context, 'plzValidUrlLbl'), context); + urlC.clear(); + return; + } + //validate other or Youtube URL & set type accordingly + if (conType == UiUtils.getTranslatedLabel(context, 'videoOtherUrlLbl') && (urlC.text.contains("youtube") || urlC.text.contains("youtu.be"))) { + conType = UiUtils.getTranslatedLabel(context, 'videoYoutubeLbl'); + conTypeId = "video_youtube"; + } else if (conType == UiUtils.getTranslatedLabel(context, 'videoYoutubeLbl') && (!urlC.text.contains("youtube") && !urlC.text.contains("youtu.be"))) { + conType = UiUtils.getTranslatedLabel(context, 'videoOtherUrlLbl'); + conTypeId = "video_other"; + } + if (selectedPublishDate != null && selectedShowTillDate != null && selectedShowTillDate!.isBefore(selectedPublishDate!)) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'dateConfirmation'), context); + } + + if (form.validate()) { + if (!widget.isEdit && image == null) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'plzAddMainImageLbl'), context); + return; + } + + //validate slug here + if (!widget.isEdit && context.read().state is! SlugCheckFetchSuccess) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'slugUsedAlready'), context); + return; + } + setState(() => isNext = true); + } + } + + Widget nextBtn() { + return Padding( + padding: const EdgeInsetsDirectional.only(top: 10, bottom: 20, start: 20, end: 20), + child: InkWell( + splashColor: Colors.transparent, + child: Container( + height: 45.0, + width: MediaQuery.of(context).size.width * 0.4, + alignment: Alignment.center, + decoration: BoxDecoration(color: UiUtils.getColorScheme(context).primaryContainer, borderRadius: BorderRadius.circular(7.0)), + child: CustomTextLabel( + text: 'nxt', + textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).surface, fontWeight: FontWeight.w600, fontSize: 18, letterSpacing: 0.6))), + onTap: () => onSubmitButton(), + )); + } + + validateFunc(String description, String summDescription) { + desc = description; + summDesc = summDescription; + validateForm(); + } + + isDraftNews(int isDrafted) { + //set isDraft here + isDraft = isDrafted; + } + + Widget tagListItem(int index, List tagList) { + return UiUtils.setTopPaddingParent( + childWidget: InkWell( + onTap: () { + if (!tagsId.contains(tagList[index].id!)) { + setState(() { + tagsName.add(tagList[index].tagName!); + tagsId.add(tagList[index].id!); + }); + } else { + setState(() { + tagsName.remove(tagList[index].tagName!); + tagsId.remove(tagList[index].id!); + }); + } + Navigator.pop(context); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5.0), + color: tagsId.isNotEmpty + ? tagsId.contains(tagList[index].id!) + ? Theme.of(context).primaryColor + : UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.1) + : UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.1)), + padding: const EdgeInsets.all(10.0), + alignment: Alignment.center, + child: CustomTextLabel( + text: tagList[index].tagName!, + textStyle: Theme.of(context) + .textTheme + .titleMedium! + .copyWith(color: (tagsId.contains(tagList[index].id!)) ? UiUtils.getColorScheme(context).secondary : UiUtils.getColorScheme(context).primaryContainer)), + ), + ), + ); + } + + Widget subCatListItem(int index, List catList) { + return UiUtils.setTopPaddingParent( + childWidget: InkWell( + onTap: () { + setState(() { + subCatSel = catList[catIndex!].subData![index].subCatName!; + subSelId = catList[catIndex!].subData![index].id!; + }); + Navigator.pop(context); + }, + child: + UiUtils.setBottomsheetContainer(entryId: subSelId ?? "0", listItem: catList[catIndex!].subData![index].subCatName!, compareTo: catList[catIndex!].subData![index].id!, context: context)), + ); + } + + void updateDropdownsForLanguage() { + getCategory(languageId: langId); + getTag(languageId: langId); + } + + Widget langListItem(int index, List langList) { + return UiUtils.setTopPaddingParent( + childWidget: InkWell( + onTap: () { + langId = langList[index].id!; + langName = langList[index].language!; + catSel = ""; + catSelId = null; + tagsName.clear(); + tagsId.clear(); + //load categories according to language selected + updateDropdownsForLanguage(); + setState(() {}); + Navigator.pop(context); + }, + child: UiUtils.setBottomsheetContainer(entryId: langId, listItem: langList[index].language!, compareTo: langList[index].id!, context: context), + ), + ); + } + + Widget catListItem(int index, List catList) { + return UiUtils.setTopPaddingParent( + childWidget: InkWell( + onTap: () { + setState(() { + subSelId = null; + subCatSel = ""; + catSel = catList[index].categoryName!; + catSelId = catList[index].id!; + catIndex = index; + }); + Navigator.pop(context); + }, + child: UiUtils.setBottomsheetContainer(entryId: catSelId ?? "0", listItem: catList[index].categoryName!, compareTo: catList[index].id!, context: context), + ), + ); + } + + Widget locationCityListItem(int index, List locationCityList) { + return UiUtils.setTopPaddingParent( + childWidget: InkWell( + onTap: () { + setState(() { + locationSel = locationCityList[index].locationName; + locationSelId = locationCityList[index].id; + locationIndex = index; + }); + Navigator.pop(context); + }, + child: UiUtils.setBottomsheetContainer(entryId: locationSelId ?? "0", listItem: locationCityList[index].locationName, compareTo: locationCityList[index].id, context: context), + ), + ); + } + + @override + Widget build(BuildContext context) { + setContentType(); + return Scaffold( + bottomNavigationBar: !isNext ? nextBtn() : null, + key: _scaffoldKey, + appBar: getAppBar(), + body: BlocConsumer( + bloc: context.read(), + listener: (context, state) async { + if (state is AddNewsFetchFailure) showSnackBar(state.errorMessage, context); + if (state is AddNewsFetchSuccess) { + if (!widget.isEdit) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'newsCreatedSuccessfully'), context, durationInMiliSeconds: 3000); + if (widget.from == "myNews") { + FocusScope.of(context).unfocus(); + clearText(); + Navigator.of(context).pop(); + } else { + Navigator.of(context).pushReplacementNamed(Routes.manageUserNews).whenComplete(() { + FocusScope.of(context).unfocus(); + clearText(); + }); + } + } else { + Future.delayed(Duration.zero, () { + context + .read() + .getGetUserNews(latitude: SettingsLocalDataRepository().getLocationCityValues().first, longitude: SettingsLocalDataRepository().getLocationCityValues().last); + }).then((value) { + context.read().getGetUserDraftedNews(); + showSnackBar(state.addNews["message"], context); + Navigator.of(context).pop(); + }); + } + } + }, + builder: (context, state) { + return Form( + key: _formkey, + child: Stack(children: [ + if (state is AddNewsFetchInProgress) Center(child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)), + !isNext + ? PopScope( + canPop: (!isNext) ? true : false, + onPopInvoked: (bool isTrue) { + setState(() => isNext = false); + }, + child: SingleChildScrollView( + padding: const EdgeInsetsDirectional.only(start: 20, end: 20, bottom: 20), + physics: const AlwaysScrollableScrollPhysics(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + newsTitleName(), + languageSelName(), + catSelectionName(), + subCatSelectionName(), + contentTypeSelName(), + if (widget.isEdit) contentUrlForVideoUpload(), + contentVideoUpload(), + contentUrlSet(), + tagSelectionName(), + locationCitySelectionName(), + publishDateSelection(), + showTillSelDate(), + uploadMainImage(), + uploadOtherImage(), + if (widget.isEdit) modelOtherImage(), + webNewsDetails() + ], + )), + ) + : NewsDescription(desc ?? "", summDesc ?? "", updateParent, validateFunc, (widget.isEdit) ? 2 : 1, isDraftNews) + ])); + }), + ); + } + + Widget customTextFormFieldWithWarningLabel({required TextEditingController controller, required String hintText, required String warningText, required int maxLines}) { + return Column( + children: [ + Container( + width: double.maxFinite, + margin: const EdgeInsetsDirectional.symmetric(vertical: 7), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: UiUtils.getColorScheme(context).surface), + child: TextFormField( + textInputAction: (maxLines > 1) ? TextInputAction.newline : TextInputAction.next, + maxLines: maxLines, + controller: controller, + style: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer), + validator: (val) { + switch (hintText) { + case 'slugLbl': + return Validators.slugValidation(val!, context); + } + return null; + }, + onChanged: (String value) => setState(() { + switch (hintText) { + case 'metaTitleLbl': + metaTitle = value; + break; + case 'metaDescriptionLbl': + metaDescription = value; + break; + case 'metaKeywordLbl': + metaKeyword = value; + break; + case 'slugLbl': + slugC.text = slugC.text.replaceAll(' ', '-'); + slug = slugC.text; + //call APi to check Availability of Entered slug + if (slugC.text.trim().isNotEmpty) { + context.read().checkSlugAvailability(slug: slugC.text, langId: context.read().state.id); + } + break; + } + }), + onTapOutside: (val) => FocusScope.of(context).unfocus(), + decoration: InputDecoration( + hintText: UiUtils.getTranslatedLabel(context, hintText), + hintStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6)), + filled: true, + fillColor: UiUtils.getColorScheme(context).surface, + contentPadding: const EdgeInsets.symmetric(horizontal: 25, vertical: 17), + focusedBorder: OutlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(10.0)), + enabledBorder: OutlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(10.0))))), + ((!isMetaTitle && hintText == 'metaTitleLbl') || + (!isMetaDescription && hintText == 'metaDescriptionLbl') || + (!isMetaKeyword && hintText == 'metaKeywordLbl') || + (!isSlug && hintText == 'slugLbl')) + ? Container( + padding: const EdgeInsets.symmetric(vertical: 5), + decoration: BoxDecoration(border: Border.all(color: warningColor), borderRadius: BorderRadius.circular(10)), + child: Row( + children: [ + const Padding(padding: EdgeInsets.all(3.0), child: Icon(Icons.info_rounded, color: warningColor)), + Expanded(child: CustomTextLabel(text: warningText, textAlign: TextAlign.left)), + IconButton( + padding: EdgeInsets.zero, + onPressed: (() { + switch (hintText) { + case 'metaTitleLbl': + setState(() => isMetaTitle = true); + break; + case 'metaDescriptionLbl': + setState(() => isMetaDescription = true); + break; + case 'metaKeywordLbl': + setState(() => isMetaKeyword = true); + break; + case 'slugLbl': + setState(() => isSlug = true); + break; + } + }), + icon: const Icon(Icons.close_rounded, color: borderColor, size: 18)) + ], + ), + ) + : const SizedBox.shrink() + ], + ); + } + + Widget webNewsDetails() { + return Container( + margin: const EdgeInsets.symmetric(vertical: 10), + child: Column(children: [ + if (!widget.isEdit) autoGenerateMetaInfoButton(), + customTextFormFieldWithWarningLabel(controller: metaTitleC, hintText: 'metaTitleLbl', warningText: 'metaTitleWarningLbl', maxLines: 1), + customTextFormFieldWithWarningLabel(controller: metaDescriptionC, hintText: 'metaDescriptionLbl', warningText: 'metaDescriptionWarningLbl', maxLines: 2), + customTextFormFieldWithWarningLabel(controller: metaKeywordC, hintText: 'metaKeywordLbl', warningText: 'metaKeywordWarningLbl', maxLines: 1), + customTextFormFieldWithWarningLabel(controller: slugC, hintText: 'slugLbl', warningText: 'slugWarningLbl', maxLines: 1) + ])); + } + + bool isProcessing = false; + Widget autoGenerateMetaInfoButton() { + return Padding( + padding: EdgeInsets.symmetric(vertical: 5), + child: (isProcessing) + ? Container( + height: MediaQuery.of(context).size.height * 0.04, + width: MediaQuery.of(context).size.width * 0.48, + child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor), + ) + : CustomTextButton( + onTap: () async { + if ((title ?? '').isEmpty) return showSnackBar(UiUtils.getTranslatedLabel(context, "newsTitleReqLbl"), context); + + if (langId.isEmpty) return showSnackBar(UiUtils.getTranslatedLabel(context, "chooseLanLbl"), context); + setState(() { + isProcessing = true; + }); + try { + final meta = await GeminiService.generateMetaInfo(title: title ?? "", language: langName, languageCode: langId, apiKey: context.read().getGeminiAPiKey()); + + metaTitle = metaTitleC.text = meta["meta_title"]; + metaDescription = metaDescriptionC.text = meta["meta_description"]; + metaKeyword = metaKeywordC.text = meta["meta_keywords"]; + slug = slugC.text = meta["slug"]; + + setState(() { + isProcessing = false; + }); + showSnackBar(UiUtils.getTranslatedLabel(context, "metaInformationApplied"), context); + } catch (e) { + setState(() { + isProcessing = false; + }); + showSnackBar(UiUtils.getTranslatedLabel(context, "failedToGenerateMetaInfo"), context); + } + }, + textWidget: Container( + height: MediaQuery.of(context).size.height * 0.04, + width: MediaQuery.of(context).size.width * 0.48, + decoration: BoxDecoration(color: UiUtils.getColorScheme(context).primaryContainer, borderRadius: BorderRadius.circular(7.0)), + child: Center( + child: CustomTextLabel(text: UiUtils.getTranslatedLabel(context, 'autoGenerateMetaInfo'), textStyle: TextStyle(color: UiUtils.getColorScheme(context).secondary)), + ), + ), + buttonStyle: ButtonStyle(overlayColor: WidgetStateProperty.all(Colors.transparent), foregroundColor: WidgetStateProperty.all(UiUtils.getColorScheme(context).onPrimary))), + ); + } + + updateParent(String description, String summDescription, bool next) { + setState(() { + desc = description; + summDescription = summDescription; + isNext = next; + }); + } + + validateForm() async { + if (await InternetConnectivity.isNetworkAvailable()) { + context.read().addNews( + context: context, + newsId: (widget.isEdit) ? widget.model!.id! : null, + actionType: (widget.isEdit) ? "2" : "1", + catId: catSelId!, + title: title!, + conTypeId: conTypeId, + conType: conType, + image: image, + langId: langId, + subCatId: subSelId, + showTill: showTill, + desc: desc, + otherImage: otherImage, + tagId: tagsId.isNotEmpty ? tagsId.join(',') : null, + url: urlC.text.isNotEmpty ? urlC.text : null, + videoUpload: videoUpload, + locationId: locationSelId, + metaTitle: metaTitle ?? "", + metaDescription: metaDescription ?? "", + metaKeyword: metaKeyword ?? "", + slug: slug!, + publishDate: publishDate.isEmpty ? null : publishDate, + summDescription: summDesc, + isDraft: isDraft ?? 0); + getCategory(); //get default language categories again for Categories Tab + } else { + showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context); + } + } +} diff --git a/news-app/lib/ui/screens/AddEditNews/ManageUserNews.dart b/news-app/lib/ui/screens/AddEditNews/ManageUserNews.dart new file mode 100644 index 00000000..2721926f --- /dev/null +++ b/news-app/lib/ui/screens/AddEditNews/ManageUserNews.dart @@ -0,0 +1,164 @@ +import 'package:flutter/material.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/cubits/GetUserDraftedNewsCubit.dart'; +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; +import 'package:news/ui/screens/AddEditNews/Widgets/userAllNews.dart'; +import 'package:news/ui/screens/AddEditNews/Widgets/userDrafterNews.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:shimmer/shimmer.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/getUserNewsCubit.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/utils/uiUtils.dart'; + +class ManageUserNews extends StatefulWidget { + const ManageUserNews({super.key}); + + @override + ManageUserNewsState createState() => ManageUserNewsState(); +} + +class ManageUserNewsState extends State with TickerProviderStateMixin { + final bool _isButtonExtended = true; + late final ScrollController controller = ScrollController()..addListener(hasMoreNewsScrollListener); + late final ScrollController draftController = ScrollController()..addListener(hasMoreDraftedNewsScrollListener); + + Set get locationValue => SettingsLocalDataRepository().getLocationCityValues(); + + @override + void initState() { + getNews(); + getUserDraftedNews(); + super.initState(); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + void getUserDraftedNews() { + context.read().getGetUserDraftedNews(userId: int.parse(context.read().getUserId())); + } + + void getNews() { + context.read().getGetUserNews(latitude: locationValue.first, longitude: locationValue.last); + } + + void getMoreNews() { + context.read().getMoreGetUserNews(latitude: locationValue.first, longitude: locationValue.last); + } + + void hasMoreNewsScrollListener() { + if (controller.position.maxScrollExtent == controller.offset) { + if (context.read().hasMoreGetUserNews()) { + getMoreNews(); + } else {} + } + } + + void hasMoreDraftedNewsScrollListener() { + if (draftController.position.maxScrollExtent == draftController.offset) { + if (context.read().hasMoreGetUserDraftedNews()) { + getUserDraftedNews(); + } else {} + } + } + + getAppBar() { + return PreferredSize( + preferredSize: const Size(double.infinity, 45), + child: UiUtils.applyBoxShadow( + context: context, + child: AppBar( + centerTitle: false, + backgroundColor: Colors.transparent, + title: Transform( + transform: Matrix4.translationValues(-20.0, 0.0, 0.0), + child: CustomTextLabel( + text: 'manageNewsLbl', + textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600, letterSpacing: 0.5))), + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + child: Icon(Icons.arrow_back, color: UiUtils.getColorScheme(context).primaryContainer)), + ), + ), + )); + } + + newsAddBtn() { + return Column(mainAxisAlignment: MainAxisAlignment.end, children: [ + FloatingActionButton( + isExtended: _isButtonExtended, + backgroundColor: UiUtils.getColorScheme(context).surface, + child: Icon(Icons.add, size: 32, color: UiUtils.getColorScheme(context).primaryContainer), + onPressed: () { + Navigator.of(context).pushNamed(Routes.addNews, arguments: {"isEdit": false, "from": "myNews"}); + }), + const SizedBox(height: 10) + ]); + } + + contentShimmer(BuildContext context) { + return Shimmer.fromColors( + baseColor: Colors.grey.withOpacity(0.6), + highlightColor: Colors.grey, + child: ListView.builder( + shrinkWrap: true, + physics: const AlwaysScrollableScrollPhysics(), + padding: const EdgeInsetsDirectional.only(start: 20, end: 20), + itemBuilder: (_, i) => + Container(decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: Colors.grey.withOpacity(0.6)), margin: const EdgeInsets.only(top: 20), height: 190.0), + itemCount: 6)); + } + + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: 2, + child: Scaffold( + appBar: getAppBar(), + floatingActionButton: newsAddBtn(), + body: Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Column( + children: [ + Container( + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: UiUtils.getColorScheme(context).surface), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + margin: const EdgeInsetsDirectional.only(start: 10, end: 10), + child: TabBar( + indicator: BoxDecoration( + color: UiUtils.getColorScheme(context).primaryContainer, + borderRadius: BorderRadius.circular(10), + ), + labelColor: UiUtils.getColorScheme(context).surface, + unselectedLabelColor: UiUtils.getColorScheme(context).primaryContainer, + tabs: [ + Tab(text: UiUtils.getTranslatedLabel(context, 'manageNewsAllLbl')), + Tab(text: UiUtils.getTranslatedLabel(context, 'manageNewsDraftLbl')), + ], + ), + ), + Expanded( + child: TabBarView( + children: [ + UserAllNewsTab(controller: controller, contentShimmer: contentShimmer(context), fetchNews: getNews, fetchMoreNews: getMoreNews), + UserDrafterNewsTab(controller: draftController, contentShimmer: contentShimmer(context), fetchDraftedNews: getUserDraftedNews, fetchMoreDraftedNews: getUserDraftedNews), + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/news-app/lib/ui/screens/AddEditNews/NewsDescription.dart b/news-app/lib/ui/screens/AddEditNews/NewsDescription.dart new file mode 100644 index 00000000..cd198313 --- /dev/null +++ b/news-app/lib/ui/screens/AddEditNews/NewsDescription.dart @@ -0,0 +1,302 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:html_editor_plus/html_editor.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; +import 'package:news/ui/screens/AddEditNews/Widgets/geminiService.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:shimmer/shimmer.dart'; +import 'package:news/ui/styles/colors.dart'; + +class NewsDescription extends StatefulWidget { + String? description; + Function changeDesc; + Function? validateDesc; + int? from; + String? summDescription; + Function? isDraftNews; + + NewsDescription(this.description, this.summDescription, this.changeDesc, this.validateDesc, this.from, this.isDraftNews, {super.key}); + + @override + NewsDescriptionState createState() => NewsDescriptionState(); +} + +class NewsDescriptionState extends State { + String result = ''; + bool isLoading = true; + bool isSubmitted = false; + + final HtmlEditorController controller = HtmlEditorController(); + + final TextEditingController summaryController = TextEditingController(); + + @override + void initState() { + setValue(); + super.initState(); + } + + setValue() async { + Future.delayed( + const Duration(seconds: 4), + () { + setState(() { + isLoading = false; + }); + }, + ); + } + + getAppBar() { + return PreferredSize( + preferredSize: const Size(double.infinity, 45), + child: UiUtils.applyBoxShadow( + context: context, + child: AppBar( + centerTitle: false, + backgroundColor: Colors.transparent, + title: Transform( + transform: Matrix4.translationValues(-20.0, 0.0, 0.0), + child: CustomTextLabel( + text: widget.from == 2 ? 'editNewsLbl' : 'createNewsLbl', + textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600, letterSpacing: 0.5), + ), + ), + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: InkWell( + onTap: () { + controller.getText().then((value) { + widget.changeDesc(value, summaryController.text, false); + }); + }, + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + child: Icon(Icons.arrow_back, color: UiUtils.getColorScheme(context).primaryContainer), + ), + ), + actions: [ + Container( + padding: const EdgeInsetsDirectional.only(end: 20), + alignment: Alignment.center, + child: CustomTextLabel(text: 'step2of2Lbl', textStyle: Theme.of(context).textTheme.bodySmall!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6))), + ) + ], + ), + )); + } + + Widget nextBtn() { + return (isSubmitted) + ? UiUtils.showCircularProgress(true, Theme.of(context).primaryColor) + : Padding( + padding: const EdgeInsetsDirectional.all(10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: InkWell( + onTap: () => validateTextAndSubmit(isDraft: 1), + splashColor: Colors.transparent, + child: Container( + height: 45.0, + width: MediaQuery.of(context).size.width * 0.4, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(7.0), + border: Border.all(color: UiUtils.getColorScheme(context).primaryContainer), + ), + child: CustomTextLabel( + text: 'saveAsDraftLbl', + textStyle: Theme.of(context) + .textTheme + .titleLarge + ?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600, fontSize: 18, letterSpacing: 0.6))), + )), + SizedBox(width: 10), + Expanded( + child: InkWell( + splashColor: Colors.transparent, + child: Container( + height: 45.0, + width: MediaQuery.of(context).size.width * 0.4, + alignment: Alignment.center, + decoration: BoxDecoration(color: UiUtils.getColorScheme(context).primaryContainer, borderRadius: BorderRadius.circular(7.0)), + child: CustomTextLabel( + text: 'publishBtnLbl', + textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).surface, fontWeight: FontWeight.w600, fontSize: 18, letterSpacing: 0.6), + ), + ), + onTap: () => validateTextAndSubmit(isDraft: 0), + ), + ), + ], + ), + ); + } + + void validateTextAndSubmit({required int isDraft}) { + widget.isDraftNews!(isDraft); + controller.getText().then((value) { + isSubmitted = true; + widget.validateDesc!(value, summaryController.text); + }); + } + + Widget shimmer() { + return SizedBox( + width: double.infinity, + child: Shimmer.fromColors( + baseColor: Colors.grey[300]!, + highlightColor: Colors.grey[100]!, + child: Container( + height: MediaQuery.of(context).size.height * 0.741, + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: Theme.of(context).cardColor), + )), + ); + } + + @override + Widget build(BuildContext context) { + if (widget.description != null && widget.description != "") controller.setText(widget.description!); //incase of Edit + return Scaffold( + appBar: getAppBar(), + bottomNavigationBar: nextBtn(), + body: PopScope( + canPop: false, + onPopInvoked: (bool isTrue) { + controller.getText().then((value) { + widget.changeDesc(value, summaryController.text, false); + }); + }, + child: GestureDetector( + onTap: () { + if (!kIsWeb) { + FocusScope.of(context).unfocus(); //dismiss keyboard + } + }, + child: Padding( + padding: const EdgeInsetsDirectional.all(20), + child: isLoading + ? shimmer() + : SingleChildScrollView( + child: Column( + children: [ + SizedBox( + height: MediaQuery.of(context).size.height * 0.45, + child: Theme( + data: Theme.of(context).copyWith(textTheme: TextTheme(titleSmall: Theme.of(context).textTheme.titleMedium!.copyWith(color: Colors.orange))), + child: HtmlEditor( + controller: controller, + htmlEditorOptions: HtmlEditorOptions( + hint: UiUtils.getTranslatedLabel(context, 'descLbl'), + adjustHeightForKeyboard: true, + autoAdjustHeight: true, + shouldEnsureVisible: true, + spellCheck: true, + disabled: false), + htmlToolbarOptions: HtmlToolbarOptions( + toolbarPosition: ToolbarPosition.aboveEditor, + toolbarType: ToolbarType.nativeExpandable, + gridViewHorizontalSpacing: 0, + gridViewVerticalSpacing: 0, + toolbarItemHeight: 30, + buttonColor: UiUtils.getColorScheme(context).primaryContainer, + buttonFocusColor: Theme.of(context).primaryColor, + buttonBorderColor: Colors.red, + buttonFillColor: secondaryColor, + dropdownIconColor: Theme.of(context).primaryColor, + dropdownIconSize: 26, + textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer), + onButtonPressed: (ButtonType type, bool? status, Function? updateStatus) { + return true; + }, + onDropdownChanged: (DropdownType type, dynamic changed, Function(dynamic)? updateSelectedItem) { + return true; + }, + mediaLinkInsertInterceptor: (String url, InsertFileType type) { + return true; + }, + mediaUploadInterceptor: (PlatformFile file, InsertFileType type) async { + return true; + }, + ), + otherOptions: OtherOptions( + height: MediaQuery.of(context).size.height * 0.725, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: UiUtils.getColorScheme(context).surface, + ), + ), + callbacks: Callbacks( + onChangeCodeview: (String? changed) { + result = changed!; + }, + onImageUploadError: ( + FileUpload? file, + String? base64Str, + UploadError error, + ) {}, + onNavigationRequestMobile: (String url) { + return NavigationActionPolicy.ALLOW; + }, + ), + ), + ), + ), + summarySection() + ], + ), + ), + )), + ), + ); + } + + Widget summarySection() { + if (widget.summDescription != null && widget.summDescription!.isNotEmpty) summaryController.text = widget.summDescription!; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 20), + CustomTextLabel(text: UiUtils.getTranslatedLabel(context, 'summarizedDescription'), textStyle: Theme.of(context).textTheme.titleMedium), + const SizedBox(height: 10), + TextField( + controller: summaryController, + maxLines: 3, + readOnly: true, + decoration: InputDecoration( + hintText: UiUtils.getTranslatedLabel(context, 'clickSummarizeDescription'), + border: OutlineInputBorder(borderRadius: BorderRadius.circular(10)), + filled: true, + contentPadding: EdgeInsets.all(5), + fillColor: UiUtils.getColorScheme(context).surface)), + const SizedBox(height: 10), + Center( + child: ElevatedButton( + onPressed: () async { + String fullText = await controller.getText(); + if (fullText.trim().isEmpty) return showSnackBar('enterDescriptionFirst', context); + String summary = await GeminiService.summarizeDescription(fullText, context.read().getGeminiAPiKey()); + setState(() { + summaryController.text = summary; + }); + }, + style: ElevatedButton.styleFrom( + backgroundColor: UiUtils.getColorScheme(context).primaryContainer, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(7)), + ), + child: Text( + UiUtils.getTranslatedLabel(context, 'summarizedDescription'), + style: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).surface, fontWeight: FontWeight.w600), + ), + ), + ) + ], + ); + } +} diff --git a/news-app/lib/ui/screens/AddEditNews/Widgets/UserNewsWidgets.dart b/news-app/lib/ui/screens/AddEditNews/Widgets/UserNewsWidgets.dart new file mode 100644 index 00000000..1d96300f --- /dev/null +++ b/news-app/lib/ui/screens/AddEditNews/Widgets/UserNewsWidgets.dart @@ -0,0 +1,251 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hive/hive.dart'; +import 'package:intl/intl.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/deleteUserNewsCubit.dart'; +import 'package:news/cubits/getUserNewsCubit.dart'; +import 'package:news/cubits/themeCubit.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/ui/styles/appTheme.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; +import 'package:news/ui/widgets/customTextBtn.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart'; +import 'package:news/utils/hiveBoxKeys.dart'; +import 'package:news/utils/uiUtils.dart'; + +class UsernewsWidgets { + static final labelKeys = {"standard_post": 'stdPostLbl', "video_youtube": 'videoYoutubeLbl', "video_other": 'videoOtherUrlLbl', "video_upload": 'videoUploadLbl'}; + + static buildNewsContainer( + {required BuildContext context, + required NewsModel model, + required int index, + required int totalCurrentNews, + required bool hasMoreNewsFetchError, + required bool hasMore, + required Function fetchMoreNews}) { + if (index == totalCurrentNews - 1 && index != 0) { + if (hasMore) { + if (hasMoreNewsFetchError) { + return Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0), + child: IconButton(onPressed: () => fetchMoreNews, icon: Icon(Icons.error, color: Theme.of(context).primaryColor)), + )); + } else { + return Center(child: Padding(padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0), child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor))); + } + } + } + + return InkWell( + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + onTap: () { + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": model, "isFromBreak": false, "fromShowMore": false}); + }, + child: Container( + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: UiUtils.getColorScheme(context).surface), + padding: const EdgeInsetsDirectional.all(15), + margin: const EdgeInsets.only(top: 20), + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.24, + height: MediaQuery.of(context).size.height * 0.26, + child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + newsImage(imageURL: model.image!, context: context), + Padding( + padding: const EdgeInsets.only(left: 10.0), + child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ + categoryName(context: context, categoryName: (model.categoryName != null && model.categoryName!.trim().isNotEmpty) ? model.categoryName! : ""), + setDate(context: context, dateValue: model.date!) + ]), + ), + Spacer(), + deleteAndEditButton(context: context, isEdit: true, onTap: () => Navigator.of(context).pushNamed(Routes.addNews, arguments: {"model": model, "isEdit": true, "from": "myNews"})), + deleteAndEditButton(context: context, isEdit: false, onTap: () => deleteNewsDialogue(context, model.id!, index)) + ], + ), + Divider(thickness: 2), + Expanded( + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + CustomTextLabel( + text: model.title!, + maxLines: 3, + overflow: TextOverflow.ellipsis, + softWrap: true, + textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w700)), + contentTypeView(context: context, model: model), + ])), + Divider(thickness: 2), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + (model.isExpired == 1) + ? Container( + child: Row( + children: [ + SvgPictureWidget( + assetName: 'expiredNews', + assetColor: + (context.read().state.appTheme == AppTheme.Dark ? ColorFilter.mode(darkIconColor, BlendMode.srcIn) : ColorFilter.mode(iconColor, BlendMode.srcIn))), + SizedBox(width: 2.5), + Text( + UiUtils.getTranslatedLabel(context, 'expiredKey'), + style: TextStyle(color: (context.read().state.appTheme == AppTheme.Dark ? darkIconColor : iconColor), fontWeight: FontWeight.w500), + ), + ], + )) + : SizedBox.shrink(), + (model.status == "0") + ? Container( + padding: EdgeInsets.symmetric(vertical: 3, horizontal: 5), + child: Tooltip( + margin: const EdgeInsets.symmetric(horizontal: 20), + decoration: BoxDecoration(color: UiUtils.getColorScheme(context).primaryContainer, borderRadius: BorderRadius.circular(10)), + textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: UiUtils.getColorScheme(context).secondary, fontSize: 10), + message: UiUtils.getTranslatedLabel(context, 'newsCreatedSuccessfully'), + child: Row( + children: [ + SvgPictureWidget( + assetName: 'deactivatedNews', + assetColor: (context.read().state.appTheme == AppTheme.Dark + ? const ColorFilter.mode(darkIconColor, BlendMode.srcIn) + : const ColorFilter.mode(iconColor, BlendMode.srcIn))), + const SizedBox(width: 2.5), + Text( + UiUtils.getTranslatedLabel(context, 'deactivatedKey'), + style: TextStyle(color: (context.read().state.appTheme == AppTheme.Dark ? darkIconColor : iconColor), fontWeight: FontWeight.bold), + ), + ], + ), + )) + : SizedBox.shrink(), + ], + ), + ]), + ))); + } + + static Widget newsImage({required BuildContext context, required String imageURL}) { + return ClipRRect( + borderRadius: BorderRadius.circular(45), + child: CustomNetworkImage(networkImageUrl: imageURL, fit: BoxFit.cover, height: MediaQuery.of(context).size.width * 0.18, isVideo: false, width: MediaQuery.of(context).size.width * 0.18)); + } + + static Widget categoryName({required BuildContext context, required String categoryName}) { + return (categoryName.trim().isNotEmpty) + ? Padding( + padding: const EdgeInsets.only(top: 4), + child: CustomTextLabel( + text: categoryName, + overflow: TextOverflow.ellipsis, + softWrap: true, + textStyle: Theme.of(context).textTheme.bodyLarge!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.9), fontSize: 16, fontWeight: FontWeight.w600)), + ) + : const SizedBox.shrink(); + } + + static Widget deleteAndEditButton({required BuildContext context, required bool isEdit, required void Function()? onTap}) { + return InkWell( + onTap: onTap, + child: Container( + padding: const EdgeInsetsDirectional.only(top: 3, bottom: 3, start: 5), + alignment: Alignment.center, + child: SvgPictureWidget( + assetName: (isEdit) ? 'editMyNews' : 'deleteMyNews', + height: 30, + width: 30, + fit: BoxFit.contain, + assetColor: (isEdit) ? ColorFilter.mode(UiUtils.getColorScheme(context).onPrimary, BlendMode.srcIn) : null), + )); + } + + static Widget setDate({required BuildContext context, required String dateValue}) { + DateTime time = DateTime.parse(dateValue); + var newFormat = DateFormat("dd-MMM-yyyy", Hive.box(settingsBoxKey).get(currentLanguageCodeKey)); + final newNewsDate = newFormat.format(time); + + return CustomTextLabel( + text: newNewsDate, + overflow: TextOverflow.ellipsis, + softWrap: true, + textStyle: Theme.of(context).textTheme.bodySmall!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8))); + } + + static deleteNewsDialogue(BuildContext mainContext, String id, int index) async { + await showDialog( + context: mainContext, + builder: (BuildContext context) { + return StatefulBuilder(builder: (BuildContext context, StateSetter setStater) { + return AlertDialog( + backgroundColor: UiUtils.getColorScheme(context).surface, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5.0))), + content: CustomTextLabel(text: 'doYouReallyNewsLbl', textStyle: Theme.of(context).textTheme.titleMedium), + title: const CustomTextLabel(text: 'delNewsLbl'), + titleTextStyle: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w600), + actions: [ + CustomTextButton( + textWidget: CustomTextLabel( + text: 'noLbl', textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.bold)), + onTap: () { + Navigator.of(context).pop(false); + }), + BlocConsumer( + bloc: context.read(), + listener: (context, state) { + if (state is DeleteUserNewsSuccess) { + context.read().deleteNews(index); + showSnackBar(state.message, context); + Navigator.pop(context); + } + }, + builder: (context, state) { + return CustomTextButton( + textWidget: CustomTextLabel( + text: 'yesLbl', textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.bold)), + onTap: () async { + context.read().setDeleteUserNews(newsId: id); + }); + }) + ], + ); + }); + }); + } + + static Widget contentTypeView({required BuildContext context, required NewsModel model}) { + String contType = ""; + + final key = labelKeys[model.contentType]; + if (key != null) { + contType = UiUtils.getTranslatedLabel(context, key); + } + return (model.contentType != "") + ? Padding( + padding: const EdgeInsets.only(top: 7), + child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ + CustomTextLabel( + text: 'contentTypeLbl', + maxLines: 1, + overflow: TextOverflow.ellipsis, + softWrap: true, + textStyle: Theme.of(context).textTheme.bodyLarge!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withAlpha((0.3 * 255).round()))), + CustomTextLabel(text: " : ", textStyle: Theme.of(context).textTheme.bodyLarge!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withAlpha((0.3 * 255).round()))), + CustomTextLabel( + text: contType, + maxLines: 1, + overflow: TextOverflow.ellipsis, + softWrap: true, + textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withAlpha((0.3 * 255).round()))) + ]), + ) + : SizedBox.shrink(); + } +} diff --git a/news-app/lib/ui/screens/AddEditNews/Widgets/customBottomsheet.dart b/news-app/lib/ui/screens/AddEditNews/Widgets/customBottomsheet.dart new file mode 100644 index 00000000..1da60606 --- /dev/null +++ b/news-app/lib/ui/screens/AddEditNews/Widgets/customBottomsheet.dart @@ -0,0 +1,157 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/categoryCubit.dart'; +import 'package:news/cubits/locationCityCubit.dart'; +import 'package:news/cubits/updateBottomsheetContentCubit.dart'; +import 'package:news/cubits/tagCubit.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; + +class CustomBottomsheet extends StatefulWidget { + final BuildContext context; + final String titleTxt; + final int listLength; + final String language_id; + final NullableIndexedWidgetBuilder listViewChild; + + const CustomBottomsheet({super.key, required this.context, required this.titleTxt, required this.listLength, required this.listViewChild, required this.language_id}); + + @override + CustomBottomsheetState createState() => CustomBottomsheetState(); +} + +class CustomBottomsheetState extends State { + late final ScrollController locationScrollController = ScrollController(); + late final ScrollController languageScrollController = ScrollController(); + late final ScrollController categoryScrollController = ScrollController(); + late final ScrollController subcategoryScrollController = ScrollController(); + late final ScrollController tagScrollController = ScrollController(); + + ScrollController scController = ScrollController(); + @override + void initState() { + super.initState(); + + initScrollController(); + } + + @override + void dispose() { + disposeScrollController(); + super.dispose(); + } + + void initScrollController() { + switch (widget.titleTxt) { + case 'chooseLanLbl': + scController = languageScrollController; + break; + case 'selCatLbl': + scController = categoryScrollController; + break; + case 'selSubCatLbl': + scController = subcategoryScrollController; + break; + case 'selTagLbl': + scController = tagScrollController; + break; + case 'selLocationLbl': + scController = locationScrollController; + break; + } + scController.addListener(() => hasMoreLocationScrollListener()); + } + + disposeScrollController() { + switch (widget.titleTxt) { + case 'chooseLanLbl': + languageScrollController.dispose(); + break; + case 'selCatLbl': + categoryScrollController.dispose(); + break; + case 'selSubCatLbl': + subcategoryScrollController.dispose(); + break; + case 'selTagLbl': + tagScrollController.dispose(); + break; + case 'selLocationLbl': + locationScrollController.dispose(); + break; + } + } + + void hasMoreLocationScrollListener() { + if (scController.offset >= scController.position.maxScrollExtent && !scController.position.outOfRange) { + switch (widget.titleTxt) { + case 'selCatLbl': + if (context.read().hasMoreCategory()) { + context.read().getMoreCategory(langId: widget.language_id); + } + break; + case 'selTagLbl': + if (context.read().hasMoreTags()) { + context.read().getMoreTags(langId: widget.language_id); + } + break; + case 'selLocationLbl': + if (context.read().hasMoreLocation()) { + context.read().getMoreLocationCity(); + } + break; + } + } + } + + @override + Widget build(BuildContext context) { + return Builder( + builder: (BuildContext context) => BlocBuilder( + builder: (context, state) { + int listLength = widget.listLength; + switch (widget.titleTxt) { + case 'selLocationLbl': + listLength = state.locationData.length; + break; + case 'selTagLbl': + listLength = state.tagsData.length; + break; + case 'chooseLanLbl': + listLength = state.languageData.length; + break; + case 'selCatLbl': + listLength = state.categoryData.length; + } + return DraggableScrollableSheet( + snap: true, + snapSizes: const [0.5, 0.9], + expand: false, + builder: (_, controller) { + controller = scController; + return Container( + padding: const EdgeInsetsDirectional.only(bottom: 15.0, top: 15.0, start: 20.0, end: 20.0), + decoration: BoxDecoration(borderRadius: const BorderRadius.only(topLeft: Radius.circular(30), topRight: Radius.circular(30)), color: UiUtils.getColorScheme(context).surface), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + CustomTextLabel( + text: widget.titleTxt, + textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold, color: UiUtils.getColorScheme(context).primaryContainer)), + const SizedBox(height: 10), + Expanded( + child: ListView.builder( + controller: controller, + physics: const AlwaysScrollableScrollPhysics(), + shrinkWrap: true, + padding: const EdgeInsetsDirectional.only(top: 10.0, bottom: 25.0), + itemCount: listLength, + itemBuilder: widget.listViewChild)), + ], + )); + }); + }, + )); + } +} diff --git a/news-app/lib/ui/screens/AddEditNews/Widgets/geminiService.dart b/news-app/lib/ui/screens/AddEditNews/Widgets/geminiService.dart new file mode 100644 index 00000000..7d160551 --- /dev/null +++ b/news-app/lib/ui/screens/AddEditNews/Widgets/geminiService.dart @@ -0,0 +1,149 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:news/utils/api.dart'; + +/// AI Service Module using Google Gemini API +/// This module contains all AI-related API calls for the application + +class GeminiService { + static const String _geminiModel = "gemini-2.0-flash"; // Or gemini-1.5-pro + + /// Helper function to call Gemini API + static Future> _callGeminiAPI(String prompt, String apiKey) async { + try { + final requestBody = { + "contents": [ + { + "parts": [ + {"text": prompt} + ] + } + ] + }; + + final url = Uri.parse("${Api.geminiMetaInfoApi}$_geminiModel:generateContent?key=$apiKey"); + + final response = await http.post( + url, + headers: {"Content-Type": "application/json"}, + body: jsonEncode(requestBody), + ); + + if (response.statusCode != 200) { + final errorData = jsonDecode(response.body); + throw Exception(errorData["error"]?["message"] ?? "Failed to generate content"); + } + + return jsonDecode(response.body); + } catch (e) { + rethrow; + } + } + + /// Generate content + static Future generateContent({ + String? title, + String? category, + String? language, + String? languageCode, + required String apiKey, + }) async { + try { + String fullPrompt = "You are a skilled news article writer. Create engaging and informative content."; + + if (title != null) { + fullPrompt += "\n\nWrite an article with the title: \"$title\""; + } + + if (category != null) { + fullPrompt += "\nCategory: $category"; + } + + if (language != null && languageCode != null) { + fullPrompt += "\n\nIMPORTANT: Generate all content in $language language ($languageCode). The response MUST be in $language."; + } + + fullPrompt += "\n\nRequest: \n\nArticle:"; + + final response = await _callGeminiAPI(fullPrompt, apiKey); + + return response["candidates"][0]["content"]["parts"][0]["text"]; + } catch (e) { + rethrow; + } + } + + /// Generate meta info + static Future> generateMetaInfo({required String title, String? language, String? languageCode, required String apiKey}) async { + try { + String languageInstruction = ""; + if (language != null && languageCode != null) { + languageInstruction = "\n\nIMPORTANT: Generate all content in $language language ($languageCode). The response MUST be in same language as title."; + } + + final prompt = """ +You are an SEO expert. Generate meta title, description, keywords, and a slug for this news article titled: "$title".$languageInstruction + +Return ONLY a JSON object with these fields: +- meta_title +- meta_description +- meta_keywords +- slug + +Response must be valid JSON. +"""; + + final response = await _callGeminiAPI(prompt, apiKey); + final responseText = response["candidates"][0]["content"]["parts"][0]["text"].trim(); + + try { + return jsonDecode(responseText); + } catch (_) { + final match = RegExp(r"\{[\s\S]*\}").firstMatch(responseText); + if (match != null) { + return jsonDecode(match.group(0)!); + } + return { + "meta_title": title, + "meta_description": "Read about $title in our latest news article.", + "meta_keywords": title.toLowerCase().split(" ").join(","), + "slug": title.toLowerCase().replaceAll(RegExp(r'[^a-z0-9]+'), "-").replaceAll(RegExp(r'^-|-$'), ""), + }; + } + } catch (e) { + rethrow; + } + } + + /// Summarize description + static Future summarizeDescription(String description, String apiKey, {String language = "English", String languageCode = "en"}) async { + try { + if (description.trim().isEmpty) return ""; + + final cleanContent = description.replaceAll(RegExp(r"<[^>]*>"), "").trim(); + if (cleanContent.isEmpty) return ""; + + final prompt = """ +You are a skilled content summarizer. Summarize the following news content: + +Content: "$cleanContent" + +Instructions: +- 200-250 words +- Maintain key facts +- Professional news style +- No explanations, only summary +- IMPORTANT: Generate in $language ($languageCode). + +Summary:"""; + + final response = await _callGeminiAPI(prompt, apiKey); + final summary = response["candidates"][0]["content"]["parts"][0]["text"].trim(); + + String finalSummary = summary.replaceAll(RegExp(r"^['\']+|['\']+$"), '').trim(); + return finalSummary; + } catch (e) { + return description.replaceAll(RegExp(r"<[^>]*>"), "").substring(0, 150) + "..."; + } + } +} diff --git a/news-app/lib/ui/screens/AddEditNews/Widgets/userAllNews.dart b/news-app/lib/ui/screens/AddEditNews/Widgets/userAllNews.dart new file mode 100644 index 00000000..be9a33d0 --- /dev/null +++ b/news-app/lib/ui/screens/AddEditNews/Widgets/userAllNews.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/getUserNewsCubit.dart'; +import 'package:news/ui/screens/AddEditNews/Widgets/UserNewsWidgets.dart'; +import 'package:news/ui/widgets/errorContainerWidget.dart'; +import 'package:news/utils/ErrorMessageKeys.dart'; +import 'package:news/utils/uiUtils.dart'; + +class UserAllNewsTab extends StatelessWidget { + final ScrollController controller; + final Widget contentShimmer; + final Function fetchNews; + final Function fetchMoreNews; + + UserAllNewsTab({super.key, required this.controller, required this.contentShimmer, required this.fetchNews, required this.fetchMoreNews}); + + @override + Widget build(BuildContext context) { + return BlocBuilder(builder: (context, state) { + if (state is GetUserNewsFetchSuccess) { + return RefreshIndicator( + onRefresh: () async => fetchNews, + child: Padding( + padding: const EdgeInsetsDirectional.only(start: 10, end: 10, bottom: 10), + child: ListView.builder( + controller: controller, + physics: const AlwaysScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: state.getUserNews.length, + itemBuilder: (context, index) { + return UsernewsWidgets.buildNewsContainer( + context: context, + model: state.getUserNews[index], + hasMore: state.hasMore, + hasMoreNewsFetchError: state.hasMoreFetchError, + index: index, + totalCurrentNews: state.getUserNews.length, + fetchMoreNews: fetchMoreNews); + }), + ), + ); + } + if (state is GetUserNewsFetchFailure) { + return ErrorContainerWidget(errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage, onRetry: fetchNews); + } + return contentShimmer; + }); + } +} diff --git a/news-app/lib/ui/screens/AddEditNews/Widgets/userDrafterNews.dart b/news-app/lib/ui/screens/AddEditNews/Widgets/userDrafterNews.dart new file mode 100644 index 00000000..c5cfdf34 --- /dev/null +++ b/news-app/lib/ui/screens/AddEditNews/Widgets/userDrafterNews.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/GetUserDraftedNewsCubit.dart'; +import 'package:news/ui/screens/AddEditNews/Widgets/UserNewsWidgets.dart'; +import 'package:news/ui/widgets/errorContainerWidget.dart'; +import 'package:news/utils/ErrorMessageKeys.dart'; +import 'package:news/utils/uiUtils.dart'; + +class UserDrafterNewsTab extends StatelessWidget { + final ScrollController controller; + final Widget contentShimmer; + final Function fetchDraftedNews; + final Function fetchMoreDraftedNews; + + UserDrafterNewsTab({super.key, required this.controller, required this.contentShimmer, required this.fetchDraftedNews, required this.fetchMoreDraftedNews}); + + @override + Widget build(BuildContext context) { + return BlocBuilder(builder: (context, state) { + if (state is GetUserDraftedNewsFetchSuccess) { + return Padding( + padding: const EdgeInsetsDirectional.only(start: 10, end: 10, bottom: 10), + child: RefreshIndicator( + onRefresh: () async => fetchDraftedNews, + child: ListView.builder( + controller: controller, + physics: const AlwaysScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: state.GetUserDraftedNews.length, + itemBuilder: (context, index) { + return UsernewsWidgets.buildNewsContainer( + context: context, + model: state.GetUserDraftedNews[index], + hasMore: state.hasMore, + hasMoreNewsFetchError: state.hasMoreFetchError, + index: index, + totalCurrentNews: state.GetUserDraftedNews.length, + fetchMoreNews: fetchMoreDraftedNews); + }), + ), + ); + } + if (state is GetUserDraftedNewsFetchFailure) { + return ErrorContainerWidget( + errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage, onRetry: fetchDraftedNews); + } + return contentShimmer; + }); + } +} diff --git a/news-app/lib/ui/screens/BookmarkScreen.dart b/news-app/lib/ui/screens/BookmarkScreen.dart new file mode 100644 index 00000000..12ce4e3b --- /dev/null +++ b/news-app/lib/ui/screens/BookmarkScreen.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/Bookmark/bookmarkCubit.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/ui/widgets/customAppBar.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/errorContainerWidget.dart'; +import 'package:news/ui/widgets/newsCard.dart'; +import 'package:news/ui/widgets/shimmerNewsList.dart'; +import 'package:news/utils/ErrorMessageKeys.dart'; +import 'package:news/utils/internetConnectivity.dart'; +import 'package:news/utils/uiUtils.dart'; + +class BookmarkScreen extends StatefulWidget { + const BookmarkScreen({super.key}); + + @override + BookmarkScreenState createState() => BookmarkScreenState(); +} + +class BookmarkScreenState extends State { + late final ScrollController _controller = ScrollController()..addListener(hasMoreBookmarkScrollListener); + + @override + void initState() { + super.initState(); + getBookMark(); + } + + void getBookMark() async { + if (await InternetConnectivity.isNetworkAvailable()) { + context.read().getBookmark(langId: context.read().state.id); + } + } + + void hasMoreBookmarkScrollListener() { + if (_controller.position.maxScrollExtent == _controller.offset) { + if (context.read().hasMoreBookmark()) { + context.read().getMoreBookmark(langId: context.read().state.id); + } + } + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: CustomAppBar(height: 45, isBackBtn: true, label: 'bookmarkLbl', horizontalPad: 15, isConvertText: true), + body: Padding( + padding: const EdgeInsetsDirectional.only(bottom: 10.0), + child: BlocBuilder( + builder: (context, state) { + if (state is BookmarkFetchSuccess && state.bookmark.isNotEmpty) { + return Padding( + padding: const EdgeInsetsDirectional.only(start: 15.0, end: 15.0, top: 10.0, bottom: 10.0), + child: RefreshIndicator( + onRefresh: () async { + getBookMark(); + }, + child: ListView.builder( + controller: _controller, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: state.bookmark.length, + itemBuilder: (context, index) { + return _buildBookmarkContainer(model: state.bookmark[index], hasMore: state.hasMore, hasMoreBookFetchError: state.hasMoreFetchError, index: index, totalCurrentBook: 6); + }), + ), + ); + } else if (state is BookmarkFetchFailure || ((state is! BookmarkFetchInProgress))) { + if (state is BookmarkFetchFailure) { + return ErrorContainerWidget( + errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage, onRetry: getBookMark); + } else { + return const Center(child: CustomTextLabel(text: 'bookmarkNotAvail', textAlign: TextAlign.center)); + } + } + //default/Processing state + return Padding(padding: const EdgeInsets.only(bottom: 10.0, left: 10.0, right: 10.0), child: ShimmerNewsList(isNews: false)); + }, + ))); + } + + _buildBookmarkContainer({required NewsModel model, required int index, required int totalCurrentBook, required bool hasMoreBookFetchError, required bool hasMore}) { + if (index == totalCurrentBook - 1 && index != 0 && hasMore) { + if (hasMoreBookFetchError) { + return Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0), + child: IconButton( + onPressed: () { + context.read().getMoreBookmark(langId: context.read().state.id); + }, + icon: Icon(Icons.error, color: Theme.of(context).primaryColor)))); + } + } + + return Padding( + padding: const EdgeInsetsDirectional.only(top: 15.0), + child: NewsCard( + newsDetail: model, + showViews: true, + onTap: () async { + //Interstitial Ad here + UiUtils.showInterstitialAds(context: context); + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": model, "isFromBreak": false, "fromShowMore": false}); + })); + } +} diff --git a/news-app/lib/ui/screens/CategoryScreen.dart b/news-app/lib/ui/screens/CategoryScreen.dart new file mode 100644 index 00000000..ec826827 --- /dev/null +++ b/news-app/lib/ui/screens/CategoryScreen.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/categoryCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/data/models/CategoryModel.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/errorContainerWidget.dart'; +import 'package:news/ui/widgets/customAppBar.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import 'package:news/utils/ErrorMessageKeys.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/app/routes.dart'; + +class CategoryScreen extends StatefulWidget { + const CategoryScreen({super.key}); + + @override + CategoryScreenState createState() => CategoryScreenState(); +} + +class CategoryScreenState extends State { + late final ScrollController _categoryScrollController = ScrollController()..addListener(hasMoreCategoryScrollListener); + + void getCategory() { + Future.delayed(Duration.zero, () { + context.read().getCategory(langId: context.read().state.id); + }); + } + + @override + void initState() { + getCategory(); + super.initState(); + } + + @override + void dispose() { + _categoryScrollController.dispose(); + super.dispose(); + } + + void hasMoreCategoryScrollListener() { + if (_categoryScrollController.offset >= _categoryScrollController.position.maxScrollExtent && !_categoryScrollController.position.outOfRange) { + if (context.read().hasMoreCategory()) { + context.read().getMoreCategory(langId: context.read().state.id); + } else {} + } + } + + Widget _buildCategory() { + return BlocBuilder( + builder: (context, state) { + if (state is CategoryFetchSuccess) { + return RefreshIndicator( + onRefresh: () async { + getCategory(); + }, + child: GridView.count( + physics: const AlwaysScrollableScrollPhysics(), + scrollDirection: Axis.vertical, + padding: EdgeInsets.only(top: 15, bottom: MediaQuery.of(context).size.height / 10.0, left: 15, right: 15), + crossAxisCount: 2, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + childAspectRatio: 0.92, + shrinkWrap: true, + controller: _categoryScrollController, + children: List.generate(state.category.length, (index) { + return _buildCategoryContainer( + category: state.category[index], hasMore: state.hasMore, hasMoreCategoryFetchError: state.hasMoreFetchError, index: index, totalCurrentCategory: state.category.length); + }), + )); + } + if (state is CategoryFetchFailure) { + return ErrorContainerWidget( + errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage, onRetry: getCategory); + } + return const SizedBox.shrink(); + }, + ); + } + + _buildCategoryContainer({required CategoryModel category, required int index, required int totalCurrentCategory, required bool hasMoreCategoryFetchError, required bool hasMore}) { + if (index == totalCurrentCategory - 1 && index != 0) { + if (hasMore) { + if (hasMoreCategoryFetchError) { + return const SizedBox.shrink(); + } else { + return Center(child: Padding(padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0), child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor))); + } + } + } + return GestureDetector( + onTap: () { + Navigator.of(context).pushNamed(Routes.subCat, arguments: {"catId": category.id, "catName": category.categoryName}); + }, + child: Card( + color: UiUtils.getColorScheme(context).surface, + child: Column( + spacing: 10, + mainAxisSize: MainAxisSize.min, + children: [ + (category.image != null) + ? Padding( + padding: const EdgeInsets.all(7.0), + child: ClipRRect( + borderRadius: BorderRadiusGeometry.circular(3), child: CustomNetworkImage(networkImageUrl: category.image!, height: 100, width: 140, isVideo: false, fit: BoxFit.cover)), + ) + : const SizedBox.shrink(), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 5), + child: CustomTextLabel( + text: category.categoryName!, + textStyle: TextStyle(fontWeight: FontWeight.w600, fontSize: 16, color: UiUtils.getColorScheme(context).primaryContainer), + textAlign: TextAlign.center, + maxLines: 2)), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold(appBar: CustomAppBar(height: 45, isBackBtn: false, label: 'categoryLbl', isConvertText: true), body: _buildCategory()); + } +} diff --git a/news-app/lib/ui/screens/HomePage/HomePage.dart b/news-app/lib/ui/screens/HomePage/HomePage.dart new file mode 100644 index 00000000..8b8ef379 --- /dev/null +++ b/news-app/lib/ui/screens/HomePage/HomePage.dart @@ -0,0 +1,407 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hive/hive.dart'; +import 'package:location/location.dart' as loc; +import 'package:marqueer/marqueer.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/cubits/Auth/registerTokenCubit.dart'; +import 'package:news/cubits/Bookmark/bookmarkCubit.dart'; +import 'package:news/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; +import 'package:news/cubits/breakingNewsCubit.dart'; +import 'package:news/cubits/featureSectionCubit.dart'; +import 'package:news/cubits/generalNewsCubit.dart'; +import 'package:news/cubits/getUserDataByIdCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/languageJsonCubit.dart'; +import 'package:news/cubits/liveStreamCubit.dart'; +import 'package:news/cubits/otherPagesCubit.dart'; +import 'package:news/cubits/sectionByIdCubit.dart'; +import 'package:news/cubits/settingCubit.dart'; +import 'package:news/cubits/weatherCubit.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/models/authorModel.dart'; +import 'package:news/data/repositories/SectionById/sectionByIdRepository.dart'; +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; +import 'package:news/ui/screens/HomePage/Widgets/GeneralNewsRandomStyle.dart'; +import 'package:news/ui/screens/HomePage/Widgets/LiveWithSearchView.dart'; +import 'package:news/ui/screens/HomePage/Widgets/SectionShimmer.dart'; +import 'package:news/ui/screens/HomePage/Widgets/WeatherData.dart'; +import 'package:news/ui/screens/HomePage/Widgets/SectionStyle1.dart'; +import 'package:news/ui/screens/HomePage/Widgets/SectionStyle2.dart'; +import 'package:news/ui/screens/HomePage/Widgets/SectionStyle3.dart'; +import 'package:news/ui/screens/HomePage/Widgets/SectionStyle4.dart'; +import 'package:news/ui/screens/HomePage/Widgets/SectionStyle5.dart'; +import 'package:news/ui/screens/HomePage/Widgets/SectionStyle6.dart'; +import 'package:news/ui/screens/Profile/Widgets/customAlertDialog.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; +import 'package:news/ui/widgets/adSpaces.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/errorContainerWidget.dart'; +import 'package:news/utils/ErrorMessageKeys.dart'; +import 'package:news/utils/hiveBoxKeys.dart'; +import 'package:news/utils/strings.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/data/models/AuthModel.dart'; +import 'package:news/data/models/FeatureSectionModel.dart'; + +class HomeScreen extends StatefulWidget { + const HomeScreen({super.key}); + + @override + HomeScreenState createState() => HomeScreenState(); +} + +class HomeScreenState extends State with TickerProviderStateMixin { + final GlobalKey _refreshIndicatorKey = GlobalKey(); + late final ScrollController featuredSectionsScrollController = ScrollController()..addListener(hasMoreFeaturedSectionsScrollListener); + + final loc.Location _location = loc.Location(); + bool? _serviceEnabled; + loc.PermissionStatus? _permissionGranted; + double? lat; + double? lon; + bool updateList = false; + Set get locationValue => SettingsLocalDataRepository().getLocationCityValues(); + late final appConfig, authConfig; + String languageId = "14"; //set it as default language code + + void getSections() { + Future.delayed(Duration.zero, () { + context.read().getSection(langId: languageId, latitude: locationValue.first, longitude: locationValue.last); + }).whenComplete(() => getGeneralNews()); + } + + void getLiveStreamData() { + Future.delayed(Duration.zero, () { + context.read().getLiveStream(langId: languageId); + }); + } + + void getBookmark() { + Future.delayed(Duration.zero, () { + context.read().getBookmark(langId: languageId); + }); + } + + void getLikeNews() { + Future.delayed(Duration.zero, () { + context.read().getLike(langId: languageId); + }); + } + + void getUserData() { + Future.delayed(Duration.zero, () { + context.read().getUserById(); + }); + } + + checkForAppUpdate() { + WidgetsBinding.instance.addPostFrameCallback((_) async { + if (context.read().state is AppConfigurationFetchSuccess) { + if (context.read().isUpdateRequired()) { + openUpdateDialog(); + } + } + }); + } + + openUpdateDialog() { + bool isForceUpdate = (context.read().getForceUpdateMode() != "" && context.read().getForceUpdateMode() == "1") ? true : false; + + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return StatefulBuilder(builder: (BuildContext context, StateSetter setStater) { + return PopScope( + canPop: false, + child: CustomAlertDialog( + isForceAppUpdate: (isForceUpdate) ? true : false, + context: context, + yesButtonText: 'yesLbl', + yesButtonTextPostfix: '', + noButtonText: (isForceUpdate) ? 'exitLbl' : 'noLbl', + imageName: '', + titleWidget: CustomTextLabel( + text: (isForceUpdate) ? 'forceUpdateTitleLbl' : 'newVersionAvailableTitleLbl', + textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w800, color: UiUtils.getColorScheme(context).primaryContainer)), + messageText: context.read().getTranslatedLabels((isForceUpdate) ? 'forceUpdateDescLbl' : 'newVersionAvailableDescLbl'), + onYESButtonPressed: () => UiUtils.gotoStores(context)), + ); + }); + }); + } + + showPermissionPopup() async { + loc.LocationData locationData; + + _serviceEnabled = await _location.serviceEnabled(); + if (!_serviceEnabled!) { + _serviceEnabled = await _location.requestService(); + if (!_serviceEnabled!) { + return; + } + } + + _permissionGranted = await _location.hasPermission(); + if (_permissionGranted == loc.PermissionStatus.denied) { + SettingsLocalDataRepository().setLocationCityKeys(null, null); + _permissionGranted = await _location.requestPermission(); + if (_permissionGranted != loc.PermissionStatus.granted) { + return; + } + } + + locationData = await _location.getLocation(); + + setState(() { + lat = locationData.latitude; + lon = locationData.longitude; + }); + getLocationPermission(); + + return (locationData); + } + + getLocationPermission() async { + if (appConfig.getLocationWiseNewsMode() == "1") { + SettingsLocalDataRepository().setLocationCityKeys(lat, lon); + //update latitude,longitude - along with token + if (context.read().getSettings().token != '') { + context.read().registerToken(fcmId: context.read().getSettings().token, context: context); + context.read().changeFcmToken(context.read().getSettings().token); + } + } else { + SettingsLocalDataRepository().setLocationCityKeys(null, null); + } + + if (appConfig.getWeatherMode() == "1") { + getWeatherData(); + } + } + + Future getWeatherData() async { + if (lat != null && lon != null) { + context.read().getWeatherDetails(langId: (Hive.box(settingsBoxKey).get(currentLanguageCodeKey)), lat: lat.toString(), lon: lon.toString()); + } + } + + void getBreakingNews() { + Future.delayed(Duration.zero, () { + context.read().getBreakingNews(langId: languageId); + }); + } + + void getGeneralNews() { + context.read().getGeneralNews(langId: languageId, latitude: locationValue.first, longitude: locationValue.last); + } + + @override + void initState() { + super.initState(); + appConfig = context.read(); + authConfig = context.read(); + languageId = context.read().state.id; + getSections(); + if (appConfig.getWeatherMode() == "1" || appConfig.getLocationWiseNewsMode() == "1") showPermissionPopup(); + if (authConfig.getUserId() != "0") { + getUserData(); + } + getLiveStreamData(); + if (appConfig.getBreakingNewsMode() == "1") getBreakingNews(); + if (appConfig.getMaintenanceMode() == "1") Navigator.of(context).pushReplacementNamed(Routes.maintenance); + } + + @override + void dispose() { + featuredSectionsScrollController.dispose(); + super.dispose(); + } + + void hasMoreFeaturedSectionsScrollListener() { + if (featuredSectionsScrollController.position.atEdge) { + if (context.read().hasMoreSections()) { + context.read().getMoreSections(langId: languageId, latitude: locationValue.first, longitude: locationValue.last); + } else {} + } + } + + Widget breakingNewsMarquee() { + return BlocBuilder(builder: ((context, state) { + return (state is BreakingNewsFetchSuccess && state.breakingNews.isNotEmpty) + ? Container( + margin: const EdgeInsets.only(top: 10), + color: primaryColor, + height: 32, + child: Marqueer.builder( + pps: 25.0, + restartAfterInteractionDuration: const Duration(seconds: 1), + separatorBuilder: (_, index) => + Center(child: Text(' ● ', style: Theme.of(context).textTheme.titleSmall!.copyWith(color: UiUtils.getColorScheme(context).secondary, fontWeight: FontWeight.normal))), + itemBuilder: (context, index) { + var multiplier = index ~/ state.breakingNews.length; + var i = index; + if (multiplier > 0) { + i = index - (multiplier * state.breakingNews.length); + } + final item = state.breakingNews[i]; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 5), + child: CustomTextLabel( + text: item.title!, textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: UiUtils.getColorScheme(context).secondary, fontWeight: FontWeight.normal))); + }, + ), + ) + : const SizedBox.shrink(); + })); + } + + Widget getSectionList() { + return BlocBuilder(builder: (context, newsState) { + return BlocBuilder(builder: (context, sectionState) { + if (sectionState is SectionFetchSuccess) { + //if it has only one section and it doesn't have news in it then show No data found message + if (sectionState.section.length == 1) { + if (sectionState.section.first.newsType == "breaking_news") { + if (sectionState.section.first.breakNewsTotal == 0) { + return ErrorContainerWidget(errorMsg: ErrorMessageKeys.noDataMessage, onRetry: _refresh); + } + } else if (sectionState.section.first.newsTotal != null && sectionState.section.first.newsTotal! == 0) { + return ErrorContainerWidget(errorMsg: ErrorMessageKeys.noDataMessage, onRetry: _refresh); + } + } + + return ListView.builder( + shrinkWrap: true, + padding: EdgeInsets.zero, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: ((context, index) { + FeatureSectionModel model = sectionState.section[index]; + //check for more featured sections + if (index == sectionState.section.length - 1 && index != 0) { + if (sectionState.hasMore) { + if (sectionState.hasMoreFetchError) { + return const SizedBox.shrink(); + } else { + return Center(child: Padding(padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0), child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor))); + } + } + } + + return sectionData(model: model); + }), + itemCount: sectionState.section.length); + } + if (sectionState is SectionFetchFailure) { + if (context.read().state is GeneralNewsFetchSuccess) { + return sectionData(newsModelList: (context.read().state as GeneralNewsFetchSuccess).generalNews); + } else { + return ErrorContainerWidget( + errorMsg: (sectionState.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : sectionState.errorMessage, onRetry: _refresh); + } + } + return sectionShimmer(context); //state is SectionFetchInProgress || state is SectionInitial + }); + }); + } + + Widget sectionData({FeatureSectionModel? model, List? newsModelList}) { + return (model != null) + ? Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ + if (model.adSpaceDetails != null) AdSpaces(adsModel: model.adSpaceDetails!), //sponsored ads + if (model.styleApp == 'style_1') Style1Section(model: model), + if (model.styleApp == 'style_2') Style2Section(model: model), + if (model.styleApp == 'style_3') Style3Section(model: model), + if (model.styleApp == 'style_4') Style4Section(model: model), + if (model.styleApp == 'style_5') Style5Section(model: model), + if (model.styleApp == 'style_6') BlocProvider(create: (context) => SectionByIdCubit(SectionByIdRepository()), child: Style6Section(model: model)) + ]) + : GeneralNewsRandomStyle(modelList: newsModelList!); + } + + //refresh function to refresh page + Future _refresh() async { + getSections(); + getLocationPermission(); + if (authConfig.getUserId() != "0") { + getUserData(); + getBookmark(); + getLikeNews(); + } + getLiveStreamData(); + getPages(); + + if (appConfig.getBreakingNewsMode() == "1") getBreakingNews(); + if (appConfig.getMaintenanceMode() == "1") Navigator.of(context).pushReplacementNamed(Routes.maintenance); + if (appConfig.getWeatherMode() == "1") getWeatherData(); + } + + getPages() { + Future.delayed(Duration.zero, () { + context.read().getOtherPage(langId: languageId); + }); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + body: RefreshIndicator( + key: _refreshIndicatorKey, + onRefresh: () => _refresh(), + child: BlocListener( + bloc: context.read(), + listener: (context, state) { + if (state is GetUserByIdFetchSuccess) { + var data = state.result; + if (data[STATUS] == 0) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'deactiveMsg'), context); + Future.delayed(const Duration(seconds: 2), () { + UiUtils.userLogOut(contxt: context); + }); + } else { + authConfig.updateDetails( + authModel: AuthModel( + id: data[ID].toString(), + name: data[NAME], + status: data[STATUS].toString(), + mobile: data[MOBILE], + email: data[EMAIL], + type: data[TYPE], + profile: data[PROFILE], + role: data[ROLE].toString(), + jwtToken: data[TOKEN], + isAuthor: data[IS_AUTHOR], + authorDetails: (data[IS_AUTHOR] == 1 && data[AUTHOR] != null) ? Author.fromJson(data[AUTHOR]) : null)); + } + } + }, + child: SingleChildScrollView( + controller: featuredSectionsScrollController, + physics: ClampingScrollPhysics(), //To restrict scrolling on Refresh + child: Padding( + padding: const EdgeInsetsDirectional.only(start: 15.0, end: 15.0, bottom: 10.0), + child: Column( + children: [ + const LiveWithSearchView(), + BlocBuilder(builder: (context, state) { + if (state is WeatherFetchSuccess) { + return WeatherDataView(weatherData: state.weatherData); + } + return SizedBox.shrink(); + }), + breakingNewsMarquee(), + getSectionList() + ], + ), + ), + ), + ))), + ); + } +} diff --git a/news-app/lib/ui/screens/HomePage/Widgets/CommonSectionTitle.dart b/news-app/lib/ui/screens/HomePage/Widgets/CommonSectionTitle.dart new file mode 100644 index 00000000..69619c6b --- /dev/null +++ b/news-app/lib/ui/screens/HomePage/Widgets/CommonSectionTitle.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:news/data/models/FeatureSectionModel.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/utils/uiUtils.dart'; + +Widget commonSectionTitle(FeatureSectionModel model, BuildContext context) { + return ListTile( + minVerticalPadding: 5, + contentPadding: EdgeInsets.zero, + title: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: CustomTextLabel( + text: model.title!, + textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.bold), + softWrap: true, + maxLines: 1, + overflow: TextOverflow.ellipsis)), + GestureDetector( + onTap: () { + UiUtils.showInterstitialAds(context: context); + if ((model.newsType == 'news' || model.newsType == "user_choice") || model.videosType == 'news' && model.newsType != 'breaking_news') { + Navigator.of(context).pushNamed(Routes.sectionNews, arguments: {"sectionId": model.id!, "title": model.title!}); + } else { + Navigator.of(context).pushNamed(Routes.sectionBreakNews, arguments: {"sectionId": model.id!, "title": model.title!}); + } + }, + child: CustomTextLabel( + text: 'viewMore', + textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(decoration: TextDecoration.underline, fontWeight: FontWeight.bold, color: UiUtils.getColorScheme(context).outline)), + ) + ], + ), + subtitle: (model.shortDescription != null) + ? CustomTextLabel( + text: model.shortDescription!, + textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6)), + softWrap: true, + maxLines: 3, + overflow: TextOverflow.ellipsis) + : SizedBox.shrink(), + ); +} diff --git a/news-app/lib/ui/screens/HomePage/Widgets/GeneralNewsRandomStyle.dart b/news-app/lib/ui/screens/HomePage/Widgets/GeneralNewsRandomStyle.dart new file mode 100644 index 00000000..c4d16b08 --- /dev/null +++ b/news-app/lib/ui/screens/HomePage/Widgets/GeneralNewsRandomStyle.dart @@ -0,0 +1,165 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/generalNewsCubit.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import 'package:news/utils/uiUtils.dart'; + +class GeneralNewsRandomStyle extends StatefulWidget { + final List modelList; + + const GeneralNewsRandomStyle({super.key, required this.modelList}); + + @override + GeneralNewsRandomStyleState createState() => GeneralNewsRandomStyleState(); +} + +class GeneralNewsRandomStyleState extends State { + late final ScrollController scrollController = ScrollController()..addListener(hasMoreGeneralNewsListener); + late int counter; //counter will handle unique index in both list & grid + late List newsList; + @override + void initState() { + super.initState(); + newsList = widget.modelList; + } + + @override + void dispose() { + scrollController.removeListener(() {}); + super.dispose(); + } + + void hasMoreGeneralNewsListener() { + if (scrollController.position.maxScrollExtent == scrollController.offset) { + if (context.read().hasMoreGeneralNews()) { + context.read().getMoreGeneralNews( + langId: context.read().state.id, + latitude: SettingsLocalDataRepository().getLocationCityValues().first, + longitude: SettingsLocalDataRepository().getLocationCityValues().last); + } + } + } + + @override + Widget build(BuildContext context) { + counter = 0; + return Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListView.separated( + controller: scrollController, + shrinkWrap: true, + padding: EdgeInsets.zero, + physics: const NeverScrollableScrollPhysics(), + itemCount: newsList.length, + separatorBuilder: (BuildContext context, int index) { + return (index.isOdd && counter + 1 < newsList.length) ? getGrid() : const SizedBox.shrink(); + }, + itemBuilder: (BuildContext context, int index) { + return (counter < newsList.length) ? listRow(counter++) : const SizedBox.shrink(); + }, + ), + ], + ); + } + + Widget getGrid() { + return SizedBox( + height: 200, + child: GridView.count( + crossAxisCount: 1, + scrollDirection: Axis.horizontal, + children: List.generate((counter % 3 == 0) ? 3 : 2, (index) { + return Padding(padding: const EdgeInsets.only(right: 15), child: listRow((counter < newsList.length) ? counter++ : counter)); + }))); + } + + Widget listRow(int index) { + NewsModel newsModel = newsList[index]; + return Padding( + padding: const EdgeInsets.only(top: 15), + child: GestureDetector( + onTap: () { + List newList = []; + newList.addAll(newsList); + newList.removeAt(index); + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": newsList[index], "newsList": newList, "isFromBreak": false, "fromShowMore": false}); + }, + child: Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(15), + child: ShaderMask( + shaderCallback: (rect) => + LinearGradient(begin: Alignment.center, end: Alignment.bottomCenter, colors: [Colors.transparent, darkSecondaryColor.withOpacity(0.9)]).createShader(rect), + blendMode: BlendMode.darken, + child: Container( + width: double.maxFinite, + height: MediaQuery.of(context).size.height / 3.3, + color: primaryColor.withValues(alpha: 0.15), + child: CustomNetworkImage( + networkImageUrl: newsModel.image!, + fit: BoxFit.cover, + width: double.maxFinite, + height: MediaQuery.of(context).size.height / 3.3, + isVideo: newsModel.type == 'videos' ? true : false), + ))), + if (newsModel.type == 'videos') + Positioned.directional( + textDirection: Directionality.of(context), + top: MediaQuery.of(context).size.height * 0.12, + start: MediaQuery.of(context).size.width / 3, + end: MediaQuery.of(context).size.width / 3, + child: InkWell( + onTap: () { + List allNewsList = List.from(newsList)..removeAt(index); + Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 1, "model": newsModel, "otherVideos": allNewsList}); + }, + child: UiUtils.setPlayButton(context: context))), + Positioned.directional( + textDirection: Directionality.of(context), + bottom: 10, + start: 10, + end: 10, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + if (newsModel.categoryName != null) + ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: Container( + padding: const EdgeInsets.all(5), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: CustomTextLabel( + text: newsModel.categoryName!, + textStyle: Theme.of(context).textTheme.bodyLarge?.copyWith(color: secondaryColor.withOpacity(0.6)), + overflow: TextOverflow.ellipsis, + softWrap: true)), + ), + ), + Padding( + padding: const EdgeInsets.only(top: 8), + child: CustomTextLabel( + text: newsModel.title!, + textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: secondaryColor, fontWeight: FontWeight.normal), + maxLines: 2, + overflow: TextOverflow.ellipsis, + softWrap: true)), + ], + )) + ], + )), + ); + } +} diff --git a/news-app/lib/ui/screens/HomePage/Widgets/LiveWithSearchView.dart b/news-app/lib/ui/screens/HomePage/Widgets/LiveWithSearchView.dart new file mode 100644 index 00000000..f733fb46 --- /dev/null +++ b/news-app/lib/ui/screens/HomePage/Widgets/LiveWithSearchView.dart @@ -0,0 +1,145 @@ +import 'package:flutter/material.dart'; +import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart'; +import 'package:shimmer/shimmer.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/themeCubit.dart'; +import 'package:news/ui/styles/appTheme.dart'; +import 'package:news/cubits/liveStreamCubit.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; + +class LiveWithSearchView extends StatefulWidget { + const LiveWithSearchView({super.key}); + + @override + LiveWithSearchState createState() => LiveWithSearchState(); +} + +class LiveWithSearchState extends State { + Widget liveWithSearchView() { + return BlocBuilder( + bloc: context.read(), + builder: (context, state) { + if (state is LiveStreamFetchSuccess) { + return Padding( + padding: const EdgeInsets.only(top: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // Logo Caribe + Container( + decoration: BoxDecoration(shape: BoxShape.circle, color: UiUtils.getColorScheme(context).surface), + height: 60, + width: 60, + child: Center( + child: SvgPictureWidget(assetName: "logo_caribe", height: 40.0, width: 40.0), + )), + const SizedBox(width: 10), + // Search bar + Expanded( + child: InkWell( + child: Container( + alignment: Alignment.centerLeft, + height: 60, + decoration: BoxDecoration(borderRadius: BorderRadius.circular(30.0), color: UiUtils.getColorScheme(context).surface), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Padding( + padding: const EdgeInsetsDirectional.only(start: 10.0), + child: Icon(Icons.search_rounded, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7))), + Padding( + padding: const EdgeInsetsDirectional.only(start: 10.0), + child: CustomTextLabel(text: 'searchHomeNews', maxLines: 3, textStyle: TextStyle(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)))), + ], + ), + )), + onTap: () { + Navigator.of(context).pushNamed(Routes.search); + }, + )), + if (state.liveStream.isNotEmpty && context.read().getLiveStreamMode() == "1") + Padding( + padding: const EdgeInsetsDirectional.only(start: 10.0), + child: InkWell( + child: Container( + decoration: BoxDecoration(shape: BoxShape.circle, color: UiUtils.getColorScheme(context).surface), + height: 60, + width: 60, + child: Center( + child: SvgPictureWidget(assetName: (context.read().state.appTheme == AppTheme.Dark ? "live_news_dark" : "live_news"), height: 30.0, width: 54.0), + )), + onTap: () { + Navigator.of(context).pushNamed(Routes.live, arguments: {"liveNews": state.liveStream}); + }, + )) + ], + )); + } + if (state is LiveStreamFetchFailure) { + return Padding( + padding: const EdgeInsets.only(top: 10), + child: Row( + children: [ + // Logo Caribe + Container( + decoration: BoxDecoration(shape: BoxShape.circle, color: UiUtils.getColorScheme(context).surface), + height: 60, + width: 60, + child: Center( + child: SvgPictureWidget(assetName: "logo_caribe", height: 40.0, width: 40.0), + )), + const SizedBox(width: 10), + // Search bar + Expanded( + child: InkWell( + child: Container( + alignment: Alignment.centerLeft, + height: 60, + decoration: BoxDecoration(borderRadius: BorderRadius.circular(30.0), color: UiUtils.getColorScheme(context).surface), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + Padding(padding: const EdgeInsetsDirectional.only(start: 10.0), child: Icon(Icons.search_rounded, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7))), + Padding( + padding: const EdgeInsetsDirectional.only(start: 10.0), + child: CustomTextLabel(text: 'searchHomeNews', maxLines: 3, textStyle: TextStyle(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7))), + ), + ], + ), + )), + onTap: () { + Navigator.of(context).pushNamed(Routes.search); + }, + ), + ), + ], + ), + ); + } + return shimmerData(); //state is LiveStreamFetchInProgress || state is LiveStreamInitial + }); + } + + Widget shimmerData() { + return Shimmer.fromColors( + baseColor: Colors.grey.withOpacity(0.6), + highlightColor: Colors.grey, + child: Container( + height: 60, + margin: const EdgeInsets.only(top: 15), + width: double.maxFinite, + padding: const EdgeInsets.only(left: 10.0, right: 10.0), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(30), color: Colors.grey.withOpacity(0.6)))); + } + + @override + Widget build(BuildContext context) { + return liveWithSearchView(); + } +} diff --git a/news-app/lib/ui/screens/HomePage/Widgets/SectionShimmer.dart b/news-app/lib/ui/screens/HomePage/Widgets/SectionShimmer.dart new file mode 100644 index 00000000..3881a63d --- /dev/null +++ b/news-app/lib/ui/screens/HomePage/Widgets/SectionShimmer.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; + +Widget sectionShimmer(BuildContext context) { + return Shimmer.fromColors( + baseColor: Colors.grey.withOpacity(0.6), + highlightColor: Colors.grey, + child: ListView(shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), padding: const EdgeInsetsDirectional.only(top: 10), children: [ + Container( + height: 55, + width: double.maxFinite, + decoration: BoxDecoration(color: Colors.grey.withOpacity(0.6), borderRadius: BorderRadius.circular(10)), + ), + ListView.builder( + padding: EdgeInsets.zero, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: 5, + itemBuilder: (context, index) { + return Container( + alignment: Alignment.center, + height: MediaQuery.of(context).size.height / 3.3, + width: MediaQuery.of(context).size.width, + margin: const EdgeInsets.only(top: 15), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: Colors.grey.withOpacity(0.6)), + ); + }), + ])); +} diff --git a/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle1.dart b/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle1.dart new file mode 100644 index 00000000..4b5fffdb --- /dev/null +++ b/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle1.dart @@ -0,0 +1,321 @@ +import 'dart:math'; + +import 'package:news/ui/screens/HomePage/Widgets/CommonSectionTitle.dart'; +import 'package:flutter/material.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/data/models/FeatureSectionModel.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/networkImage.dart'; + +class Style1Section extends StatefulWidget { + final FeatureSectionModel model; + + const Style1Section({super.key, required this.model}); + + @override + Style1SectionState createState() => Style1SectionState(); +} + +class Style1SectionState extends State { + int? style1Sel; + PageController? _pageStyle1Controller = PageController(); + int limit = limitOfStyle1; + int newsLength = 0; + int brNewsLength = 0; + + @override + void dispose() { + _pageStyle1Controller!.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + newsLength = (widget.model.newsType == 'news' || widget.model.newsType == "user_choice") ? widget.model.news!.length : widget.model.videos!.length; + brNewsLength = widget.model.newsType == 'breaking_news' ? widget.model.breakNews!.length : widget.model.breakVideos!.length; + + return style1Data(widget.model); + } + + Widget style1Data(FeatureSectionModel model) { + if (model.breakVideos!.isNotEmpty || model.breakNews!.isNotEmpty || model.videos!.isNotEmpty || model.news!.isNotEmpty) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + commonSectionTitle(model, context), + if ((model.newsType == 'news' || model.videosType == "news" || model.newsType == "user_choice") && + ((model.newsType == 'news' || model.newsType == "user_choice") ? model.news!.isNotEmpty : model.videos!.isNotEmpty)) + style1NewsData(model), + if ((model.newsType == 'breaking_news' || model.videosType == "breaking_news") && (model.newsType == 'breaking_news' ? model.breakNews!.isNotEmpty : model.breakVideos!.isNotEmpty)) + style1BreakNewsData(model) + ], + ); + } else { + return const SizedBox.shrink(); + } + } + + Widget style1NewsData(FeatureSectionModel model) { + if ((model.newsType == 'news' || model.newsType == "user_choice") ? model.news!.length > 1 : model.videos!.length > 1) { + style1Sel ??= 1; + _pageStyle1Controller = PageController(initialPage: 1, viewportFraction: 0.87); + } else { + style1Sel = 0; + _pageStyle1Controller = PageController(initialPage: 0, viewportFraction: 1); + } + + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: MediaQuery.of(context).size.height * 0.36, + width: double.maxFinite, + child: PageView.builder( + physics: const BouncingScrollPhysics(), + itemCount: min(newsLength, limit), + scrollDirection: Axis.horizontal, + pageSnapping: true, + controller: _pageStyle1Controller, + onPageChanged: (index) { + setState(() => style1Sel = index); + }, + itemBuilder: (BuildContext context, int index) { + NewsModel data = (model.newsType == 'news' || model.newsType == "user_choice") ? model.news![index] : model.videos![index]; + return InkWell( + child: Padding( + padding: EdgeInsetsDirectional.only(start: 7, end: 7, top: style1Sel == index ? 0 : MediaQuery.of(context).size.height * 0.027), + child: Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(15), + child: CustomNetworkImage( + networkImageUrl: data.image!, + height: style1Sel == index ? MediaQuery.of(context).size.height / 4 : MediaQuery.of(context).size.height / 5, + width: double.maxFinite, + fit: BoxFit.cover, + isVideo: model.newsType == 'videos' ? true : false), + ), + if (model.newsType == 'videos') + Positioned.directional( + textDirection: Directionality.of(context), + top: MediaQuery.of(context).size.height * 0.075, + start: MediaQuery.of(context).size.width / 3, + end: MediaQuery.of(context).size.width / 3, + child: InkWell( + onTap: () { + List newsList = List.from(model.videos ?? [])..removeAt(index); + Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 1, "model": data, "otherVideos": newsList}); + }, + child: UiUtils.setPlayButton(context: context)), + ), + Positioned.directional( + textDirection: Directionality.of(context), + start: 8, + end: 8, + top: MediaQuery.of(context).size.height / 7, + child: Container( + alignment: Alignment.center, + height: MediaQuery.of(context).size.height / 5, + width: MediaQuery.of(context).size.width, + margin: const EdgeInsetsDirectional.all(10), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: UiUtils.getColorScheme(context).surface), + padding: const EdgeInsets.all(13), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (data.categoryName != null) + Container( + height: 20.0, + padding: const EdgeInsetsDirectional.only(start: 8.0, end: 8.0, top: 2.5), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(5), color: Theme.of(context).primaryColor), + child: CustomTextLabel( + text: data.categoryName!, + textAlign: TextAlign.center, + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: secondaryColor), + overflow: TextOverflow.ellipsis, + softWrap: true)), + Padding( + padding: const EdgeInsets.only(top: 15.0), + child: CustomTextLabel( + text: data.title!, + textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.normal), + softWrap: true, + maxLines: 3, + overflow: TextOverflow.ellipsis)), + ], + ), + )), + ], + ), + ), + onTap: () { + if (model.newsType == 'news' || model.newsType == "user_choice") { + //interstitial ads + UiUtils.showInterstitialAds(context: context); + List newsList = []; + newsList.addAll(model.news!); + newsList.removeAt(index); + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": data, "newsList": newsList, "isFromBreak": false, "fromShowMore": false}); + } + }, + ); + }, + ), + ), + style1Indicator(model, min(newsLength, limit)) + ], + ); + } + + Widget style1BreakNewsData(FeatureSectionModel model) { + if (model.newsType == 'breaking_news' ? model.breakNews!.length > 1 : model.breakVideos!.length > 1) { + style1Sel ??= 1; + _pageStyle1Controller = PageController(initialPage: 1, viewportFraction: 0.87); + } else { + style1Sel = 0; + _pageStyle1Controller = PageController(initialPage: 0, viewportFraction: 1); + } + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: MediaQuery.of(context).size.height * 0.36, + width: double.maxFinite, + child: PageView.builder( + physics: const BouncingScrollPhysics(), + itemCount: min(brNewsLength, limit), + scrollDirection: Axis.horizontal, + controller: _pageStyle1Controller, + reverse: false, + onPageChanged: (index) { + setState(() => style1Sel = index); + }, + itemBuilder: (BuildContext context, int index) { + BreakingNewsModel data = model.newsType == 'breaking_news' ? model.breakNews![index] : model.breakVideos![index]; + + return Padding( + padding: EdgeInsetsDirectional.only(start: 7, end: 7, top: style1Sel == index ? 0 : MediaQuery.of(context).size.height * 0.027), + child: InkWell( + onTap: () { + if (model.newsType == 'breaking_news') { + //interstitial ads + UiUtils.showInterstitialAds(context: context); + List breakList = []; + breakList.addAll(model.breakNews!); + breakList.removeAt(index); + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"breakModel": data, "breakNewsList": breakList, "isFromBreak": true, "fromShowMore": false}); + } + }, + child: Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(15), + child: CustomNetworkImage( + networkImageUrl: data.image!, + height: style1Sel == index ? MediaQuery.of(context).size.height / 4 : MediaQuery.of(context).size.height / 5, + width: double.maxFinite, + fit: BoxFit.cover, + isVideo: model.newsType == 'videos' ? true : false), + ), + if (model.newsType == 'videos') + Positioned.directional( + textDirection: Directionality.of(context), + top: MediaQuery.of(context).size.height * 0.075, + start: MediaQuery.of(context).size.width / 3, + end: MediaQuery.of(context).size.width / 3, + child: InkWell( + onTap: () { + List allBrNewsList = List.from(model.breakVideos ?? [])..removeAt(index); + Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 3, "breakModel": data, "otherBreakingVideos": allBrNewsList}); + }, + child: UiUtils.setPlayButton(context: context)), + ), + Positioned.directional( + textDirection: Directionality.of(context), + start: 8, + end: 8, + top: MediaQuery.of(context).size.height / 7, + child: Container( + alignment: Alignment.center, + height: MediaQuery.of(context).size.height / 5, + width: MediaQuery.of(context).size.width, + margin: const EdgeInsetsDirectional.all(10), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: UiUtils.getColorScheme(context).surface), + padding: const EdgeInsets.all(13), + child: CustomTextLabel( + text: data.title!, + textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.normal), + softWrap: true, + maxLines: 4, + overflow: TextOverflow.ellipsis))), + ], + ), + ), + ); + }, + ), + ), + style1Indicator(model, min(brNewsLength, limit)) + ], + ); + } + + Widget style1Indicator(FeatureSectionModel model, int len) { + return len <= 1 + ? const SizedBox.shrink() + : Align( + alignment: Alignment.center, + child: Padding( + padding: const EdgeInsets.only(top: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: map( + (model.newsType == 'news' || model.newsType == "user_choice") + ? model.news! + : (model.newsType == BREAKING_NEWS) + ? model.breakNews! + : (model.newsType == 'videos' && (model.videosTotal! > 0 && model.videos!.isNotEmpty)) + ? model.videos! + : model.breakVideos!, + (index, url) { + return Container( + alignment: Alignment.center, + child: Padding( + padding: const EdgeInsetsDirectional.only(start: 5.0, end: 5.0), + child: Container( + height: 14.0, + width: 14.0, + decoration: BoxDecoration(color: Colors.transparent, shape: BoxShape.circle, border: Border.all(color: UiUtils.getColorScheme(context).primaryContainer)), + child: style1Sel == index + ? Container( + margin: const EdgeInsets.all(2), + decoration: BoxDecoration(color: Theme.of(context).primaryColor, shape: BoxShape.circle), + ) + : const SizedBox.shrink()), + ), + ); + }, + ), + ))); + } + + List map(List list, Function handler) { + List result = []; + int mapLength = (widget.model.newsType == 'news' || widget.model.newsType == "user_choice") ? min(newsLength, limit) : min(brNewsLength, limit); + for (var i = 0; i < mapLength; i++) { + result.add(handler(i, list[i])); + } + return result; + } +} diff --git a/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle2.dart b/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle2.dart new file mode 100644 index 00000000..3805586f --- /dev/null +++ b/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle2.dart @@ -0,0 +1,170 @@ +import 'dart:math'; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; +import 'package:news/ui/screens/HomePage/Widgets/CommonSectionTitle.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/data/models/FeatureSectionModel.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/uiUtils.dart'; + +class Style2Section extends StatelessWidget { + final FeatureSectionModel model; + bool isNews = true; + + Style2Section({super.key, required this.model}); + + @override + Widget build(BuildContext context) { + return style2Data(model, context); + } + + Widget style2Data(FeatureSectionModel model, BuildContext context) { + if (model.breakVideos!.isNotEmpty || model.breakNews!.isNotEmpty || model.videos!.isNotEmpty || model.news!.isNotEmpty) { + if (model.newsType == 'news' || model.videosType == "news" || model.newsType == "user_choice") { + if ((model.newsType == 'news' || model.newsType == "user_choice") ? model.news!.isNotEmpty : model.videos!.isNotEmpty) { + isNews = true; + } + } + + if (model.newsType == 'breaking_news' || model.videosType == "breaking_news") { + if (model.newsType == 'breaking_news' ? model.breakNews!.isNotEmpty : model.breakVideos!.isNotEmpty) { + isNews = false; + } + } + int limit = limitOfAllOtherStyle; + int newsLength = (model.newsType == 'news' || model.newsType == "user_choice") ? model.news!.length : model.videos!.length; + int brNewsLength = model.newsType == 'breaking_news' ? model.breakNews!.length : model.breakVideos!.length; + + var totalCount = (isNews) ? min(newsLength, limit) : min(brNewsLength, limit); + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + commonSectionTitle(model, context), + ListView.builder( + padding: EdgeInsets.zero, + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: totalCount, + itemBuilder: (context, index) => (isNews) + ? setStyle2(context: context, index: index, model: model, newsModel: (model.newsType == 'news' || model.newsType == "user_choice") ? model.news![index] : model.videos![index]) + : setStyle2(context: context, index: index, model: model, breakingNewsModel: (model.newsType == 'breaking_news') ? model.breakNews![index] : model.breakVideos![index])), + ], + ); + } else { + return const SizedBox.shrink(); + } + } + + Widget setStyle2({required BuildContext context, required int index, required FeatureSectionModel model, NewsModel? newsModel, BreakingNewsModel? breakingNewsModel}) { + return Padding( + padding: EdgeInsets.only(top: index == 0 ? 0 : 15), + child: Column( + children: [ + if (context.read().getInAppAdsMode() == "1" && + (context.read().getAdsType() != "unity" || context.read().getIOSAdsType() != "unity")) + nativeAdsShow(context: context, index: index), + InkWell( + onTap: () { + //interstitial ads + UiUtils.showInterstitialAds(context: context); + if (model.newsType == 'news' || model.newsType == "user_choice") { + List newsList = []; + newsList.addAll(model.news!); + newsList.removeAt(index); + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": newsModel, "newsList": newsList, "isFromBreak": false, "fromShowMore": false}); + } else if (model.newsType == 'breaking_news') { + List breakList = []; + breakList.addAll(model.breakNews!); + breakList.removeAt(index); + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"breakModel": breakingNewsModel, "breakNewsList": breakList, "isFromBreak": true, "fromShowMore": false}); + } + }, + child: Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(15), + child: ShaderMask( + shaderCallback: (rect) => + LinearGradient(begin: Alignment.center, end: Alignment.bottomCenter, colors: [Colors.transparent, darkSecondaryColor.withOpacity(0.9)]).createShader(rect), + blendMode: BlendMode.darken, + child: Container( + color: primaryColor.withAlpha(5), + width: double.maxFinite, + height: MediaQuery.of(context).size.height / 3.3, + child: CustomNetworkImage( + networkImageUrl: (newsModel != null) ? newsModel.image! : breakingNewsModel!.image!, + fit: BoxFit.cover, + width: double.maxFinite, + height: MediaQuery.of(context).size.height / 3.3, + isVideo: model.newsType == 'videos' ? true : false), + ), + )), + if (model.newsType == 'videos') + Positioned.directional( + textDirection: Directionality.of(context), + top: MediaQuery.of(context).size.height * 0.12, + start: MediaQuery.of(context).size.width / 3, + end: MediaQuery.of(context).size.width / 3, + child: InkWell( + onTap: () { + List newsList = []; + List brNewsList = []; + if (model.breakVideos != null && model.breakVideos!.isNotEmpty) brNewsList = List.from(model.breakVideos ?? [])..removeAt(index); + if (model.videos != null && model.videos!.isNotEmpty) newsList = List.from(model.videos ?? [])..removeAt(index); + Navigator.of(context).pushNamed(Routes.newsVideo, arguments: { + "from": 1, + "model": (newsModel != null) ? newsModel : breakingNewsModel!, + if (newsList.isNotEmpty) "otherVideos": newsList, + if (brNewsList.isNotEmpty) "otherBreakingVideos": brNewsList + }); + }, + child: UiUtils.setPlayButton(context: context))), + Positioned.directional( + textDirection: Directionality.of(context), + bottom: 10, + start: 10, + end: 10, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + if (newsModel != null && newsModel.categoryName != null) + ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: Container( + padding: const EdgeInsets.all(5), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: CustomTextLabel( + text: newsModel.categoryName!, + textStyle: Theme.of(context).textTheme.bodyLarge?.copyWith(color: secondaryColor.withOpacity(0.6)), + overflow: TextOverflow.ellipsis, + softWrap: true)), + ), + ), + Padding( + padding: const EdgeInsets.only(top: 8), + child: CustomTextLabel( + text: (newsModel != null) ? newsModel.title! : breakingNewsModel!.title!, + textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: secondaryColor, fontWeight: FontWeight.normal), + maxLines: 2, + overflow: TextOverflow.ellipsis, + softWrap: true)), + ], + )) + ], + ), + ), + ], + ), + ); + } +} diff --git a/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle3.dart b/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle3.dart new file mode 100644 index 00000000..c4d8700a --- /dev/null +++ b/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle3.dart @@ -0,0 +1,241 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/data/models/FeatureSectionModel.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import 'package:news/ui/screens/HomePage/Widgets/CommonSectionTitle.dart'; + +class Style3Section extends StatelessWidget { + final FeatureSectionModel model; + + const Style3Section({super.key, required this.model}); + + @override + Widget build(BuildContext context) { + return style3Data(model, context); + } + + Widget style3Data(FeatureSectionModel model, BuildContext context) { + int limit = limitOfAllOtherStyle; + int newsLength = (model.newsType == 'news' || model.newsType == "user_choice") ? model.news!.length : model.videos!.length; + int brNewsLength = model.newsType == 'breaking_news' ? model.breakNews!.length : model.breakVideos!.length; + + if (model.breakVideos!.isNotEmpty || model.breakNews!.isNotEmpty || model.videos!.isNotEmpty || model.news!.isNotEmpty) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + commonSectionTitle(model, context), + if ((model.newsType == 'breaking_news' || model.videosType == "breaking_news") && (model.newsType == 'breaking_news' ? model.breakNews!.isNotEmpty : model.breakVideos!.isNotEmpty)) + SizedBox( + height: MediaQuery.of(context).size.height * 0.34, + width: MediaQuery.of(context).size.width, + child: ListView.builder( + padding: EdgeInsets.zero, + shrinkWrap: true, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: min(brNewsLength, limit), + scrollDirection: Axis.horizontal, + itemBuilder: (BuildContext context, int index) { + BreakingNewsModel data = model.newsType == 'breaking_news' ? model.breakNews![index] : model.breakVideos![index]; + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + if ((index != 0 && index % nativeAdsIndex == 0) && + context.read().getInAppAdsMode() == "1" && + (context.read().getAdsType() != "unity" || context.read().getIOSAdsType() != "unity")) + SizedBox(width: MediaQuery.of(context).size.width * 0.87, child: nativeAdsShow(context: context, index: index)), + InkWell( + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.87, + child: Stack( + children: [ + Positioned.directional( + textDirection: Directionality.of(context), + start: 0, + end: 0, + top: MediaQuery.of(context).size.height / 15, + child: Container( + alignment: Alignment.center, + height: MediaQuery.of(context).size.height / 4, + margin: EdgeInsetsDirectional.only(start: index == 0 ? 0 : 10, end: 10, top: 10, bottom: 10), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: UiUtils.getColorScheme(context).surface), + padding: const EdgeInsets.all(14), + child: Padding( + padding: EdgeInsets.only(top: MediaQuery.of(context).size.height / 9), + child: CustomTextLabel( + text: data.title!, + textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.normal), + softWrap: true, + maxLines: 2, + overflow: TextOverflow.ellipsis)), + )), + Positioned.directional( + textDirection: Directionality.of(context), + start: 30, + end: 30, + child: ClipRRect( + borderRadius: BorderRadius.circular(15), + child: CustomNetworkImage( + networkImageUrl: data.image!, + height: MediaQuery.of(context).size.height / 4.7, + width: double.maxFinite, + fit: BoxFit.cover, + isVideo: model.newsType == 'videos' ? true : false), + ), + ), + if (model.newsType == 'videos') + Positioned.directional( + textDirection: Directionality.of(context), + top: MediaQuery.of(context).size.height * 0.085, + start: MediaQuery.of(context).size.width / 3, + end: MediaQuery.of(context).size.width / 3, + child: InkWell( + onTap: () { + List brNewsList = List.from(model.breakVideos ?? [])..removeAt(index); + Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 3, "breakModel": data, "otherBreakingVideos": brNewsList}); + }, + child: UiUtils.setPlayButton(context: context)), + ), + ], + ), + ), + onTap: () { + if (model.newsType == 'breaking_news') { + //interstitial ads + UiUtils.showInterstitialAds(context: context); + List breakList = []; + breakList.addAll(model.breakNews!); + breakList.removeAt(index); + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"breakModel": data, "breakNewsList": breakList, "isFromBreak": true, "fromShowMore": false}); + } + }, + ), + ], + ); + })), + if ((model.newsType == 'news' || model.videosType == "news" || model.newsType == "user_choice") && + ((model.newsType == 'news' || model.newsType == "user_choice") ? model.news!.isNotEmpty : model.videos!.isNotEmpty)) + SizedBox( + height: MediaQuery.of(context).size.height * 0.34, + child: ListView.builder( + padding: EdgeInsets.zero, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: min(newsLength, limit), + scrollDirection: Axis.horizontal, + shrinkWrap: true, + itemBuilder: (BuildContext context, int index) { + NewsModel data = (model.newsType == 'news' || model.newsType == "user_choice") ? model.news![index] : model.videos![index]; + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + if ((index != 0 && index % nativeAdsIndex == 0) && + context.read().getInAppAdsMode() == "1" && + (context.read().getAdsType() != "unity" || context.read().getIOSAdsType() != "unity")) + SizedBox(width: MediaQuery.of(context).size.width * 0.87, child: nativeAdsShow(context: context, index: index)), + InkWell( + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.87, + child: Stack( + children: [ + Positioned.directional( + textDirection: Directionality.of(context), + start: 0, + end: 0, + top: MediaQuery.of(context).size.height / 15, + child: Container( + alignment: Alignment.center, + height: MediaQuery.of(context).size.height / 3.8, + margin: EdgeInsetsDirectional.only(start: index == 0 ? 0 : 10, end: 10, top: 10, bottom: 10), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: UiUtils.getColorScheme(context).surface), + padding: const EdgeInsets.all(14), + child: Padding( + padding: EdgeInsets.only(top: MediaQuery.of(context).size.height / 8), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (data.categoryName != null) + Container( + height: 20.0, + padding: const EdgeInsetsDirectional.only(start: 8.0, end: 8.0, top: 2.5), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(5), color: Theme.of(context).primaryColor), + child: CustomTextLabel( + text: data.categoryName!, + textAlign: TextAlign.center, + textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: secondaryColor), + overflow: TextOverflow.ellipsis, + softWrap: true)), + Padding( + padding: const EdgeInsets.only(top: 10.0), + child: CustomTextLabel( + text: data.title!, + textStyle: + Theme.of(context).textTheme.titleMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.normal), + softWrap: true, + maxLines: 2, + overflow: TextOverflow.ellipsis)), + ], + ), + ), + )), + Positioned.directional( + textDirection: Directionality.of(context), + start: 30, + end: 30, + child: ClipRRect( + borderRadius: BorderRadius.circular(15), + child: CustomNetworkImage( + networkImageUrl: data.image!, + height: MediaQuery.of(context).size.height / 4.7, + width: MediaQuery.of(context).size.width, + fit: BoxFit.cover, + isVideo: model.newsType == 'videos' ? true : false), + ), + ), + if (model.newsType == 'videos') + Positioned.directional( + textDirection: Directionality.of(context), + top: MediaQuery.of(context).size.height * 0.085, + start: MediaQuery.of(context).size.width / 3, + end: MediaQuery.of(context).size.width / 3, + child: InkWell( + onTap: () { + List allNewsList = List.from(model.videos ?? [])..removeAt(index); + Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 1, "model": data, "otherVideos": allNewsList}); + }, + child: UiUtils.setPlayButton(context: context)), + ), + ], + ), + ), + onTap: () { + if (model.newsType == 'news' || model.newsType == "user_choice") { + //interstitial ads + UiUtils.showInterstitialAds(context: context); + List newsList = []; + newsList.addAll(model.news!); + newsList.removeAt(index); + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": data, "newsList": newsList, "isFromBreak": false, "fromShowMore": false}); + } + }, + ), + ], + ); + })), + ], + ); + } else { + return const SizedBox.shrink(); + } + } +} diff --git a/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle4.dart b/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle4.dart new file mode 100644 index 00000000..c6ef1d38 --- /dev/null +++ b/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle4.dart @@ -0,0 +1,189 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:news/ui/screens/HomePage/Widgets/CommonSectionTitle.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/data/models/FeatureSectionModel.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/networkImage.dart'; + +class Style4Section extends StatelessWidget { + final FeatureSectionModel model; + + const Style4Section({super.key, required this.model}); + + @override + Widget build(BuildContext context) { + return style4Data(model, context); + } + + Widget style4Data(FeatureSectionModel model, BuildContext context) { + int limit = limitOfAllOtherStyle; + int newsLength = (model.newsType == 'news' || model.newsType == "user_choice") ? model.news!.length : model.videos!.length; + int brNewsLength = model.newsType == 'breaking_news' ? model.breakNews!.length : model.breakVideos!.length; + + if (model.breakVideos!.isNotEmpty || model.breakNews!.isNotEmpty || model.videos!.isNotEmpty || model.news!.isNotEmpty) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + commonSectionTitle(model, context), + if ((model.newsType == 'news' || model.videosType == "news" || model.newsType == "user_choice") && + ((model.newsType == 'news' || model.newsType == "user_choice") ? model.news!.isNotEmpty : model.videos!.isNotEmpty)) + Column( + children: [ + setGridLayout( + context: context, + totalCount: min(newsLength, limit), + childWidget: (context, index) { + NewsModel data = (model.newsType == 'news' || model.newsType == "user_choice") ? model.news![index] : model.videos![index]; + return InkWell( + child: Container( + padding: const EdgeInsets.all(7), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: UiUtils.getColorScheme(context).surface), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Stack(children: [ + setNewsImage(context: context, imageURL: data.image!), + if (data.categoryName != null && data.categoryName != "") + Align( + alignment: Alignment.topLeft, + child: Container( + margin: const EdgeInsetsDirectional.only(start: 7.0, top: 7.0), + height: 18.0, + padding: const EdgeInsetsDirectional.only(start: 6.0, end: 6.0, top: 2.5), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(5), color: Theme.of(context).primaryColor), + child: CustomTextLabel( + text: data.categoryName!, + textAlign: TextAlign.center, + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: secondaryColor), + overflow: TextOverflow.ellipsis, + softWrap: true)), + ), + if (model.newsType == 'videos') + Positioned.directional( + textDirection: Directionality.of(context), + top: MediaQuery.of(context).size.height * 0.065, + start: MediaQuery.of(context).size.width / 6, + end: MediaQuery.of(context).size.width / 6, + child: InkWell( + onTap: () { + List allNewsList = List.from(model.videos ?? [])..removeAt(index); + Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 1, "model": data, "otherVideos": allNewsList}); + }, + child: UiUtils.setPlayButton(context: context)), + ), + ]), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: CustomTextLabel( + text: data.title!, + textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer), + softWrap: true, + maxLines: 3, + overflow: TextOverflow.ellipsis)), + ], + ), + ), + onTap: () { + if (model.newsType == 'news' || model.newsType == "user_choice") { + //show interstitial ads + UiUtils.showInterstitialAds(context: context); + List newsList = []; + newsList.addAll(model.news!); + newsList.removeAt(index); + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": data, "newsList": newsList, "isFromBreak": false, "fromShowMore": false}); + } + }, + ); + }), + ], + ), + if ((model.newsType == 'breaking_news' || model.videosType == "breaking_news") && (model.newsType == 'breaking_news' ? model.breakNews!.isNotEmpty : model.breakVideos!.isNotEmpty)) + setGridLayout( + context: context, + totalCount: min(brNewsLength, limit), + childWidget: (context, index) { + BreakingNewsModel data = model.newsType == 'breaking_news' ? model.breakNews![index] : model.breakVideos![index]; + return InkWell( + child: Container( + padding: const EdgeInsets.all(7), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: UiUtils.getColorScheme(context).surface), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Stack(children: [ + setNewsImage(context: context, imageURL: data.image!), + if (model.newsType == 'videos') + Positioned.directional( + textDirection: Directionality.of(context), + top: MediaQuery.of(context).size.height * 0.065, + start: MediaQuery.of(context).size.width / 6, + end: MediaQuery.of(context).size.width / 6, + child: InkWell( + onTap: () { + List brNewsList = List.from(model.breakVideos ?? [])..removeAt(index); + Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 3, "breakModel": data, "otherBreakingVideos": brNewsList}); + }, + child: UiUtils.setPlayButton(context: context)), + ), + ]), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: CustomTextLabel( + text: data.title!, + textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer), + softWrap: true, + maxLines: 3, + overflow: TextOverflow.ellipsis)), + ], + ), + ), + onTap: () { + if (model.newsType == 'breaking_news') { + //interstitial ads + UiUtils.showInterstitialAds(context: context); + List breakList = []; + breakList.addAll(model.breakNews!); + breakList.removeAt(index); + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"breakModel": data, "breakNewsList": breakList, "isFromBreak": true, "fromShowMore": false}); + } + }, + ); + }) + ], + ); + } else { + return const SizedBox.shrink(); + } + } + + Widget setGridLayout({required BuildContext context, required int totalCount, required Widget? Function(BuildContext, int) childWidget}) { + return GridView.builder( + padding: EdgeInsets.zero, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(mainAxisExtent: MediaQuery.of(context).size.height * 0.27, crossAxisCount: 2, crossAxisSpacing: 10, mainAxisSpacing: 13), + shrinkWrap: true, + itemCount: totalCount, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: childWidget); + } + + Widget setNewsImage({required BuildContext context, required String imageURL}) { + return ClipRRect( + borderRadius: BorderRadius.circular(10), + child: CustomNetworkImage( + networkImageUrl: imageURL, + height: MediaQuery.of(context).size.height * 0.175, + width: MediaQuery.of(context).size.width, + fit: BoxFit.cover, + isVideo: model.newsType == 'videos' ? true : false)); + } +} diff --git a/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle5.dart b/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle5.dart new file mode 100644 index 00000000..a15f6d79 --- /dev/null +++ b/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle5.dart @@ -0,0 +1,400 @@ +import 'dart:math'; + +import 'package:news/ui/screens/HomePage/Widgets/CommonSectionTitle.dart'; +import 'package:flutter/material.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/data/models/FeatureSectionModel.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/networkImage.dart'; + +class Style5Section extends StatelessWidget { + final FeatureSectionModel model; + + const Style5Section({super.key, required this.model}); + + @override + Widget build(BuildContext context) { + return style5Data(model, context); + } + + Widget style5SingleNewsData(FeatureSectionModel model, BuildContext context) { + NewsModel data = (model.newsType == 'news' || model.newsType == "user_choice") ? model.news![0] : model.videos![0]; + return InkWell( + child: Container( + height: MediaQuery.of(context).size.height * 0.28, + width: double.maxFinite, + decoration: BoxDecoration(borderRadius: BorderRadius.circular(15)), + child: Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(15), + child: CustomNetworkImage( + networkImageUrl: data.image!, height: MediaQuery.of(context).size.height * 0.28, width: double.maxFinite, fit: BoxFit.cover, isVideo: model.newsType == 'videos' ? true : false), + ), + Align( + alignment: Alignment.bottomLeft, + child: Padding( + padding: EdgeInsets.all(MediaQuery.of(context).size.height * 0.01), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (data.categoryName != null) + Container( + height: 20.0, + padding: const EdgeInsetsDirectional.only(start: 8.0, end: 8.0, top: 2.5), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(5), color: Theme.of(context).primaryColor), + child: CustomTextLabel( + text: data.categoryName!, + textAlign: TextAlign.left, + textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: secondaryColor), + overflow: TextOverflow.ellipsis, + softWrap: true)), + Padding( + padding: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.01), + child: CustomTextLabel( + text: data.title!, + textAlign: TextAlign.left, + textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: secondaryColor, fontWeight: FontWeight.w700), + maxLines: 3, + overflow: TextOverflow.ellipsis, + softWrap: true)), + Padding( + padding: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.01), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.calendar_month, color: secondaryColor, size: 20), + Padding( + padding: const EdgeInsetsDirectional.only(start: 10), + child: CustomTextLabel( + text: UiUtils.convertToAgo(context, DateTime.parse(data.publishDate ?? data.date!), 0)!, + textAlign: TextAlign.left, + textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: secondaryColor, fontWeight: FontWeight.w600), + overflow: TextOverflow.ellipsis, + softWrap: true)), + ], + ), + ), + if (model.newsType == 'videos') + InkWell( + child: Padding(padding: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.02), child: UiUtils.setPlayButton(context: context)), + onTap: () { + List allNewsList = List.from(model.videos ?? [])..removeWhere((item) => item.id == data.id); + Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 1, "model": data, "otherVideos": allNewsList}); + }, + ), + ], + ), + ), + ), + ], + ), + ), + onTap: () { + if (model.newsType == 'news' || model.newsType == "user_choice") { + //interstitial ads + UiUtils.showInterstitialAds(context: context); + List newsList = []; + newsList.addAll(model.news!); + newsList.removeAt(0); + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": data, "newsList": newsList, "isFromBreak": false, "fromShowMore": false}); + } + }, + ); + } + + Widget style5SingleBreakNewsData(FeatureSectionModel model, BuildContext context) { + BreakingNewsModel data = model.newsType == 'breaking_news' ? model.breakNews![0] : model.breakVideos![0]; + return InkWell( + child: Container( + height: MediaQuery.of(context).size.height * 0.28, + width: double.maxFinite, + decoration: BoxDecoration(borderRadius: BorderRadius.circular(15)), + child: Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(15), + child: ShaderMask( + shaderCallback: (rect) => LinearGradient(begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.transparent, darkSecondaryColor.withOpacity(0.9)]).createShader(rect), + blendMode: BlendMode.darken, + child: CustomNetworkImage( + networkImageUrl: data.image!, height: MediaQuery.of(context).size.height * 0.28, width: double.maxFinite, fit: BoxFit.cover, isVideo: model.newsType == 'videos' ? true : false), + ), + ), + (model.newsType == 'videos') + ? Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: EdgeInsets.all(MediaQuery.of(context).size.height * 0.01), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.02), + child: CustomTextLabel( + text: data.title!, + textAlign: TextAlign.left, + textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: secondaryColor, fontWeight: FontWeight.w700), + maxLines: 3, + overflow: TextOverflow.ellipsis, + softWrap: true)), + InkWell( + child: Padding(padding: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.02), child: UiUtils.setPlayButton(context: context)), + onTap: () { + List brNewsList = List.from(model.breakVideos ?? [])..removeWhere((item) => item.id == data.id); + Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 3, "breakModel": data, "otherBreakingVideos": brNewsList}); + }, + ), + ], + )), + ) + : Positioned( + bottom: 0, + left: 0, + right: 0, + child: Container( + padding: EdgeInsets.all(MediaQuery.of(context).size.height * 0.01), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(15)), + child: CustomTextLabel( + text: data.title!, + textAlign: TextAlign.left, + textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: secondaryColor, fontWeight: FontWeight.w700), + maxLines: 3, + overflow: TextOverflow.ellipsis, + softWrap: true)), + ), + ], + ), + ), + onTap: () { + if (model.newsType == 'breaking_news') { + //show interstitial ads + UiUtils.showInterstitialAds(context: context); + List breakList = []; + breakList.addAll(model.breakNews!); + breakList.removeAt(0); + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"breakModel": data, "breakNewsList": breakList, "isFromBreak": true, "fromShowMore": false}); + } + }, + ); + } + + Widget style5Data(FeatureSectionModel model, BuildContext context) { + int limit = limitOfAllOtherStyle; + int newsLength = (model.newsType == 'news' || model.newsType == "user_choice") ? model.news!.length : model.videos!.length; + int brNewsLength = model.newsType == 'breaking_news' ? model.breakNews!.length : model.breakVideos!.length; + + if (model.breakVideos!.isNotEmpty || model.breakNews!.isNotEmpty || model.videos!.isNotEmpty || model.news!.isNotEmpty) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + commonSectionTitle(model, context), + if ((model.newsType == 'news' || model.videosType == "news" || model.newsType == "user_choice") && (model.newsType == 'news' || model.newsType == "user_choice") + ? model.news!.isNotEmpty + : model.videos!.isNotEmpty) + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + style5SingleNewsData(model, context), + if (newsLength > 1) + Padding( + padding: const EdgeInsets.only(top: 15.0), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: List.generate(min(newsLength, limit), (index) { + if (newsLength > index + 1) { + NewsModel data = (model.newsType == 'news' || model.newsType == "user_choice") ? model.news![index + 1] : model.videos![index + 1]; + return InkWell( + child: SizedBox( + width: MediaQuery.of(context).size.width / 2.35, + child: Padding( + padding: EdgeInsetsDirectional.only(start: index == 0 ? 0 : 10.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Stack(children: [ + ClipRRect( + borderRadius: BorderRadius.circular(10), + child: CustomNetworkImage( + networkImageUrl: data.image!, + height: MediaQuery.of(context).size.height * 0.15, + width: MediaQuery.of(context).size.width / 2.35, + fit: BoxFit.cover, + isVideo: model.newsType == 'videos' ? true : false), + ), + if (data.categoryName != null) + Align( + alignment: Alignment.topLeft, + child: Container( + margin: const EdgeInsetsDirectional.only(start: 7.0, top: 7.0), + height: 18.0, + padding: const EdgeInsetsDirectional.only(start: 6.0, end: 6.0, top: 2.5), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(5), color: Theme.of(context).primaryColor), + child: CustomTextLabel( + text: data.categoryName!, + textAlign: TextAlign.left, + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: secondaryColor), + overflow: TextOverflow.ellipsis, + softWrap: true)), + ), + if (model.newsType == 'videos') + Positioned.directional( + textDirection: Directionality.of(context), + top: MediaQuery.of(context).size.height * 0.058, + start: MediaQuery.of(context).size.width / 8.5, + end: MediaQuery.of(context).size.width / 8.5, + child: InkWell( + onTap: () { + List allNewsList = List.from(model.videos ?? [])..removeAt(index); + Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 1, "model": data, "otherVideos": allNewsList}); + }, + child: UiUtils.setPlayButton(context: context, heightVal: 28)), + ), + ]), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: CustomTextLabel( + text: data.title!, + textStyle: Theme.of(context).textTheme.labelMedium!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer), + softWrap: true, + maxLines: 2, + overflow: TextOverflow.ellipsis)), + Padding( + padding: const EdgeInsets.only(top: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.calendar_month, color: UiUtils.getColorScheme(context).primaryContainer, size: 15), + Padding( + padding: const EdgeInsetsDirectional.only(start: 5), + child: CustomTextLabel( + text: UiUtils.convertToAgo(context, DateTime.parse(data.publishDate ?? data.date!), 0)!, + textAlign: TextAlign.left, + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer), + overflow: TextOverflow.ellipsis, + softWrap: true)), + ], + ), + ), + ], + ), + ), + ), + onTap: () { + if (model.newsType == 'news' || model.newsType == "user_choice") { + //interstitial ads + UiUtils.showInterstitialAds(context: context); + List newsList = []; + newsList.addAll(model.news!); + newsList.removeAt(index + 1); + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": data, "newsList": newsList, "isFromBreak": false, "fromShowMore": false}); + } + }, + ); + } else { + return const SizedBox.shrink(); + } + })), + )), + ], + ), + if ((model.newsType == 'breaking_news' && model.breakNews?.isNotEmpty == true) || (model.videosType == 'breaking_news' && model.breakVideos?.isNotEmpty == true)) + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + style5SingleBreakNewsData(model, context), + if (brNewsLength > 1) + Padding( + padding: const EdgeInsets.only(top: 15.0), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: List.generate(min(brNewsLength, limit), (index) { + if (brNewsLength > index + 1) { + BreakingNewsModel data = model.newsType == 'breaking_news' ? model.breakNews![index + 1] : model.breakVideos![index + 1]; + return InkWell( + child: Container( + width: MediaQuery.of(context).size.width / 2.35, + padding: EdgeInsetsDirectional.only(start: index == 0 ? 0 : 10.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Stack(children: [ + ClipRRect( + borderRadius: BorderRadius.circular(10), + child: CustomNetworkImage( + networkImageUrl: data.image!, + height: MediaQuery.of(context).size.height * 0.15, + width: MediaQuery.of(context).size.width / 2.35, + fit: BoxFit.cover, + isVideo: model.newsType == 'videos' ? true : false), + ), + if (model.newsType == 'videos') + Positioned.directional( + textDirection: Directionality.of(context), + top: MediaQuery.of(context).size.height * 0.058, + start: MediaQuery.of(context).size.width / 8.5, + end: MediaQuery.of(context).size.width / 8.5, + child: InkWell( + onTap: () { + List brNewsList = List.from(model.breakVideos ?? [])..removeAt(index); + Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 3, "breakModel": data, "otherBreakingVideos": brNewsList}); + }, + child: UiUtils.setPlayButton(context: context, heightVal: 28)), + ), + ]), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: CustomTextLabel( + text: data.title!, + textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer), + softWrap: true, + maxLines: 2, + overflow: TextOverflow.ellipsis)), + ], + ), + ), + onTap: () { + if (model.newsType == 'breaking_news') { + //interstitial ads + UiUtils.showInterstitialAds(context: context); + List breakList = []; + breakList.addAll(model.breakNews!); + breakList.removeAt(index + 1); + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"breakModel": data, "breakNewsList": breakList, "isFromBreak": true, "fromShowMore": false}); + } + }, + ); + } else { + return const SizedBox.shrink(); + } + })), + )), + ], + ) + ], + ); + } else { + return const SizedBox.shrink(); + } + } +} diff --git a/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle6.dart b/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle6.dart new file mode 100644 index 00000000..3c5903ef --- /dev/null +++ b/news-app/lib/ui/screens/HomePage/Widgets/SectionStyle6.dart @@ -0,0 +1,216 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/sectionByIdCubit.dart'; +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/data/models/FeatureSectionModel.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; + +class Style6Section extends StatefulWidget { + final FeatureSectionModel model; + + const Style6Section({super.key, required this.model}); + + @override + Style6SectionState createState() => Style6SectionState(); +} + +class Style6SectionState extends State { + int? style6Sel; + bool isNews = true; + late final ScrollController style6ScrollController = ScrollController()..addListener(hasMoreSectionScrollListener); + late BreakingNewsModel breakingNewsData; + late NewsModel newsData; + + @override + void initState() { + super.initState(); + getSectionDataById(); + } + + @override + void dispose() { + style6ScrollController.removeListener(() {}); + super.dispose(); + } + + void hasMoreSectionScrollListener() { + if (style6ScrollController.position.maxScrollExtent == style6ScrollController.offset) { + if (context.read().hasMoreSections() && !(context.read().state is SectionByIdFetchInProgress)) { + context.read().getMoreSectionById(langId: context.read().state.id, sectionId: widget.model.id!); + } + } + } + + @override + Widget build(BuildContext context) { + return style6Data(); + } + + void getSectionDataById() { + Future.delayed(Duration.zero, () { + context.read().getSectionById( + langId: context.read().state.id, + sectionId: widget.model.id!, + latitude: SettingsLocalDataRepository().getLocationCityValues().first, + longitude: SettingsLocalDataRepository().getLocationCityValues().last); + }); + } + + Widget style6Data() { + return BlocBuilder(builder: (context, state) { + if (state is SectionByIdFetchSuccess) { + isNews = (state.type == 'news' || state.type == 'user_choice' || state.type == 'videos') && state.newsModel.isNotEmpty + ? true + : (state.type == 'breaking_news' || state.type == 'videos') && state.breakNewsModel.isNotEmpty + ? false + : isNews; + int totalCount = (isNews) ? state.newsModel.length : state.breakNewsModel.length; + return (state.breakNewsModel.isNotEmpty || state.newsModel.isNotEmpty) + ? Padding(padding: const EdgeInsets.symmetric(vertical: 5), child: style6NewsDetails(newsData: state.newsModel, brNewsData: state.breakNewsModel, total: totalCount, type: state.type)) + : SizedBox.shrink(); + } + return SizedBox.shrink(); + }); + } + + Widget style6NewsDetails({List? newsData, List? brNewsData, String? type, int total = 0}) { + return SizedBox( + height: MediaQuery.of(context).size.height / 2.8, + child: SingleChildScrollView( + controller: style6ScrollController, + scrollDirection: Axis.horizontal, + child: Row( + children: List.generate(total, (index) { + return SizedBox( + width: 180, + child: Padding( + padding: const EdgeInsets.all(5), + child: (isNews) + ? setImageCard(index: index, total: total, type: type!, newsModel: newsData![index], allNewsList: newsData) + : setImageCard(index: index, total: total, type: type!, breakingNewsModel: brNewsData![index], breakingNewsList: brNewsData))); + })))); + } + + Widget setImageCard( + {required int index, + required int total, + required String type, + NewsModel? newsModel, + BreakingNewsModel? breakingNewsModel, + List? allNewsList, + List? breakingNewsList}) { + return InkWell( + child: Stack(children: [ + ClipRRect( + borderRadius: BorderRadius.circular(5), + child: ShaderMask( + shaderCallback: (bounds) { + return LinearGradient(begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.transparent, darkSecondaryColor.withOpacity(0.9)]).createShader(bounds); + }, + blendMode: BlendMode.darken, + child: Container( + color: primaryColor.withValues(alpha: 0.15), + width: MediaQuery.of(context).size.width / 1.9, + height: MediaQuery.of(context).size.height / 2.5, + child: CustomNetworkImage( + networkImageUrl: (newsModel != null) ? newsModel.image! : breakingNewsModel!.image!, + height: MediaQuery.of(context).size.height / 2.5, + width: MediaQuery.of(context).size.width / 1.9, + fit: BoxFit.cover, + isVideo: type == "videos"), + ), + )), + (newsModel != null && newsModel.categoryName != null) + ? Align( + alignment: Alignment.topLeft, + child: Container( + margin: const EdgeInsetsDirectional.only(start: 7.0, top: 7.0), + height: 20.0, + padding: const EdgeInsetsDirectional.only(start: 6.0, end: 6.0, top: 2.5), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(3), color: Theme.of(context).primaryColor), + child: CustomTextLabel( + text: newsModel.categoryName!, + textAlign: TextAlign.center, + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: secondaryColor), + overflow: TextOverflow.ellipsis, + softWrap: true))) + : const SizedBox.shrink(), + (type == "videos") + ? Positioned.directional( + textDirection: Directionality.of(context), + top: MediaQuery.of(context).size.height * 0.13, + start: MediaQuery.of(context).size.width / 6, + end: MediaQuery.of(context).size.width / 6, + child: UiUtils.setPlayButton(context: context)) + : const SizedBox.shrink(), + Positioned.directional( + textDirection: Directionality.of(context), + bottom: 5, + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + (newsModel != null) + ? Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: CustomTextLabel( + text: UiUtils.convertToAgo(context, DateTime.parse(newsModel.publishDate ?? newsModel.date!), 3)!, + textStyle: Theme.of(context).textTheme.labelSmall!.copyWith(color: Colors.white))) + : const SizedBox.shrink(), + Container( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 5), + width: MediaQuery.of(context).size.width * 0.50, + child: CustomTextLabel( + text: (newsModel != null) ? newsModel.title! : breakingNewsModel!.title!, + textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: secondaryColor), + softWrap: true, + maxLines: 2, + overflow: TextOverflow.ellipsis)) + ]), + ), + ]), + onTap: () { + if (type == "videos") { + List newsList = []; + List brNewsList = []; + if (newsModel != null && allNewsList != null && allNewsList.isNotEmpty) { + newsList = List.from(allNewsList); + newsList.removeAt(index); + } else if (breakingNewsModel != null && breakingNewsList != null && breakingNewsList.isNotEmpty) { + brNewsList = List.from(breakingNewsList); + brNewsList.removeAt(index); + } + // List newsList = List.from(allNewsList ?? [])..removeAt(index); + // List breakNewsList = List.from(breakingNewsList ?? [])..removeAt(index); + Navigator.of(context).pushNamed(Routes.newsVideo, arguments: { + "from": (newsModel != null) ? 1 : 3, + if (newsModel != null) "model": newsModel, + if (breakingNewsModel != null) "breakModel": breakingNewsModel, + "otherBreakingVideos": brNewsList, + "otherVideos": newsList, + }); + } else if (type == 'news' || type == "user_choice") { + //interstitial ads + UiUtils.showInterstitialAds(context: context); + if (allNewsList != null && allNewsList.isNotEmpty) { + List newsList = List.from(allNewsList); + newsList.removeAt(index); + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": newsModel, "newsList": newsList, "isFromBreak": false, "fromShowMore": false}); + } + } else { + if (breakingNewsList != null && breakingNewsList.isNotEmpty) { + //interstitial ads + UiUtils.showInterstitialAds(context: context); + List breakList = List.from(breakingNewsList); + breakList.removeAt(index); + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"breakModel": breakingNewsModel, "breakNewsList": breakList, "isFromBreak": true, "fromShowMore": false}); + } + } + }, + ); + } +} diff --git a/news-app/lib/ui/screens/HomePage/Widgets/WeatherData.dart b/news-app/lib/ui/screens/HomePage/Widgets/WeatherData.dart new file mode 100644 index 00000000..5c0df327 --- /dev/null +++ b/news-app/lib/ui/screens/HomePage/Widgets/WeatherData.dart @@ -0,0 +1,140 @@ +import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; +import 'package:intl/intl.dart'; +import 'package:news/utils/hiveBoxKeys.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/data/models/WeatherData.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; + +class WeatherDataView extends StatefulWidget { + final WeatherDetails weatherData; + + const WeatherDataView({super.key, required this.weatherData}); + + @override + WeatherDataState createState() => WeatherDataState(); +} + +class WeatherDataState extends State { + late Future _future; + + @override + void initState() { + super.initState(); + _future = UiUtils.checkIfValidLocale(langCode: Hive.box(settingsBoxKey).get(currentLanguageCodeKey)); + } + + Widget weatherDataView() { + return FutureBuilder( + future: _future, + builder: (context, snapshot) { + DateTime now = DateTime.now(); + String day = DateFormat('EEEE').format(now); + WeatherDetails weatherData = widget.weatherData; + return Container( + margin: const EdgeInsetsDirectional.only(top: 15.0), + padding: const EdgeInsets.all(10.0), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: UiUtils.getColorScheme(context).surface), + height: MediaQuery.of(context).size.height * 0.14, + child: Row( + children: [ + Expanded( + flex: 3, + child: Column( + children: [ + CustomTextLabel( + text: 'weatherLbl', + textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8)), + maxLines: 1, + overflow: TextOverflow.ellipsis, + softWrap: true), + Row( + children: [ + Image.network("https:${weatherData.icon!}", width: 40.0, height: 40.0), + Padding( + padding: const EdgeInsetsDirectional.only(start: 7.0), + child: CustomTextLabel( + text: "${weatherData.tempC!.toString()}\u2103", + textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8)), + overflow: TextOverflow.ellipsis, + softWrap: true, + maxLines: 1)) + ], + ) + ], + ), + ), + const Spacer(), + Expanded( + flex: 4, + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + CustomTextLabel( + text: "${weatherData.name!},${weatherData.region!}", + textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600), + overflow: TextOverflow.ellipsis, + softWrap: true, + maxLines: 1), + CustomTextLabel( + text: weatherData.country!, + textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600), + overflow: TextOverflow.ellipsis, + softWrap: true, + maxLines: 1), + Padding( + padding: const EdgeInsetsDirectional.only(top: 3.0), + child: CustomTextLabel( + text: day, + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8)), + overflow: TextOverflow.ellipsis, + softWrap: true, + maxLines: 1)), + Padding( + padding: const EdgeInsetsDirectional.only(top: 3.0), + child: CustomTextLabel( + text: weatherData.text!, + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8)), + maxLines: 1, + overflow: TextOverflow.ellipsis, + softWrap: true)), + Expanded( + child: Padding( + padding: const EdgeInsetsDirectional.only(top: 3.0), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Icon(Icons.arrow_upward_outlined, size: 13.0, color: UiUtils.getColorScheme(context).primaryContainer), + CustomTextLabel( + text: "H:${weatherData.maxTempC!.toString()}\u2103", + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8)), + maxLines: 1, + overflow: TextOverflow.ellipsis, + softWrap: true), + Padding( + padding: const EdgeInsetsDirectional.only(start: 8.0), + child: Icon(Icons.arrow_downward_outlined, size: 13.0, color: UiUtils.getColorScheme(context).primaryContainer)), + CustomTextLabel( + text: "L:${weatherData.minTempC!.toString()}\u2103", + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8)), + maxLines: 1, + overflow: TextOverflow.ellipsis, + softWrap: true) + ], + )), + ) + ], + ), + ) + ], + )); + }, + ); + } + + @override + Widget build(BuildContext context) { + return weatherDataView(); + } +} diff --git a/news-app/lib/ui/screens/ImagePreviewScreen.dart b/news-app/lib/ui/screens/ImagePreviewScreen.dart new file mode 100644 index 00000000..2df9c6e4 --- /dev/null +++ b/news-app/lib/ui/screens/ImagePreviewScreen.dart @@ -0,0 +1,125 @@ +// ignore: must_be_immutable + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:photo_view/photo_view.dart'; + +class ImagePreview extends StatefulWidget { + final int index; + final List imgList; + + const ImagePreview({super.key, required this.index, required this.imgList}); + + @override + State createState() => StatePreview(); + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute(builder: (_) => ImagePreview(index: arguments['index'], imgList: arguments['imgList'])); + } +} + +class StatePreview extends State { + final GlobalKey _scaffoldKey = GlobalKey(); + int? curPos; + + @override + void initState() { + super.initState(); + + curPos = widget.index; + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + key: _scaffoldKey, + body: Stack( + children: [ + PageView.builder( + itemCount: widget.imgList.length, + controller: PageController(initialPage: curPos!), + onPageChanged: (index) async { + setState(() { + curPos = index; + }); + }, + itemBuilder: (BuildContext context, int index) { + return PhotoView( + backgroundDecoration: BoxDecoration(color: Theme.of(context).colorScheme.secondary), + initialScale: PhotoViewComputedScale.contained * 0.9, + minScale: PhotoViewComputedScale.contained * 0.9, + imageProvider: NetworkImage(widget.imgList[index])); + }), + Positioned.directional( + top: 35, + start: 20.0, + textDirection: Directionality.of(context), + child: InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: ClipRRect( + borderRadius: BorderRadius.circular(22.0), + child: Container( + height: 35, + width: 35, + alignment: Alignment.center, + decoration: BoxDecoration(color: UiUtils.getColorScheme(context).primaryContainer, shape: BoxShape.circle), + child: Icon( + Icons.keyboard_backspace_rounded, + color: UiUtils.getColorScheme(context).surface, + ))), + )), + Positioned(bottom: 10.0, left: 25.0, right: 25.0, child: SelectedPhoto(numberOfDots: widget.imgList.length, photoIndex: curPos!)) + ], + )); + } +} + +class SelectedPhoto extends StatelessWidget { + final int? numberOfDots; + final int? photoIndex; + + const SelectedPhoto({super.key, this.numberOfDots, this.photoIndex}); + + Widget _inactivePhoto(BuildContext context) { + return Padding( + padding: const EdgeInsetsDirectional.only(start: 3.0, end: 3.0), + child: Container(height: 8.0, width: 8.0, decoration: BoxDecoration(color: Theme.of(context).primaryColor.withOpacity(0.4), borderRadius: BorderRadius.circular(4.0))), + ); + } + + Widget _activePhoto(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(left: 5.0, right: 5.0), + child: Container( + height: 10.0, + width: 10.0, + decoration: + BoxDecoration(color: Theme.of(context).primaryColor, borderRadius: BorderRadius.circular(10.0), boxShadow: const [BoxShadow(color: Colors.grey, spreadRadius: 0.0, blurRadius: 2.0)]), + ), + ); + } + + List _buildDots(BuildContext context) { + List dots = []; + for (int i = 0; i < numberOfDots!; i++) { + dots.add(i == photoIndex ? _activePhoto(context) : _inactivePhoto(context)); + } + return dots; + } + + @override + Widget build(BuildContext context) { + return Center( + child: Row(mainAxisAlignment: MainAxisAlignment.center, children: _buildDots(context)), + ); + } +} diff --git a/news-app/lib/ui/screens/LiveStreaming.dart b/news-app/lib/ui/screens/LiveStreaming.dart new file mode 100644 index 00000000..3bf04694 --- /dev/null +++ b/news-app/lib/ui/screens/LiveStreaming.dart @@ -0,0 +1,79 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import 'package:news/ui/widgets/customAppBar.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/data/models/LiveStreamingModel.dart'; + +class LiveStreaming extends StatefulWidget { + List liveNews; + + LiveStreaming({super.key, required this.liveNews}); + + @override + State createState() => StateLive(); + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute(builder: (_) => LiveStreaming(liveNews: arguments['liveNews'])); + } +} + +class StateLive extends State { + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold(appBar: CustomAppBar(height: 45, isBackBtn: true, label: 'liveVideosLbl', horizontalPad: 15, isConvertText: true), body: mainListBuilder()); + } + + mainListBuilder() { + return Padding( + padding: const EdgeInsets.all(20), + child: ListView.separated( + itemBuilder: ((context, index) { + List liveVideos = List.from(widget.liveNews)..removeAt(index); + return Padding( + padding: EdgeInsets.only(top: index == 0 ? 0 : 20), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(10.0)), + child: InkWell( + onTap: () { + Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 2, "liveModel": widget.liveNews[index], "otherLiveVideos": liveVideos}); + }, + child: Stack( + alignment: Alignment.center, + children: [ + CustomNetworkImage(networkImageUrl: widget.liveNews[index].image!, height: 200, fit: BoxFit.cover, isVideo: true, width: double.infinity), + const CircleAvatar(radius: 30, backgroundColor: Colors.black45, child: Icon(Icons.play_arrow, size: 40, color: Colors.white)), + Positioned.directional( + textDirection: Directionality.of(context), + bottom: 10, + start: 20, + end: 20, + child: CustomTextLabel( + text: widget.liveNews[index].title!, textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: secondaryColor), maxLines: 2, overflow: TextOverflow.ellipsis), + ) + ], + ), + ), + ), + ); + }), + separatorBuilder: (context, index) { + return const SizedBox(height: 3.0); + }, + itemCount: widget.liveNews.length), + ); + } +} diff --git a/news-app/lib/ui/screens/ManagePreference.dart b/news-app/lib/ui/screens/ManagePreference.dart new file mode 100644 index 00000000..88e1c3de --- /dev/null +++ b/news-app/lib/ui/screens/ManagePreference.dart @@ -0,0 +1,316 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/ui/widgets/errorContainerWidget.dart'; +import 'package:news/utils/ErrorMessageKeys.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/cubits/UserPreferences/setUserPreferenceCatCubit.dart'; +import 'package:news/cubits/UserPreferences/userByCategoryCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/categoryCubit.dart'; +import 'package:news/data/models/CategoryModel.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; + +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import 'package:news/ui/widgets/customBackBtn.dart'; +import 'package:news/ui/widgets/customTextBtn.dart'; + +class ManagePref extends StatefulWidget { + final int? from; + + const ManagePref({super.key, this.from}); + + @override + State createState() { + return StateManagePref(); + } + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute(builder: (_) => ManagePref(from: arguments['from'])); + } +} + +class StateManagePref extends State with TickerProviderStateMixin { + final GlobalKey _scaffoldKey = GlobalKey(); + String catId = ""; + List selectedChoices = []; + String selCatId = ""; + + static int _count = 0; + List _checks = []; + late final ScrollController _categoryScrollController = ScrollController()..addListener(hasMoreCategoryScrollListener); + + @override + void initState() { + super.initState(); + + getUserData(); + } + + void getUserData() { + Future.delayed(Duration.zero, () { + context.read().getUserById(); + }); + } + + @override + void dispose() { + _categoryScrollController.dispose(); + super.dispose(); + } + + void hasMoreCategoryScrollListener() { + if (_categoryScrollController.offset >= _categoryScrollController.position.maxScrollExtent && !_categoryScrollController.position.outOfRange) { + if (context.read().hasMoreCategory()) { + context.read().getMoreCategory(langId: context.read().state.id); + } + } + } + + @override + Widget build(BuildContext context) { + return Scaffold(key: _scaffoldKey, appBar: setAppBar(), body: contentView()); + } + + setAppBar() { + return PreferredSize( + preferredSize: const Size(double.infinity, 70), + child: UiUtils.applyBoxShadow( + context: context, + child: AppBar( + leading: widget.from == 1 ? const CustomBackButton(horizontalPadding: 15) : const SizedBox.shrink(), + titleSpacing: 0.0, + centerTitle: false, + backgroundColor: Colors.transparent, + title: CustomTextLabel( + text: 'managePreferences', + textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600, letterSpacing: 0.5)), + actions: [skipBtn()], + ), + )); + } + + skipBtn() { + if (widget.from != 1) { + return Align( + alignment: Alignment.topRight, + child: CustomTextButton( + onTap: () { + Navigator.of(context).pushReplacementNamed(Routes.home, arguments: false); + }, + text: 'skip', + color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7))); + } else { + return const SizedBox.shrink(); + } + } + + contentView() { + return BlocConsumer( + bloc: context.read(), + listener: (context, state) { + if (state is UserByCatFetchSuccess) { + if (state.userByCat != null) { + for (int i = 0; i < state.userByCat.length; i++) { + catId = state.userByCat["category_id"]; + } + setState(() { + selectedChoices = catId == "" ? catId.split('') : catId.split(','); + }); + } + context.read().getCategory(langId: context.read().state.id); + } + }, + builder: (context, state) { + return BlocConsumer( + bloc: context.read(), + listener: (context, state) { + if (state is CategoryFetchSuccess) { + if ((state).category.isNotEmpty) { + setState(() { + _count = (state).category.length; + _checks = List.generate(_count, (i) => (selectedChoices.contains((state).category[i].id)) ? true : false); + }); + } + } + }, + builder: (context, state) { + if (state is CategoryFetchInProgress || state is CategoryInitial) { + return UiUtils.showCircularProgress(true, Theme.of(context).primaryColor); + } + if (state is CategoryFetchFailure) { + return ErrorContainerWidget( + errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage, onRetry: getUserData); + } + return SingleChildScrollView( + padding: const EdgeInsetsDirectional.only(start: 15.0, end: 15.0, bottom: 20.0), + controller: _categoryScrollController, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsetsDirectional.only(top: 25.0), + child: GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, crossAxisSpacing: 20, mainAxisSpacing: 20), + shrinkWrap: true, + itemCount: (state as CategoryFetchSuccess).category.length, + physics: const ClampingScrollPhysics(), + itemBuilder: (context, index) { + return _buildCategoryContainer( + category: state.category[index], + hasMore: state.hasMore, + hasMoreCategoryFetchError: state.hasMoreFetchError, + index: index, + totalCurrentCategory: state.category.length); + })), + nxtBtn() + ], + ), + ); + }); + }); + } + + selectCatTxt() { + return Transform( + transform: Matrix4.translationValues(-50.0, 0.0, 0.0), + child: CustomTextLabel( + text: 'sel_pref_cat', textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w100, letterSpacing: 0.5)), + ); + } + + _buildCategoryContainer({required CategoryModel category, required int index, required int totalCurrentCategory, required bool hasMoreCategoryFetchError, required bool hasMore}) { + if (index == totalCurrentCategory - 1 && index != 0) { + if (hasMore) { + if (hasMoreCategoryFetchError) { + return const SizedBox.shrink(); + } else { + return Center(child: Padding(padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0), child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor))); + } + } + } + return Container( + padding: EdgeInsets.zero, + decoration: const BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(15))), + child: InkWell( + onTap: () { + _checks[index] = !_checks[index]; + if (selectedChoices.contains(category.id)) { + selectedChoices.remove(category.id); + setState(() {}); + } else { + selectedChoices.add(category.id!); + setState(() {}); + } + if (selectedChoices.isEmpty) { + setState(() { + selectedChoices.add("0"); + }); + } else { + if (selectedChoices.contains("0")) { + selectedChoices = List.from(selectedChoices)..remove("0"); + } + } + }, + child: Stack( + fit: StackFit.expand, + children: [ + ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(15.0)), + child: CustomNetworkImage(networkImageUrl: category.image!, fit: BoxFit.cover, isVideo: false, height: MediaQuery.of(context).size.height / 2.9, width: double.maxFinite)), + Positioned.directional( + //checkbox + textDirection: Directionality.of(context), + end: 10, + top: 10, + child: Container( + height: 20, + width: 20, + decoration: + BoxDecoration(borderRadius: const BorderRadius.all(Radius.circular(5)), color: selectedChoices.contains(category.id) ? Theme.of(context).primaryColor : secondaryColor), + child: selectedChoices.contains(category.id) ? const Icon(Icons.check_rounded, color: secondaryColor, size: 20) : null)), + Positioned.directional( + textDirection: Directionality.of(context), + bottom: 0, + start: 0, + end: 0, + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(15.0), bottomRight: Radius.circular(15.0)), + gradient: LinearGradient(begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [darkSecondaryColor.withOpacity(0.03), darkSecondaryColor.withOpacity(0.85)])), + padding: EdgeInsets.zero, + margin: EdgeInsets.zero, + child: Padding( + padding: const EdgeInsetsDirectional.all(10), + child: CustomTextLabel( + text: category.categoryName!, + textStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.normal, color: Colors.white), + ), + ), + ), + ), + ], + ), + )); + } + + nxtBtn() { + return BlocConsumer( + bloc: context.read(), + listener: (context, state) { + if (state is SetUserPrefCatFetchSuccess) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'preferenceSave'), context); + if (widget.from == 2) { + Navigator.of(context).pushReplacementNamed(Routes.home, arguments: false); + } else { + Navigator.pop(context); + } + } + }, + builder: (context, state) { + return Padding( + padding: const EdgeInsets.only(top: 30.0), + child: InkWell( + child: Container( + height: 45.0, + width: MediaQuery.of(context).size.width * 0.95, + alignment: Alignment.center, + decoration: BoxDecoration(color: Theme.of(context).primaryColor, borderRadius: BorderRadius.circular(15.0)), + child: (state is SetUserPrefCatFetchInProgress) + ? UiUtils.showCircularProgress(true, secondaryColor) + : CustomTextLabel( + //@Start + text: (widget.from == 2) ? 'nxt' : 'saveLbl', + //from Settings + textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: secondaryColor, fontWeight: FontWeight.w600, fontSize: 18), + ), + ), + onTap: () async { + if (context.read().getUserId() != "0") { + if (selectedChoices.isEmpty) { + //no preference selected + Navigator.of(context).pushNamedAndRemoveUntil(Routes.home, (Route route) => false); + return; + } else if (selectedChoices.length == 1) { + setState(() { + selCatId = selectedChoices.join(); + }); + } else { + setState(() { + selCatId = selectedChoices.join(','); + }); + } + context.read().setUserPrefCat(catId: selCatId); + } else { + UiUtils.loginRequired(context); + } + }), + ); + }); + } +} diff --git a/news-app/lib/ui/screens/NewsDetail/NewsDetailScreen.dart b/news-app/lib/ui/screens/NewsDetail/NewsDetailScreen.dart new file mode 100644 index 00000000..8b542351 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/NewsDetailScreen.dart @@ -0,0 +1,135 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/utils/internetConnectivity.dart'; +import 'package:unity_ads_plugin/unity_ads_plugin.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/InterstitialAds/googleInterstitialAds.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/InterstitialAds/unityInterstitialAds.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/RerwardAds/googleRewardAds.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/RerwardAds/unityRewardAds.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/NewsSubDetailsScreen.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; +import 'package:news/data/models/BreakingNewsModel.dart'; + +class NewsDetailScreen extends StatefulWidget { + final NewsModel? model; + final List? newsList; + final BreakingNewsModel? breakModel; + final List? breakNewsList; + final bool isFromBreak; + final bool fromShowMore; + final String? slug; + + const NewsDetailScreen({super.key, this.model, this.breakModel, this.breakNewsList, this.newsList, this.slug, required this.isFromBreak, required this.fromShowMore}); + + @override + NewsDetailsState createState() => NewsDetailsState(); + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute( + builder: (_) => NewsDetailScreen( + model: arguments['model'], + breakModel: arguments['breakModel'], + breakNewsList: arguments['breakNewsList'], + newsList: arguments['newsList'], + isFromBreak: arguments['isFromBreak'], + fromShowMore: arguments['fromShowMore'], + slug: arguments['slug'])); + } +} + +class NewsDetailsState extends State { + final PageController pageController = PageController(); + + bool isScrollLocked = false; + + void _setScrollLock(bool lock) { + setState(() { + isScrollLocked = lock; + }); + } + + @override + void initState() { + if (context.read().getInAppAdsMode() == "1") { + if (context.read().checkAdsType() == "google") { + createGoogleInterstitialAd(context); + createGoogleRewardedAd(context); + } else { + if (context.read().unityGameId() != null) { + UnityAds.init( + gameId: context.read().unityGameId()!, + testMode: true, //set it to False @Deployement + onComplete: () { + loadUnityInterAd(context.read().interstitialId()!); + loadUnityRewardAd(context.read().rewardId()!); + }, + onFailed: (error, message) => debugPrint('Initialization Failed: $error $message'), + ); + } + } + } + + super.initState(); + } + + Widget showBreakingNews() { + return PageView.builder( + controller: pageController, + onPageChanged: (index) async { + if (await InternetConnectivity.isNetworkAvailable()) { + if (index % rewardAdsIndex == 0) showRewardAds(); + if (index % interstitialAdsIndex == 0) UiUtils.showInterstitialAds(context: context); + } + }, + itemCount: (widget.breakNewsList == null || widget.breakNewsList!.isEmpty) ? 1 : widget.breakNewsList!.length + 1, + itemBuilder: (context, index) { + return NewsSubDetails( + onLockScroll: (p0) {}, //no change for breaking news , No comments widget + breakModel: (index == 0) ? widget.breakModel : widget.breakNewsList![index - 1], + fromShowMore: widget.fromShowMore, + isFromBreak: widget.isFromBreak, + model: widget.model); + }); + } + + void showRewardAds() { + if (context.read().getInAppAdsMode() == "1") { + if (context.read().checkAdsType() == "google") { + showGoogleRewardedAd(context); + } else { + showUnityRewardAds(context.read().rewardId()!); + } + } + } + + Widget showNews() { + return PageView.builder( + controller: pageController, + physics: isScrollLocked ? NeverScrollableScrollPhysics() : AlwaysScrollableScrollPhysics(), + onPageChanged: (index) async { + if (await InternetConnectivity.isNetworkAvailable()) { + if (index % rewardAdsIndex == 0) showRewardAds(); + if (index % interstitialAdsIndex == 0) UiUtils.showInterstitialAds(context: context); + } + }, + itemCount: (widget.newsList == null || widget.newsList!.isEmpty) ? 1 : widget.newsList!.length + 1, + itemBuilder: (context, index) { + return NewsSubDetails( + onLockScroll: _setScrollLock, + model: (index == 0) ? widget.model : widget.newsList![index - 1], + fromShowMore: widget.fromShowMore, + isFromBreak: widget.isFromBreak, + breakModel: widget.breakModel); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold(backgroundColor: UiUtils.getColorScheme(context).secondary, body: widget.isFromBreak ? showBreakingNews() : showNews()); + } +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/CommentView.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/CommentView.dart new file mode 100644 index 00000000..41531e10 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/CommentView.dart @@ -0,0 +1,318 @@ +import 'package:news/data/repositories/NewsComment/LikeAndDislikeComment/likeAndDislikeCommRepository.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/delAndReportCom.dart'; +import 'package:news/cubits/NewsComment/likeAndDislikeCommCubit.dart'; +import 'package:news/cubits/NewsComment/setCommentCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/data/models/CommentModel.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/cubits/commentNewsCubit.dart'; +import 'package:news/utils/ErrorMessageKeys.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:flutter/material.dart'; + +class CommentView extends StatefulWidget { + final String newsId; + final Function updateComFun; + final Function updateIsReplyFun; + + const CommentView({super.key, required this.newsId, required this.updateComFun, required this.updateIsReplyFun}); + + @override + CommentViewState createState() => CommentViewState(); +} + +class CommentViewState extends State { + final TextEditingController _commentC = TextEditingController(); + TextEditingController reportC = TextEditingController(); + bool comBtnEnabled = false, isReply = false, isSending = false; + int? replyComIndex; + + Widget commentsLengthView(int length) { + return Row(children: [ + if (length > 0) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Row(children: [ + CustomTextLabel( + text: 'allLbl', + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6), fontSize: 12.0, fontWeight: FontWeight.w600)), + CustomTextLabel( + text: " $length ", + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6), fontSize: 12.0, fontWeight: FontWeight.w600)), + CustomTextLabel( + text: 'comsLbl', + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6), fontSize: 12.0, fontWeight: FontWeight.w600)), + ])), + const Spacer(), + Align( + alignment: Alignment.topRight, + child: InkWell( + child: const Icon(Icons.close_rounded), + onTap: () { + widget.updateComFun(false); + }, + )) + ]); + } + + Widget profileWithSendCom() { + return Padding( + padding: const EdgeInsetsDirectional.only(top: 5.0), + child: Row(children: [ + Expanded( + flex: 1, + child: BlocBuilder(builder: (context, state) { + if (state is Authenticated && context.read().getProfile() != "") { + return CircleAvatar(backgroundImage: NetworkImage(context.read().getProfile())); + } else { + return UiUtils.setFixedSizeboxForProfilePicture(childWidget: const Icon(Icons.account_circle, size: 35)); + } + })), + BlocListener( + bloc: context.read(), + listener: (context, state) { + if (state is SetCommentFetchSuccess) { + context.read().commentUpdateList(state.setComment, state.total); + FocusScopeNode currentFocus = FocusScope.of(context); + if (!currentFocus.hasPrimaryFocus) { + currentFocus.unfocus(); + } + _commentC.clear(); + isSending = false; + setState(() {}); + } + if (state is SetCommentFetchInProgress) { + setState(() => isSending = true); + } + }, + child: Expanded( + flex: 7, + child: Padding( + padding: const EdgeInsetsDirectional.only(start: 18.0), + child: TextField( + controller: _commentC, + style: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)), + onChanged: (String val) { + (_commentC.text.trim().isNotEmpty) ? setState(() => comBtnEnabled = true) : setState(() => comBtnEnabled = false); + }, + keyboardType: TextInputType.multiline, + maxLines: null, + decoration: InputDecoration( + contentPadding: const EdgeInsets.only(top: 10.0, bottom: 2.0), + isDense: true, + suffixIconConstraints: BoxConstraints(maxHeight: (!isSending) ? 35 : 0, maxWidth: (!isSending) ? 30 : 0), + enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5), width: 1.5)), + hintText: UiUtils.getTranslatedLabel(context, 'shareThoghtLbl'), + hintStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)), + suffixIcon: (_commentC.text.trim().isNotEmpty) + ? (!isSending) + ? IconButton( + icon: Icon(Icons.send, color: comBtnEnabled ? UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8) : Colors.transparent, size: 20.0), + onPressed: () async { + (context.read().getUserId() != "0") + ? context.read().setComment(parentId: "0", newsId: widget.newsId, message: _commentC.text) + : UiUtils.loginRequired(context); + }) + : SizedBox(height: 12, width: 12, child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)) + : SizedBox.shrink()))))) + ])); + } + + _buildCommContainer({required CommentModel model, required int index, required int totalCurrentComm, required bool hasMoreCommFetchError, required bool hasMore}) { + model = model; + if (index == totalCurrentComm - 1 && index <= 0) { + //check if hasMore + if (hasMore) { + if (hasMoreCommFetchError) { + return const SizedBox.shrink(); + } else { + return Center(child: Padding(padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0), child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor))); + } + } + } + return Builder(builder: (context) { + return Row(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ + (model.profile != null && model.profile != "") + ? UiUtils.setFixedSizeboxForProfilePicture( + childWidget: CircleAvatar(backgroundImage: (model.profile != null) ? NetworkImage(model.profile!) : NetworkImage(const Icon(Icons.account_circle, size: 35) as String), radius: 32)) + : UiUtils.setFixedSizeboxForProfilePicture(childWidget: const Icon(Icons.account_circle, size: 35)), + Expanded( + child: Padding( + padding: const EdgeInsetsDirectional.only(start: 15.0), + child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ + Row(children: [ + CustomTextLabel( + text: model.name!, textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontSize: 13)), + Padding(padding: const EdgeInsetsDirectional.only(start: 10.0), child: Icon(Icons.circle, size: 4.0, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7))), + Padding( + padding: const EdgeInsetsDirectional.only(start: 10.0), + child: CustomTextLabel( + text: UiUtils.convertToAgo(context, DateTime.parse(model.date!), 1)!, + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontSize: 10), + )) + ]), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: CustomTextLabel( + text: model.message!, textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.normal))), + BlocBuilder(builder: (context, state) { + return Padding( + padding: const EdgeInsets.only(top: 15.0), + child: Row( + children: [ + GestureDetector( + child: const Icon(Icons.thumb_up_off_alt_rounded), + onTap: () { + if (context.read().getUserId() != "0") { + context + .read() + .setLikeAndDislikeComm(langId: context.read().state.id, commId: model.id!, status: (model.like == "1") ? "0" : "1", fromLike: true); + } else { + UiUtils.loginRequired(context); + } + }), + model.totalLikes! != "0" + ? Padding( + padding: const EdgeInsetsDirectional.only(start: 4.0), + child: CustomTextLabel(text: model.totalLikes!, textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer))) + : const SizedBox(width: 12), + Padding( + padding: const EdgeInsetsDirectional.only(start: 35), + child: InkWell( + child: const Icon(Icons.thumb_down_alt_rounded), + onTap: () { + if (context.read().getUserId() != "0") { + context + .read() + .setLikeAndDislikeComm(langId: context.read().state.id, commId: model.id!, status: (model.dislike == "1") ? "0" : "2", fromLike: false); + } else { + UiUtils.loginRequired(context); + } + }, + )), + model.totalDislikes! != "0" + ? Padding( + padding: const EdgeInsetsDirectional.only(start: 4.0), + child: + CustomTextLabel(text: model.totalDislikes!, textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer))) + : const SizedBox(width: 12), + Padding( + padding: const EdgeInsetsDirectional.only(start: 35), + child: InkWell( + child: const Icon(Icons.quickreply_rounded), + onTap: () { + widget.updateIsReplyFun(true, index); + setState(() { + isReply = true; + replyComIndex = index; + }); + }, + )), + model.replyComList!.isNotEmpty + ? Padding( + padding: const EdgeInsetsDirectional.only(start: 5.0), + child: CustomTextLabel( + text: model.replyComList!.length.toString(), + textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer))) + : const SizedBox.shrink(), + const Spacer(), + if (context.read().getUserId() != "0") + InkWell( + child: Icon(Icons.more_vert_outlined, color: UiUtils.getColorScheme(context).primaryContainer, size: 17), + onTap: () => delAndReportCom(index: index, newsId: widget.newsId, context: context, model: model, reportC: reportC, setState: setState)) + ], + ), + ); + }), + Padding( + padding: const EdgeInsets.only(top: 10.0), + child: InkWell( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Row(children: [ + CustomTextLabel( + text: model.replyComList!.isNotEmpty ? "${model.replyComList!.length} " : "", + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: Theme.of(context).primaryColor, fontSize: 12, fontWeight: FontWeight.w600)), + CustomTextLabel( + text: model.replyComList!.isNotEmpty ? ((model.replyComList!.length == 1) ? 'replyLbl' : 'repliesLbl') : "", + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: Theme.of(context).primaryColor, fontSize: 12, fontWeight: FontWeight.w600)) + ])), + onTap: () { + widget.updateIsReplyFun(true, index); + setState(() { + isReply = true; + replyComIndex = index; + }); + })) + ]))), + ]); + }); + } + + Widget allComListView(CommentNewsFetchSuccess state) { + return BlocListener( + listener: (context, likeDislikeState) { + if (likeDislikeState is LikeAndDislikeCommSuccess) { + final defaultIndex = state.commentNews.indexWhere((element) => element.id == likeDislikeState.comment.id); + if (defaultIndex != -1) { + state.commentNews[defaultIndex] = likeDislikeState.comment; + context.read().emitSuccessState(state.commentNews); + } + } + }, + child: Padding( + padding: const EdgeInsets.only(top: 20.0), + child: ListView.separated( + separatorBuilder: (BuildContext context, int index) => Divider(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5)), + shrinkWrap: true, + primary: false, + padding: const EdgeInsets.only(top: 20.0), + physics: const NeverScrollableScrollPhysics(), + itemCount: state.commentNews.length, + itemBuilder: (context, index) { + return _buildCommContainer( + model: state.commentNews[index], + hasMore: state.hasMore, + hasMoreCommFetchError: (state).hasMoreFetchError, + index: index, + totalCurrentComm: (state.commentNews[index].replyComList!.length + state.commentNews.length), + ); + })), + ); + } + + Widget commentView() { + return BlocBuilder(builder: (context, state) { + if (state is CommentNewsFetchInProgress || state is CommentNewsInitial) { + return Center(child: UiUtils.showCircularProgress(true, UiUtils.getColorScheme(context).primaryContainer)); + } + return Padding( + padding: const EdgeInsetsDirectional.only(top: 10.0, bottom: 10.0), + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + if (state is CommentNewsFetchSuccess) commentsLengthView((state).commentNews.length), + if (state is! CommentNewsFetchSuccess) + Row(children: [const Spacer(), Align(alignment: Alignment.topRight, child: InkWell(child: const Icon(Icons.close_rounded), onTap: () => widget.updateComFun(false)))]), + if ((state is CommentNewsFetchFailure && !state.errorMessage.contains(ErrorMessageKeys.noInternet) || state is CommentNewsFetchSuccess)) profileWithSendCom(), + if (state is CommentNewsFetchFailure) + SizedBox( + height: MediaQuery.of(context).size.height / 2, + child: Center( + child: CustomTextLabel( + text: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) + ? UiUtils.getTranslatedLabel(context, 'internetmsg') + : (state.errorMessage == "No Data Found") + ? UiUtils.getTranslatedLabel(context, 'noComments') + : state.errorMessage, + textAlign: TextAlign.center))), + if (state is CommentNewsFetchSuccess) allComListView(state) + ])); + }); + } + + @override + Widget build(BuildContext context) { + return BlocProvider(create: (context) => LikeAndDislikeCommCubit(LikeAndDislikeCommRepository()), child: commentView()); + } +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/ImageView.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/ImageView.dart new file mode 100644 index 00000000..605afcd1 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/ImageView.dart @@ -0,0 +1,98 @@ +import 'package:flutter/material.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import '../../../../app/routes.dart'; +import '../../../../data/models/BreakingNewsModel.dart'; +import '../../../../data/models/NewsModel.dart'; + +class ImageView extends StatefulWidget { + final NewsModel? model; + final BreakingNewsModel? breakModel; + final bool isFromBreak; + + const ImageView({super.key, this.model, this.breakModel, required this.isFromBreak}); + + @override + ImageViewState createState() => ImageViewState(); +} + +class ImageViewState extends State { + List allImage = []; + int _curSlider = 0; + + @override + void initState() { + allImage.clear(); + if (!widget.isFromBreak) { + allImage.add(widget.model!.image!); + if (widget.model!.imageDataList!.isNotEmpty) { + for (int i = 0; i < widget.model!.imageDataList!.length; i++) { + allImage.add(widget.model!.imageDataList![i].otherImage!); + } + } + } else { + allImage.add(widget.breakModel!.image!); + } + super.initState(); + } + + Widget imageView() { + return SizedBox( + height: MediaQuery.of(context).size.height * 0.40, + width: double.maxFinite, + child: !widget.isFromBreak + ? PageView.builder( + itemCount: allImage.length, + onPageChanged: (index) { + setState(() { + _curSlider = index; + }); + }, + itemBuilder: (BuildContext context, int index) { + return InkWell( + child: CustomNetworkImage(networkImageUrl: allImage[index], width: double.infinity, height: MediaQuery.of(context).size.height * 0.42, isVideo: false, fit: BoxFit.cover), + onTap: () { + Navigator.of(context).pushNamed(Routes.imagePreview, arguments: {"index": index, "imgList": allImage}); + }, + ); + }) + : CustomNetworkImage(networkImageUrl: widget.breakModel!.image!, width: double.infinity, height: MediaQuery.of(context).size.height * 0.42, isVideo: false, fit: BoxFit.cover)); + } + + List map(List list, Function handler) { + List result = []; + for (var i = 0; i < list.length; i++) { + result.add(handler(i, list[i])); + } + + return result; + } + + Widget imageSliderDot() { + if (!widget.isFromBreak && allImage.length > 1) { + return Align( + alignment: Alignment.bottomCenter, + child: Container( + margin: EdgeInsets.only(top: MediaQuery.of(context).size.height / 2.6 - 32), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: map(allImage, (index, url) { + return Container( + width: _curSlider == index ? 10.0 : 8.0, + height: _curSlider == index ? 10.0 : 8.0, + margin: const EdgeInsets.symmetric(horizontal: 1.0), + decoration: BoxDecoration( + shape: BoxShape.circle, + boxShadow: (_curSlider == index) ? const [BoxShadow(color: Colors.grey, spreadRadius: 0.0, blurRadius: 2.0)] : [], + color: _curSlider == index ? secondaryColor : secondaryColor.withOpacity((0.5)))); + })))); + } else { + return const SizedBox.shrink(); + } + } + + @override + Widget build(BuildContext context) { + return Stack(children: [imageView(), imageSliderDot()]); + } +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/InterstitialAds/googleInterstitialAds.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/InterstitialAds/googleInterstitialAds.dart new file mode 100644 index 00000000..28b44e21 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/InterstitialAds/googleInterstitialAds.dart @@ -0,0 +1,56 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; + +import 'package:news/cubits/appSystemSettingCubit.dart'; + +const AdRequest request = AdRequest( + //static + keywords: ['foo', 'bar'], + contentUrl: 'http://foo.com/bar.html', + nonPersonalizedAds: true, +); +int maxFailedLoadAttempts = 3; +InterstitialAd? _interstitialAd; +int _numInterstitialLoadAttempts = 0; + +void createGoogleInterstitialAd(BuildContext context) { + if (context.read().interstitialId() != "") { + InterstitialAd.load( + adUnitId: context.read().interstitialId()!, + request: request, + adLoadCallback: InterstitialAdLoadCallback( + onAdLoaded: (InterstitialAd ad) { + _interstitialAd = ad; + _numInterstitialLoadAttempts = 0; + _interstitialAd!.setImmersiveMode(true); + }, + onAdFailedToLoad: (LoadAdError error) { + _numInterstitialLoadAttempts += 1; + _interstitialAd = null; + if (_numInterstitialLoadAttempts <= maxFailedLoadAttempts) { + createGoogleInterstitialAd(context); + } + }, + )); + } +} + +void showGoogleInterstitialAd(BuildContext context) { + if (_interstitialAd == null) { + return; + } + _interstitialAd!.fullScreenContentCallback = FullScreenContentCallback( + onAdShowedFullScreenContent: (InterstitialAd ad) => debugPrint('ad onAdShowedFullScreenContent.'), + onAdDismissedFullScreenContent: (InterstitialAd ad) { + ad.dispose(); + createGoogleInterstitialAd(context); + }, + onAdFailedToShowFullScreenContent: (InterstitialAd ad, AdError error) { + ad.dispose(); + createGoogleInterstitialAd(context); + }, + ); + _interstitialAd!.show(); + _interstitialAd = null; +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/InterstitialAds/unityInterstitialAds.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/InterstitialAds/unityInterstitialAds.dart new file mode 100644 index 00000000..62c63a95 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/InterstitialAds/unityInterstitialAds.dart @@ -0,0 +1,23 @@ +import 'package:flutter/cupertino.dart'; +import 'package:unity_ads_plugin/unity_ads_plugin.dart'; + +void loadUnityInterAd(String placementId) { + UnityAds.load(placementId: placementId, onComplete: (placementId) {}, onFailed: (placementId, error, message) {}); +} + +void showUnityInterstitialAds(String placementId) { + UnityAds.showVideoAd( + placementId: placementId, + onComplete: (placementId) { + loadUnityInterAd(placementId); + }, + onFailed: (placementId, error, message) { + loadUnityInterAd(placementId); + }, + onStart: (placementId) => debugPrint('Video Ad $placementId started'), + onClick: (placementId) => debugPrint('Video Ad $placementId click'), + onSkipped: (placementId) { + loadUnityInterAd(placementId); + }, + ); +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/NewsSubDetailsScreen.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/NewsSubDetailsScreen.dart new file mode 100644 index 00000000..6fff766e --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/NewsSubDetailsScreen.dart @@ -0,0 +1,417 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_tts/flutter_tts.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/adSpacesNewsDetailsCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/commentNewsCubit.dart'; +import 'package:news/cubits/relatedNewsCubit.dart'; +import 'package:news/cubits/setNewsViewsCubit.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/ui/widgets/adSpaces.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:unity_ads_plugin/unity_ads_plugin.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/ImageView.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/horizontalBtnList.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/relatedNewsList.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/setBannderAds.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/tagView.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/titleView.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/videoBtn.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/CommentView.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/ReplyCommentView.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/backBtn.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/dateView.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/descView.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/likeBtn.dart'; + +class NewsSubDetails extends StatefulWidget { + final NewsModel? model; + final BreakingNewsModel? breakModel; + final bool fromShowMore; + final bool isFromBreak; + final Function(bool) onLockScroll; + + const NewsSubDetails({super.key, this.model, this.breakModel, required this.fromShowMore, required this.isFromBreak, required this.onLockScroll}); + + @override + NewsSubDetailsState createState() => NewsSubDetailsState(); +} + +class NewsSubDetailsState extends State { + bool comEnabled = false; + bool isReply = false; + int? replyComIndex; + int fontValue = 15; + bool isPlaying = false; + double volume = 0.5; + double pitch = 1.0; + double rate = 0.5; + BannerAd? _bannerAd; + NewsModel? newsModel; + FlutterTts? _flutterTts; + bool _isScrollingUp = false; + late final ScrollController controller = ScrollController()..addListener(hasMoreCommScrollListener); + + @override + void initState() { + super.initState(); + newsModel = widget.model; + getComments(); + getRelatedNews(); + initializeTts(); + setNewsViews(isBreakingNews: (widget.isFromBreak) ? true : false); + if (context.read().getInAppAdsMode() == "1") bannerAdsInitialized(); + Future.delayed(Duration.zero, () { + context.read().getAdspaceForNewsDetails(langId: context.read().state.id); + }); + } + + setNewsViews({required bool isBreakingNews}) { + Future.delayed(Duration.zero, () { + context.read().setNewsViews(newsId: isBreakingNews ? widget.breakModel!.id! : (newsModel!.newsId ?? newsModel!.id)!, isBreakingNews: isBreakingNews); + }); + } + + getComments() { + if (!widget.isFromBreak && context.read().getCommentsMode() == "1") { + Future.delayed(Duration.zero, () { + context.read().getCommentNews(newsId: (newsModel!.newsId != null && newsModel!.newsId!.trim().isNotEmpty) ? newsModel!.newsId! : newsModel!.id!); + }); + } + } + + getRelatedNews() { + if (!widget.isFromBreak) { + Future.delayed(Duration.zero, () { + context.read().getRelatedNews( + langId: context.read().state.id, + catId: (newsModel!.categoryId == "0" || newsModel!.categoryId == '') ? newsModel!.categoryId : null, + subCatId: (newsModel!.subCatId != "0" || newsModel!.subCatId != '') ? newsModel!.subCatId : null); + }); + } + } + + @override + void dispose() { + _flutterTts!.stop(); + controller.removeListener(hasMoreCommScrollListener); + controller.dispose(); + super.dispose(); + } + + updateFontVal(int fontVal) { + setState(() => fontValue = fontVal); + } + + initializeTts() { + _flutterTts = FlutterTts(); + _flutterTts!.awaitSpeakCompletion(true); + _flutterTts!.setStartHandler(() async { + if (mounted) { + setState(() => isPlaying = true); + } + }); + + _flutterTts!.setCompletionHandler(() { + if (mounted) { + setState(() => isPlaying = false); + } + }); + + _flutterTts!.setErrorHandler((err) { + if (mounted) { + setState(() => isPlaying = false); + } + }); + } + + bannerAdsInitialized() { + if (context.read().checkAdsType() == "unity") { + UnityAds.init( + gameId: context.read().unityGameId()!, + testMode: true, //set it to false @Deployment + onComplete: () {}, + onFailed: (error, message) {}); + } + + if (context.read().checkAdsType() == "google") { + _createBottomBannerAd(); + } + } + + void _createBottomBannerAd() { + if (context.read().bannerId() != "") { + _bannerAd = BannerAd( + adUnitId: context.read().bannerId()!, + request: const AdRequest(), + size: AdSize.banner, + listener: BannerAdListener( + onAdLoaded: (_) {}, + onAdFailedToLoad: (ad, err) { + ad.dispose(); + }, + ), + ); + + _bannerAd!.load(); + } + } + + speak(String description) async { + if (description.isNotEmpty) { + await _flutterTts!.setVolume(volume); + await _flutterTts!.setSpeechRate(rate); + await _flutterTts!.setPitch(pitch); + await _flutterTts!.setLanguage(() { + return context.read().state.languageCode; + }()); + int length = description.length; + + if (length < 4000) { + setState(() => isPlaying = true); + await _flutterTts!.speak(description); + _flutterTts!.setCompletionHandler(() { + setState(() { + _flutterTts!.stop(); + isPlaying = false; + }); + }); + } else if (length < 8000) { + if (Platform.isAndroid) await _flutterTts!.setQueueMode(1); + String temp1 = description.substring(0, length ~/ 2); + await _flutterTts!.speak(temp1); + _flutterTts!.setCompletionHandler(() { + setState(() { + isPlaying = true; + }); + }); + String temp2 = description.substring(temp1.length, description.length); + await _flutterTts!.speak(temp2); + _flutterTts!.setCompletionHandler(() { + setState(() { + isPlaying = false; + }); + }); + } else if (length < 12000) { + if (Platform.isAndroid) await _flutterTts!.setQueueMode(2); + String temp1 = description.substring(0, 3999); + await _flutterTts!.speak(temp1); + _flutterTts!.setCompletionHandler(() { + setState(() { + isPlaying = true; + }); + }); + String temp2 = description.substring(temp1.length, 7999); + await _flutterTts!.speak(temp2); + _flutterTts!.setCompletionHandler(() { + setState(() {}); + }); + String temp3 = description.substring(temp2.length, description.length); + await _flutterTts!.speak(temp3); + _flutterTts!.setCompletionHandler(() { + setState(() { + isPlaying = false; + }); + }); + } else { + String temp = description.substring(0, description.length); + await _flutterTts!.speak(temp); + _flutterTts!.setCompletionHandler(() { + setState(() { + isPlaying = false; + }); + }); + } + } + } + + stop() async { + var result = await _flutterTts!.stop(); + if (result == 1) setState(() => isPlaying = false); + } + + onBackPress(bool isTrue) { + (widget.fromShowMore == true) ? Navigator.of(context).popUntil((route) => route.isFirst) : Navigator.pop(context); + } + + Widget showViews() { + return Row(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ + Icon(Icons.remove_red_eye_rounded, size: 17, color: UiUtils.getColorScheme(context).primaryContainer), + const SizedBox(width: 5), + Padding( + padding: const EdgeInsets.only(bottom: 2), + child: CustomTextLabel( + text: ((!widget.isFromBreak) ? newsModel!.totalViews : widget.breakModel!.totalViews) ?? '0', + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600), + textAlign: TextAlign.center)) + ]); + } + + Widget showAuthor() { + return Padding( + padding: const EdgeInsets.only(top: 5, left: 5, right: 5), + child: Container( + child: InkWell( + onTap: () { + Navigator.of(context).pushNamed(Routes.authorDetails, arguments: {"authorId": newsModel!.userAthorDetails!.id ?? "0"}); + }, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + CircleAvatar(backgroundImage: NetworkImage(newsModel!.userAthorDetails!.profile!)), + SizedBox(width: 12), + Expanded( + child: CustomTextLabel( + text: newsModel!.userAthorDetails!.name ?? '', + textAlign: TextAlign.left, + textStyle: TextStyle( + color: UiUtils.getColorScheme(context).primaryContainer, + fontWeight: FontWeight.w600, // SemiBold + fontSize: 16, + ), + ), + ) + ], + ))), + ); + } + + otherMainDetails() { + int readingTime = UiUtils.calculateReadingTime(widget.isFromBreak ? widget.breakModel!.desc! : newsModel!.desc!); + String minutesPostfix = (readingTime == 1) ? UiUtils.getTranslatedLabel(context, 'minute') : UiUtils.getTranslatedLabel(context, 'minutes'); + return Padding( + padding: EdgeInsets.only(top: MediaQuery.of(context).size.height / 2.7), + child: Container( + padding: const EdgeInsetsDirectional.only(top: 20.0, start: 20.0, end: 20.0), + width: double.maxFinite, + decoration: BoxDecoration(borderRadius: const BorderRadius.only(topLeft: Radius.circular(25), topRight: Radius.circular(25)), color: UiUtils.getColorScheme(context).secondary), + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + allRowBtn( + isFromBreak: widget.isFromBreak, + context: context, + breakModel: widget.isFromBreak ? widget.breakModel : null, + model: !widget.isFromBreak ? newsModel! : null, + fontVal: fontValue, + updateFont: updateFontVal, + isPlaying: isPlaying, + speak: speak, + stop: stop, + updateComEnabled: updateCommentshow), + if (newsModel!.authorDetails != null) showAuthor(), + BlocBuilder( + builder: (context, state) { + return (state is AdSpacesNewsDetailsFetchSuccess && state.adSpaceTopData != null) ? AdSpaces(adsModel: state.adSpaceTopData!) : const SizedBox.shrink(); + }, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (!widget.isFromBreak) tagView(model: newsModel!, context: context, isFromDetailsScreen: true), + if (!isReply && !comEnabled) + Padding( + padding: const EdgeInsetsDirectional.only(top: 8.0), + child: Row(mainAxisSize: MainAxisSize.min, children: [ + if (!widget.isFromBreak) dateView(context, newsModel!.publishDate ?? newsModel!.date!), + if (!widget.isFromBreak) const SizedBox(width: 20), + showViews(), + const SizedBox(width: 20), + Icon(Icons.circle, size: 10, color: UiUtils.getColorScheme(context).primaryContainer), + const SizedBox(width: 10), + CustomTextLabel( + text: "$readingTime $minutesPostfix ${UiUtils.getTranslatedLabel(context, 'read')}", + textStyle: + Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), fontSize: 12.0, fontWeight: FontWeight.w400)) + ])), + if (!isReply && !comEnabled) titleView(title: widget.isFromBreak ? widget.breakModel!.title! : newsModel!.title!, context: context), + if (!isReply && !comEnabled) descView(desc: widget.isFromBreak ? widget.breakModel!.desc! : newsModel!.desc!, context: context, fontValue: fontValue.toDouble()), + ], + ), + if (!widget.isFromBreak && !isReply && comEnabled) CommentView(newsId: newsModel!.id!, updateComFun: updateCommentshow, updateIsReplyFun: updateComReply), + if (!widget.isFromBreak && isReply && comEnabled) ReplyCommentView(replyComIndex: replyComIndex!, replyComFun: updateComReply, newsId: newsModel!.id!), + BlocBuilder( + builder: (context, state) { + return (state is AdSpacesNewsDetailsFetchSuccess && state.adSpaceBottomData != null) + ? Padding(padding: const EdgeInsetsDirectional.only(bottom: 5), child: AdSpaces(adsModel: state.adSpaceBottomData!)) + : const SizedBox.shrink(); + }, + ), + if (!widget.isFromBreak && !isReply && !comEnabled && newsModel != null) RelatedNewsList(model: newsModel!), + ]), + )); + } + + updateCommentshow(bool comEnabledUpdate) { + setState(() { + comEnabled = comEnabledUpdate; + widget.onLockScroll(comEnabledUpdate); + }); + } + + updateComReply(bool comReplyUpdate, int comIndex) { + setState(() { + isReply = comReplyUpdate; + replyComIndex = comIndex; + widget.onLockScroll(false); + }); + } + + void hasMoreCommScrollListener() { + if (!widget.isFromBreak && comEnabled && !isReply) { + if (controller.position.maxScrollExtent == controller.offset) { + if (context.read().hasMoreCommentNews()) { + context.read().getMoreCommentNews(newsId: (newsModel!.newsId != null && newsModel!.newsId!.trim().isNotEmpty) ? newsModel!.newsId! : newsModel!.id!); + } else {} + } + } + //for comments area + if (controller.position.userScrollDirection == ScrollDirection.forward) { + // User is scrolling up + if (!_isScrollingUp) { + setState(() { + _isScrollingUp = true; + }); + } + } else if (controller.position.userScrollDirection == ScrollDirection.reverse) { + // User is scrolling down + if (_isScrollingUp) { + setState(() { + _isScrollingUp = false; + }); + } + } + } + + @override + Widget build(BuildContext context) { + return PopScope( + onPopInvoked: (bool isTrue) => onBackPress, + child: AnimatedPadding( + duration: Duration(milliseconds: 300), + padding: EdgeInsets.only(top: _isScrollingUp ? MediaQuery.of(context).viewPadding.top : 0), + child: Column(children: [ + Expanded( + flex: (Platform.isAndroid) ? 12 : 9, + child: SingleChildScrollView( + controller: !widget.isFromBreak && comEnabled && !isReply ? controller : null, + child: Stack(children: [ + ImageView(isFromBreak: widget.isFromBreak, model: newsModel, breakModel: widget.breakModel), + backBtn(context, widget.fromShowMore), + videoBtn(context: context, isFromBreak: widget.isFromBreak, model: !widget.isFromBreak ? newsModel! : null, breakModel: widget.isFromBreak ? widget.breakModel! : null), + otherMainDetails(), + if (!widget.isFromBreak) likeBtn(context, newsModel!), + ])), + ), + if ((context.read().getInAppAdsMode() == "1") || _bannerAd != null) Flexible(child: setBannerAd(context, _bannerAd)) + ]), + ), + ); + } +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/ReplyCommentView.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/ReplyCommentView.dart new file mode 100644 index 00000000..31c022a8 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/ReplyCommentView.dart @@ -0,0 +1,324 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/data/models/CommentModel.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/delAndReportReplyComm.dart'; +import 'package:news/ui/styles/colors.dart'; + +import 'package:news/ui/widgets/customTextLabel.dart'; + +import 'package:news/utils/uiUtils.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/cubits/NewsComment/likeAndDislikeCommCubit.dart'; +import 'package:news/cubits/NewsComment/setCommentCubit.dart'; +import 'package:news/cubits/commentNewsCubit.dart'; +import 'package:news/data/repositories/NewsComment/LikeAndDislikeComment/likeAndDislikeCommRepository.dart'; + +class ReplyCommentView extends StatefulWidget { + final int replyComIndex; + final Function replyComFun; + final String newsId; + + const ReplyCommentView({super.key, required this.replyComIndex, required this.replyComFun, required this.newsId}); + + @override + ReplyCommentViewState createState() => ReplyCommentViewState(); +} + +class ReplyCommentViewState extends State { + bool isReply = false, replyComEnabled = false, isSending = false; + final TextEditingController _replyComC = TextEditingController(); + TextEditingController reportC = TextEditingController(); + + Widget allReplyComView(CommentModel model) { + return Row(children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Row(children: [ + CustomTextLabel( + text: 'allLbl', + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6), fontSize: 12.0, fontWeight: FontWeight.w600)), + CustomTextLabel( + text: " ${model.replyComList!.length} ", + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6), fontSize: 12.0, fontWeight: FontWeight.w600)), + CustomTextLabel( + text: (model.replyComList!.length == 1) ? 'replyLbl' : 'repliesLbl', + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6), fontSize: 12.0, fontWeight: FontWeight.w600)), + ])), + const Spacer(), + Align( + alignment: Alignment.topRight, + child: InkWell( + child: const Icon(Icons.close), + onTap: () { + setState(() => isReply = false); + widget.replyComFun(false, widget.replyComIndex); + })) + ]); + } + + replyComProfileWithCom(CommentModel modelCom) { + return BlocBuilder( + builder: (context, state) { + DateTime replyTime = DateTime.parse(modelCom.date!); + return Container( + decoration: BoxDecoration(border: Border.all(width: 2, color: borderColor)), + padding: const EdgeInsets.all(10), + child: Row(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ + modelCom.profile != null && modelCom.profile != "" + ? UiUtils.setFixedSizeboxForProfilePicture(childWidget: CircleAvatar(backgroundImage: NetworkImage(modelCom.profile!), radius: 32)) + : UiUtils.setFixedSizeboxForProfilePicture(childWidget: const Icon(Icons.account_circle, size: 35)), + Expanded( + child: Padding( + padding: const EdgeInsetsDirectional.only(start: 15.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + CustomTextLabel( + text: modelCom.name!, + textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontSize: 13)), + Padding( + padding: const EdgeInsetsDirectional.only(start: 10.0), + child: Icon(Icons.circle, size: 4.0, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7))), + Padding( + padding: const EdgeInsetsDirectional.only(start: 10.0), + child: CustomTextLabel( + text: UiUtils.convertToAgo(context, replyTime, 1)!, + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontSize: 10))) + ], + ), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: CustomTextLabel( + text: modelCom.message!, + textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.normal)), + ), + ], + ))), + ])); + }, + ); + } + + replyComSendReplyView(CommentModel model) { + return context.read().getUserId() != "0" + ? Padding( + padding: const EdgeInsetsDirectional.only(top: 10.0), + child: Row( + children: [ + Expanded( + flex: 1, + child: context.read().getProfile() != "" + ? UiUtils.setFixedSizeboxForProfilePicture(childWidget: CircleAvatar(backgroundImage: NetworkImage(context.read().getProfile()), radius: 32)) + : UiUtils.setFixedSizeboxForProfilePicture(childWidget: const Icon(Icons.account_circle, size: 35))), + BlocListener( + bloc: context.read(), + listener: (context, state) { + if (state is SetCommentFetchSuccess) { + context.read().commentUpdateList(state.setComment, state.total); + FocusScopeNode currentFocus = FocusScope.of(context); + if (!currentFocus.hasPrimaryFocus) { + currentFocus.unfocus(); + } + _replyComC.clear(); + isSending = false; + setState(() {}); + } + if (state is SetCommentFetchInProgress) { + setState(() => isSending = true); + } + }, + child: Expanded( + flex: 7, + child: Padding( + padding: const EdgeInsetsDirectional.only(start: 18.0), + child: TextField( + controller: _replyComC, + style: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)), + onChanged: (String val) { + if (_replyComC.text.trim().isNotEmpty) { + setState(() => replyComEnabled = true); + } else { + setState(() => replyComEnabled = false); + } + }, + keyboardType: TextInputType.multiline, + maxLines: null, + decoration: InputDecoration( + contentPadding: const EdgeInsets.only(top: 10.0, bottom: 2.0), + isDense: true, + enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5), width: 1.5)), + hintText: UiUtils.getTranslatedLabel(context, 'publicReply'), + hintStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)), + suffixIconConstraints: const BoxConstraints(maxHeight: 35, maxWidth: 30), + suffixIcon: (_replyComC.text.trim().isNotEmpty) + ? (!isSending) + ? IconButton( + icon: Icon(Icons.send, color: replyComEnabled ? UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8) : Colors.transparent, size: 20.0), + onPressed: () async { + if (context.read().getUserId() != "0") { + context.read().setComment(parentId: model.id!, newsId: widget.newsId, message: _replyComC.text); + } else { + UiUtils.loginRequired(context); + } + }, + ) + : SizedBox(height: 12, width: 12, child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)) + : SizedBox.shrink()), + )))) + ], + )) + : const SizedBox.shrink(); + } + + replyAllComListView(CommentModel model) { + return Padding( + padding: const EdgeInsets.only(top: 20.0), + child: SingleChildScrollView( + child: ListView.separated( + separatorBuilder: (BuildContext context, int index) => Divider(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5)), + shrinkWrap: true, + reverse: true, + padding: const EdgeInsets.only(top: 20.0), + physics: const NeverScrollableScrollPhysics(), + itemCount: model.replyComList!.length, + itemBuilder: (context, index) { + DateTime time1 = DateTime.parse(model.replyComList![index].date!); + return Builder(builder: (context) { + return Row(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ + model.replyComList![index].profile != null && model.replyComList![index].profile != "" + ? UiUtils.setFixedSizeboxForProfilePicture(childWidget: CircleAvatar(backgroundImage: NetworkImage(model.replyComList![index].profile!), radius: 32)) + : UiUtils.setFixedSizeboxForProfilePicture(childWidget: const Icon(Icons.account_circle, size: 35)), + Expanded( + child: Padding( + padding: const EdgeInsetsDirectional.only(start: 15.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + CustomTextLabel( + text: model.replyComList![index].name!, + textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontSize: 13)), + Padding( + padding: const EdgeInsetsDirectional.only(start: 10.0), + child: Icon(Icons.circle, size: 4.0, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7))), + Padding( + padding: const EdgeInsetsDirectional.only(start: 10.0), + child: CustomTextLabel( + text: UiUtils.convertToAgo(context, time1, 1)!, + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontSize: 10), + )), + ], + ), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: CustomTextLabel( + text: model.replyComList![index].message!, + textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.normal), + ), + ), + BlocBuilder(builder: (context, state) { + return Padding( + padding: const EdgeInsets.only(top: 15.0), + child: Row( + children: [ + InkWell( + child: const Icon(Icons.thumb_up_off_alt_rounded), + onTap: () { + (context.read().getUserId() != "0") + ? context.read().setLikeAndDislikeComm( + langId: context.read().state.id, + commId: model.replyComList![index].id!, + status: (model.replyComList![index].like == "1") ? "0" : "1", + fromLike: true) + : UiUtils.loginRequired(context); + }, + ), + model.replyComList![index].totalLikes! != "0" + ? Padding( + padding: const EdgeInsetsDirectional.only(start: 4.0), + child: CustomTextLabel( + text: model.replyComList![index].totalLikes!, + textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer))) + : const SizedBox(width: 12), + Padding( + padding: const EdgeInsetsDirectional.only(start: 35), + child: InkWell( + child: const Icon(Icons.thumb_down_alt_rounded), + onTap: () { + (context.read().getUserId() != "0") + ? context.read().setLikeAndDislikeComm( + langId: context.read().state.id, + commId: model.replyComList![index].id!, + status: (model.replyComList![index].dislike == "1") ? "0" : "2", + fromLike: false) + : UiUtils.loginRequired(context); + }, + )), + model.replyComList![index].totalDislikes! != "0" + ? Padding( + padding: const EdgeInsetsDirectional.only(start: 4.0), + child: CustomTextLabel( + text: model.replyComList![index].totalDislikes!, + textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer))) + : const SizedBox.shrink(), + const Spacer(), + if (context.read().getUserId() != "0") + InkWell( + child: Icon(Icons.more_vert_outlined, color: UiUtils.getColorScheme(context).primaryContainer, size: 17), + onTap: () => delAndReportReplyComm(model: model, context: context, reportC: reportC, newsId: widget.newsId, setState: setState, replyIndex: index)) + ], + ), + ); + }) + ], + ))), + ]); + }); + }))); + } + + Widget replyCommentView() { + return BlocBuilder(builder: (context, state) { + if (state is CommentNewsFetchSuccess && state.commentNews.isNotEmpty) { + return BlocListener( + listener: (context, likeDislikeState) { + if (likeDislikeState is LikeAndDislikeCommSuccess) { + final defaultIndex = state.commentNews.indexWhere((element) => element.id == likeDislikeState.comment.id); + if (defaultIndex != -1) { + state.commentNews[defaultIndex].replyComList = likeDislikeState.comment.replyComList; + context.read().emitSuccessState(state.commentNews); + } + } + }, + child: Padding( + padding: const EdgeInsetsDirectional.only(top: 10.0, bottom: 10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + allReplyComView(state.commentNews[widget.replyComIndex]), + replyComProfileWithCom(state.commentNews[widget.replyComIndex]), + replyComSendReplyView(state.commentNews[widget.replyComIndex]), + replyAllComListView(state.commentNews[widget.replyComIndex]) + ], + )), + ); + } + if (state is CommentNewsFetchFailure) { + return Center(child: CustomTextLabel(text: state.errorMessage, textAlign: TextAlign.center)); + } + //state is CommentNewsFetchInProgress || state is CommentNewsInitial + return const Padding(padding: EdgeInsets.only(bottom: 10.0, left: 10.0, right: 10.0), child: SizedBox.shrink()); + }); + } + + @override + Widget build(BuildContext context) { + return BlocProvider(create: (context) => LikeAndDislikeCommCubit(LikeAndDislikeCommRepository()), child: replyCommentView()); + } +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/RerwardAds/googleRewardAds.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/RerwardAds/googleRewardAds.dart new file mode 100644 index 00000000..4aa4f632 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/RerwardAds/googleRewardAds.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; + +const AdRequest request = AdRequest( + //static + keywords: ['foo', 'bar'], + contentUrl: 'http://foo.com/bar.html', + nonPersonalizedAds: true, +); +RewardedAd? rewardedAd; +int _numRewardedLoadAttempts = 0; +int maxFailedLoadAttempts = 3; + +void createGoogleRewardedAd(BuildContext context) { + if (context.read().rewardId() != "") { + RewardedAd.load( + adUnitId: context.read().rewardId()!, + request: request, + rewardedAdLoadCallback: RewardedAdLoadCallback( + onAdLoaded: (RewardedAd ad) { + rewardedAd = ad; + _numRewardedLoadAttempts = 0; + }, + onAdFailedToLoad: (LoadAdError error) { + rewardedAd = null; + _numRewardedLoadAttempts += 1; + if (_numRewardedLoadAttempts <= maxFailedLoadAttempts) { + createGoogleRewardedAd(context); + } + }, + )); + } +} + +void showGoogleRewardedAd(BuildContext context) { + if (context.read().rewardId() != "") { + if (rewardedAd == null) { + return; + } + rewardedAd!.fullScreenContentCallback = FullScreenContentCallback( + onAdShowedFullScreenContent: (RewardedAd ad) => debugPrint('ad onAdShowedFullScreenContent.'), + onAdDismissedFullScreenContent: (RewardedAd ad) { + ad.dispose(); + createGoogleRewardedAd(context); + }, + onAdFailedToShowFullScreenContent: (RewardedAd ad, AdError error) { + ad.dispose(); + createGoogleRewardedAd(context); + }, + ); + + rewardedAd!.setImmersiveMode(true); + rewardedAd!.show(onUserEarnedReward: (AdWithoutView ad, RewardItem reward) {}); + rewardedAd = null; + } +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/RerwardAds/unityRewardAds.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/RerwardAds/unityRewardAds.dart new file mode 100644 index 00000000..840576a2 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/RerwardAds/unityRewardAds.dart @@ -0,0 +1,23 @@ +import 'package:flutter/foundation.dart'; +import 'package:unity_ads_plugin/unity_ads_plugin.dart'; + +void loadUnityRewardAd(String placementId) { + UnityAds.load(placementId: placementId, onComplete: (placementId) {}, onFailed: (placementId, error, message) {}); +} + +void showUnityRewardAds(String placementId) { + UnityAds.showVideoAd( + placementId: placementId, + onComplete: (placementId) { + loadUnityRewardAd(placementId); + }, + onFailed: (placementId, error, message) { + loadUnityRewardAd(placementId); + }, + onStart: (placementId) => debugPrint('Video Ad $placementId started'), + onClick: (placementId) => debugPrint('Video Ad $placementId click'), + onSkipped: (placementId) { + loadUnityRewardAd(placementId); + }, + ); +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/ShowMoreNewsList.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/ShowMoreNewsList.dart new file mode 100644 index 00000000..016d7313 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/ShowMoreNewsList.dart @@ -0,0 +1,121 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/relatedNewsCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; +import 'package:news/ui/widgets/NewsItem.dart'; +import 'package:news/ui/widgets/errorContainerWidget.dart'; +import 'package:news/ui/widgets/shimmerNewsList.dart'; + +import 'package:news/ui/widgets/customAppBar.dart'; +import 'package:news/utils/ErrorMessageKeys.dart'; +import 'package:news/utils/uiUtils.dart'; + +class ShowMoreNewsList extends StatefulWidget { + final NewsModel model; + + const ShowMoreNewsList({super.key, required this.model}); + + @override + ShowMoreNewsListState createState() => ShowMoreNewsListState(); + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute(builder: (_) => ShowMoreNewsList(model: arguments['model'])); + } +} + +class ShowMoreNewsListState extends State { + late final ScrollController controller = ScrollController()..addListener(hasMoreRelatedNewsScrollListener); + Set get locationValue => SettingsLocalDataRepository().getLocationCityValues(); + + void hasMoreRelatedNewsScrollListener() { + if (controller.position.maxScrollExtent == controller.offset) { + if (context.read().hasMoreRelatedNews()) { + context.read().getMoreRelatedNews( + langId: context.read().state.id, + catId: widget.model.subCatId == "0" || widget.model.subCatId == '' ? widget.model.categoryId : null, + subCatId: widget.model.subCatId != "0" || widget.model.subCatId != '' ? widget.model.subCatId : null, + latitude: locationValue.first, + longitude: locationValue.last); + } else {} + } + } + + _buildRelatedNewsContainer( + {required NewsModel model, required int index, required int totalCurrentRelatedNews, required bool hasMoreRelatedNewsFetchError, required bool hasMore, required List newsList}) { + if (index == totalCurrentRelatedNews - 1 && index != 0) { + if (hasMore) { + if (hasMoreRelatedNewsFetchError) { + return Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0), + child: IconButton( + onPressed: () { + context.read().getMoreRelatedNews( + langId: context.read().state.id, + catId: widget.model.subCatId == "0" || widget.model.subCatId == '' ? widget.model.categoryId : null, + subCatId: widget.model.subCatId != "0" || widget.model.subCatId != '' ? widget.model.subCatId : null, + latitude: locationValue.first, + longitude: locationValue.last); + }, + icon: Icon(Icons.error, color: Theme.of(context).primaryColor)), + ), + ); + } else { + return Center(child: Padding(padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0), child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor))); + } + } + } + + return NewsItem(model: model, index: index, newslist: newsList, fromShowMore: true); + } + + void refreshNewsList() { + context.read().getRelatedNews( + langId: context.read().state.id, + catId: widget.model.subCatId == "0" || widget.model.subCatId == '' ? widget.model.categoryId : null, + subCatId: widget.model.subCatId != "0" || widget.model.subCatId != '' ? widget.model.subCatId : null); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: CustomAppBar(height: 45, isBackBtn: true, label: 'relatedNews', horizontalPad: 15, isConvertText: true), + body: BlocBuilder( + builder: (context, state) { + if (state is RelatedNewsFetchSuccess) { + return RefreshIndicator( + onRefresh: () async { + refreshNewsList(); + }, + child: ListView.builder( + controller: controller, + physics: const AlwaysScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: state.relatedNews.length, + itemBuilder: (context, index) { + return _buildRelatedNewsContainer( + model: state.relatedNews[index], + hasMore: state.hasMore, + hasMoreRelatedNewsFetchError: state.hasMoreFetchError, + index: index, + totalCurrentRelatedNews: state.relatedNews.length, + newsList: state.relatedNews); + }), + ); + } + + if (state is RelatedNewsFetchFailure) { + return ErrorContainerWidget( + errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage, onRetry: refreshNewsList); + } + //state is RelatedNewsInitial || state is RelatedNewsFetchInProgress + return Padding(padding: const EdgeInsets.only(bottom: 10.0, left: 10.0, right: 10.0), child: ShimmerNewsList(isNews: true)); + }, + ), + ); + } +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/backBtn.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/backBtn.dart new file mode 100644 index 00000000..c3f834c4 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/backBtn.dart @@ -0,0 +1,24 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:news/ui/styles/colors.dart'; + +Widget backBtn(BuildContext context, bool fromShowMore) { + return Positioned.directional( + textDirection: Directionality.of(context), + top: 40, + start: 20.0, + child: InkWell( + child: ClipRRect( + borderRadius: BorderRadius.circular(52.0), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: Container( + alignment: Alignment.center, + height: 39, + width: 39, + decoration: const BoxDecoration(color: secondaryColor, shape: BoxShape.circle), + child: const Icon(Icons.keyboard_backspace_rounded, color: darkSecondaryColor)))), + onTap: () { + (fromShowMore == true) ? Navigator.of(context).popUntil((route) => route.isFirst) : Navigator.pop(context); + })); +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/dateView.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/dateView.dart new file mode 100644 index 00000000..f6f01281 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/dateView.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/uiUtils.dart'; + +Widget dateView(BuildContext context, String date) { + return Row( + children: [ + const Icon(Icons.access_time_filled_rounded, size: 15), + const SizedBox(width: 3), + CustomTextLabel( + text: UiUtils.convertToAgo(context, DateTime.parse(date), 0)!, + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), fontSize: 12.0, fontWeight: FontWeight.w600)) + ], + ); +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/delAndReportCom.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/delAndReportCom.dart new file mode 100644 index 00000000..f6379c5b --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/delAndReportCom.dart @@ -0,0 +1,131 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/cubits/NewsComment/deleteCommentCubit.dart'; +import 'package:news/cubits/NewsComment/flagCommentCubit.dart'; +import 'package:news/cubits/commentNewsCubit.dart'; +import 'package:news/data/models/CommentModel.dart'; +import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart'; +import 'package:news/ui/widgets/customTextBtn.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; + +delAndReportCom( + {required int index, + required CommentModel model, + required BuildContext context, + required TextEditingController reportC, + required String newsId, + required StateSetter setState, + Function? isReplyUpdate}) { + showDialog( + context: context, + barrierDismissible: true, + builder: (BuildContext context) { + return AlertDialog( + contentPadding: const EdgeInsets.all(20), + elevation: 2.0, + backgroundColor: UiUtils.getColorScheme(context).surface, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(15.0))), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (context.read().getUserId() == model.userId!) + InkWell( + onTap: () async { + if (context.read().getUserId() != "0") { + context.read().setDeleteComm(commId: model.id!); + } + }, + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + child: Row( + children: [ + CustomTextLabel( + text: 'deleteTxt', + textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontWeight: FontWeight.bold)), + const Spacer(), + BlocConsumer( + bloc: context.read(), + listener: (context, state) { + if (state is DeleteCommSuccess) { + context.read().deleteComment(index); + showSnackBar(state.message, context); + if (isReplyUpdate != null) { + isReplyUpdate(false, index); + } + Navigator.pop(context); + } + }, + builder: (context, state) { + return SvgPictureWidget(assetName: "delete_icon", assetColor: ColorFilter.mode(UiUtils.getColorScheme(context).primaryContainer, BlendMode.srcIn), height: 20, width: 20); + }), + ], + ), + ), + if (context.read().getUserId() != model.userId!) + Padding( + padding: const EdgeInsets.only(top: 15), + child: Row(children: [ + CustomTextLabel( + text: 'reportTxt', + textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontWeight: FontWeight.bold)), + const Spacer(), + SvgPictureWidget(assetName: "flag_icon", assetColor: ColorFilter.mode(UiUtils.getColorScheme(context).primaryContainer, BlendMode.srcIn), height: 20, width: 20) + ])), + if (context.read().getUserId() != model.userId!) + Padding( + padding: const EdgeInsets.only(top: 5), + child: TextField( + controller: reportC, + keyboardType: TextInputType.multiline, + maxLines: null, + style: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6)), + decoration: InputDecoration( + enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), width: 0.5)), + focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), width: 0.5)), + ))), + if (context.read().getUserId() != model.userId!) + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + CustomTextButton( + onTap: () => Navigator.pop(context), + textWidget: CustomTextLabel( + text: 'cancelBtn', + textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontWeight: FontWeight.bold))), + BlocConsumer( + bloc: context.read(), + listener: (context, state) { + if (state is SetFlagFetchSuccess) { + setState(() => reportC.text = ""); + showSnackBar(state.message, context); + Navigator.pop(context); + } + }, + builder: (context, state) { + return CustomTextButton( + onTap: () { + if (context.read().getUserId() != "0") { + if (reportC.text.trim().isNotEmpty) { + context.read().setFlag(commId: model.id!, newsId: newsId, message: reportC.text); + } else { + showSnackBar(UiUtils.getTranslatedLabel(context, 'firstFillData'), context); + } + } else { + UiUtils.loginRequired(context); + } + }, + textWidget: CustomTextLabel( + text: 'submitBtn', + textStyle: + Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontWeight: FontWeight.bold))); + }) + ], + ), + ], + ))); + }); +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/delAndReportReplyComm.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/delAndReportReplyComm.dart new file mode 100644 index 00000000..126e5162 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/delAndReportReplyComm.dart @@ -0,0 +1,130 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/models/CommentModel.dart'; +import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart'; +import 'package:news/ui/widgets/customTextBtn.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/cubits/NewsComment/deleteCommentCubit.dart'; +import 'package:news/cubits/NewsComment/flagCommentCubit.dart'; +import 'package:news/cubits/commentNewsCubit.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; + +delAndReportReplyComm( + {required CommentModel model, required BuildContext context, required TextEditingController reportC, required String newsId, required StateSetter setState, required int replyIndex}) { + showDialog( + context: context, + barrierDismissible: true, + builder: (BuildContext context) { + return AlertDialog( + contentPadding: const EdgeInsets.all(20), + elevation: 2.0, + backgroundColor: UiUtils.getColorScheme(context).surface, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(15.0))), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (context.read().getUserId() == model.replyComList![replyIndex].userId!) + InkWell( + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + onTap: () async { + if (context.read().getUserId() != "0") { + context.read().setDeleteComm(commId: model.replyComList![replyIndex].id!); + } + }, + child: Row( + children: [ + CustomTextLabel( + text: 'deleteTxt', + textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontWeight: FontWeight.bold)), + const Spacer(), + BlocConsumer( + bloc: context.read(), + listener: (context, state) { + if (state is DeleteCommSuccess) { + context.read().deleteCommentReply(model.id!, replyIndex); + showSnackBar(state.message, context); + Navigator.pop(context); + } + }, + builder: (context, state) { + return SvgPictureWidget(assetName: "delete_icon", assetColor: ColorFilter.mode(UiUtils.getColorScheme(context).primaryContainer, BlendMode.srcIn), height: 20, width: 20); + }), + ], + ), + ), + if (context.read().getUserId() != model.replyComList![replyIndex].userId!) + Padding( + padding: const EdgeInsets.only(top: 15), + child: Row( + children: [ + CustomTextLabel( + text: 'reportTxt', + textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontWeight: FontWeight.bold)), + const Spacer(), + SvgPictureWidget(assetName: "flag_icon", assetColor: ColorFilter.mode(UiUtils.getColorScheme(context).primaryContainer, BlendMode.srcIn), height: 20, width: 20) + ], + )), + if (context.read().getUserId() != model.replyComList![replyIndex].userId!) + Padding( + padding: const EdgeInsets.only(top: 5), + child: TextField( + controller: reportC, + keyboardType: TextInputType.multiline, + maxLines: null, + style: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6)), + decoration: InputDecoration( + enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), width: 0.5)), + focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), width: 0.5)), + ), + )), + if (context.read().getUserId() != model.replyComList![replyIndex].userId!) + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + CustomTextButton( + onTap: () { + Navigator.pop(context); + }, + textWidget: CustomTextLabel( + text: 'cancelBtn', + textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontWeight: FontWeight.bold))), + BlocConsumer( + bloc: context.read(), + listener: (context, state) { + if (state is SetFlagFetchSuccess) { + setState(() { + reportC.text = ""; + }); + + showSnackBar(state.message, context); + Navigator.pop(context); + } + }, + builder: (context, state) { + return CustomTextButton( + onTap: () { + if (context.read().getUserId() != "0") { + if (reportC.text.trim().isNotEmpty) { + context.read().setFlag(commId: model.replyComList![replyIndex].id!, newsId: newsId, message: reportC.text); + } else { + showSnackBar(UiUtils.getTranslatedLabel(context, 'firstFillData'), context); + } + } else { + UiUtils.loginRequired(context); + } + }, + textWidget: CustomTextLabel( + text: 'submitBtn', + textStyle: + Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), fontWeight: FontWeight.bold))); + }) + ], + ), + ], + ))); + }); +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/descView.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/descView.dart new file mode 100644 index 00000000..9d5edc8a --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/descView.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; + +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:news/ui/screens/NewsDetailsVideo.dart'; + +Widget descView({required String desc, required double fontValue, required BuildContext context}) { + return Padding( + padding: const EdgeInsets.only(top: 5.0), + child: HtmlWidget( + desc, + onTapUrl: (String? url) async { + if (await canLaunchUrl(Uri.parse(url!))) { + await launchUrl(Uri.parse(url)); + return true; + } else { + throw 'Could not launch $url'; + } + }, + onErrorBuilder: (context, element, error) => CustomTextLabel(text: '$element error: $error'), + onLoadingBuilder: (context, element, loadingProgress) => UiUtils.showCircularProgress(true, Theme.of(context).primaryColor), + renderMode: RenderMode.column, + // set the default styling for text + textStyle: TextStyle(fontSize: fontValue.toDouble()), + customWidgetBuilder: (element) { + if ((element.toString() == "") || (element.toString() == "")) { + return FittedBox( + fit: BoxFit.fill, + child: Container( + height: 220, + width: MediaQuery.of(context).size.width, + color: Colors.transparent, + child: (element.toString() == "") ? NewsDetailsVideo(src: element.attributes["src"], type: "1") : NewsDetailsVideo(type: "2", src: element.outerHtml)), + ); + } + return null; + }, + )); +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/horizontalBtnList.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/horizontalBtnList.dart new file mode 100644 index 00000000..b93edd52 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/horizontalBtnList.dart @@ -0,0 +1,288 @@ +import 'package:flutter/material.dart'; +import 'package:html/parser.dart' show parse; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/cubits/Bookmark/UpdateBookmarkCubit.dart'; +import 'package:news/cubits/Bookmark/bookmarkCubit.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/data/models/NewsModel.dart'; + +import 'package:news/ui/widgets/customTextLabel.dart'; + +import 'package:news/ui/widgets/SnackBarWidget.dart'; +import 'package:news/utils/internetConnectivity.dart'; +import 'package:news/utils/uiUtils.dart'; + +Widget allRowBtn( + {required bool isFromBreak, + required BuildContext context, + BreakingNewsModel? breakModel, + NewsModel? model, + required int fontVal, + required Function updateFont, + required bool isPlaying, + required Function speak, + required Function stop, + required Function updateComEnabled}) { + return !isFromBreak + ? Padding( + padding: const EdgeInsetsDirectional.only(end: 85), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row(children: [ + if (context.read().getCommentsMode() == "1") + InkWell( + child: Column(children: [ + const Icon(Icons.insert_comment_rounded), + Padding( + padding: const EdgeInsetsDirectional.only(top: 4.0), + child: CustomTextLabel( + text: 'comLbl', + maxLines: 3, + textAlign: TextAlign.center, + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), fontSize: 9.0))) + ]), + onTap: () { + if (model!.isCommentEnabled != null && model.isCommentEnabled == 0) { + //comments disabled by Admin + showSnackBar(UiUtils.getTranslatedLabel(context, "disabledCommentsMsg"), context); + } else { + updateComEnabled(true); + } + }), + InkWell( + child: setShareBtn(context), + onTap: () async { + if (await InternetConnectivity.isNetworkAvailable()) { + UiUtils.shareNews(context: context, slug: model!.slug!, title: model.title!, isNews: true, isVideo: false, videoId: "", isBreakingNews: false); + } else { + showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context); + } + }, + ), + BlocBuilder( + bloc: context.read(), + builder: (context, bookmarkState) { + bool isBookmark = context.read().isNewsBookmark(model!.newsId!); + return BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is UpdateBookmarkStatusSuccess) { + if (state.wasBookmarkNewsProcess) { + context.read().addBookmarkNews(state.news); + } else { + context.read().removeBookmarkNews(state.news); + } + } + isBookmark = context.read().isNewsBookmark(model.newsId!); + }), + builder: (context, state) { + return InkWell( + onTap: () { + if (context.read().getUserId() != "0") { + if (state is UpdateBookmarkStatusInProgress) { + return; + } + context.read().setBookmarkNews(news: model, status: (isBookmark) ? "0" : "1"); + } else { + UiUtils.loginRequired(context); + } + }, + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.13, + child: Column(children: [ + state is UpdateBookmarkStatusInProgress + ? SizedBox(height: 24, width: 24, child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)) + : Icon(isBookmark ? Icons.bookmark_added_rounded : Icons.bookmark_add_outlined), + Padding( + padding: const EdgeInsetsDirectional.only(top: 4.0), + child: CustomTextLabel( + text: 'saveLbl', + maxLines: 2, + textAlign: TextAlign.center, + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), fontSize: 9.0))) + ]), + )); + }); + }), + InkWell( + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.13, + child: Center( + child: Column( + children: [ + const Icon(Icons.text_fields_rounded), + Padding( + padding: const EdgeInsetsDirectional.only(top: 4.0), + child: CustomTextLabel( + text: 'txtSizeLbl', + maxLines: 3, + textAlign: TextAlign.center, + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), fontSize: 9.0))) + ], + ), + ), + ), + onTap: () { + changeFontSizeSheet(context, fontVal, updateFont); + }, + ), + InkWell( + child: setSpeakBtn(context, isPlaying), + onTap: () { + if (isPlaying) { + stop(); + } else { + final document = parse("${model!.title}\n${model.desc}"); //Speak Title along with Description + String parsedString = parse(document.body!.text).documentElement!.text; + speak(parsedString); + } + }) + ]), + )) + : Row(children: [ + InkWell( + child: setTextSize(context), + onTap: () { + changeFontSizeSheet(context, fontVal, updateFont); + }, + ), + Padding( + padding: const EdgeInsetsDirectional.only(start: 8.0), + child: InkWell( + child: setSpeakBtn(context, isPlaying), + onTap: () { + if (isPlaying) { + stop(); + } else { + final document = parse("${breakModel!.title}\n${breakModel.desc}"); //Speak Title along with Description + String parsedString = parse(document.body!.text).documentElement!.text; + speak(parsedString); + } + })), + Padding( + padding: const EdgeInsetsDirectional.only(start: 8.0), + child: InkWell( + child: setShareBtn(context), + onTap: () async { + if (await InternetConnectivity.isNetworkAvailable()) { + UiUtils.shareNews(context: context, slug: breakModel!.slug!, title: breakModel.title!, isVideo: false, videoId: "", isBreakingNews: true, isNews: false); + } else { + showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context); + } + })) + ]); +} + +changeFontSizeSheet(BuildContext context, int fontValue, Function updateFun) { + showModalBottomSheet( + context: context, + elevation: 5.0, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.only(topLeft: Radius.circular(50), topRight: Radius.circular(50))), + builder: (BuildContext context) { + return StatefulBuilder(builder: (BuildContext context, setStater) { + return Container( + padding: const EdgeInsetsDirectional.only(bottom: 20.0, top: 5.0, start: 20.0, end: 20.0), + decoration: const BoxDecoration(borderRadius: BorderRadius.only(topLeft: Radius.circular(50), topRight: Radius.circular(50))), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 30), + child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + const Icon(Icons.text_fields_rounded), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: CustomTextLabel( + text: 'txtSizeLbl', + textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer), + )), + CustomTextLabel(text: "( $fontValue )", textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer)), + ])), + SliderTheme( + data: SliderTheme.of(context).copyWith( + activeTrackColor: Colors.red[700], + inactiveTrackColor: Colors.red[100], + trackShape: const RoundedRectSliderTrackShape(), + trackHeight: 4.0, + thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 8.0), + thumbColor: Colors.redAccent, + overlayColor: Colors.red.withAlpha(32), + overlayShape: const RoundSliderOverlayShape(overlayRadius: 28.0), + tickMarkShape: const RoundSliderTickMarkShape(), + activeTickMarkColor: Colors.red[700], + inactiveTickMarkColor: Colors.red[100], + valueIndicatorShape: const PaddleSliderValueIndicatorShape(), + valueIndicatorColor: Colors.redAccent, + valueIndicatorTextStyle: const TextStyle(color: Colors.white)), + child: Slider( + label: '$fontValue', + value: fontValue.toDouble(), + activeColor: Theme.of(context).primaryColor, + min: 15, + max: 40, + divisions: 10, + onChanged: (value) { + setStater(() { + fontValue = value.round(); + updateFun(value.round()); + }); + })) + ], + )); + }); + }); +} + +setShareBtn(BuildContext context) { + return SizedBox( + width: MediaQuery.of(context).size.width * 0.13, + child: Column( + children: [ + const Icon(Icons.share_rounded), + Padding( + padding: const EdgeInsetsDirectional.only(top: 4.0), + child: CustomTextLabel( + text: 'shareLbl', + maxLines: 3, + textAlign: TextAlign.center, + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), fontSize: 9.0), + )) + ], + ), + ); +} + +setSpeakBtn(BuildContext context, bool isPlaying) { + return SizedBox( + width: MediaQuery.of(context).size.width * 0.13, + child: Column( + children: [ + Icon(Icons.speaker_phone_rounded, color: isPlaying ? Theme.of(context).primaryColor : UiUtils.getColorScheme(context).primaryContainer), + Padding( + padding: const EdgeInsetsDirectional.only(top: 4.0), + child: CustomTextLabel( + text: 'speakLoudLbl', + maxLines: 3, + textAlign: TextAlign.center, + textStyle: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: isPlaying ? Theme.of(context).primaryColor : UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), fontSize: 9.0))) + ], + ), + ); +} + +setTextSize(BuildContext context) { + return Column( + children: [ + const Icon(Icons.text_fields_rounded), + Padding( + padding: const EdgeInsetsDirectional.only(top: 4.0), + child: CustomTextLabel( + text: 'txtSizeLbl', maxLines: 2, textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), fontSize: 9.0))) + ], + ); +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/likeBtn.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/likeBtn.dart new file mode 100644 index 00000000..88cefc3d --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/likeBtn.dart @@ -0,0 +1,89 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart'; +import 'package:news/cubits/LikeAndDislikeNews/updateLikeAndDislikeCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/ui/styles/colors.dart'; + +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/uiUtils.dart'; + +Widget likeBtn(BuildContext context, NewsModel model) { + bool isLike = context.read().isNewsLikeAndDisLike(model.newsId!); + return BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is LikeAndDisLikeFetchSuccess) { + isLike = context.read().isNewsLikeAndDisLike(model.newsId!); + } + }), + builder: (context, likeAndDislikeState) { + return BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is UpdateLikeAndDisLikeStatusSuccess) { + context.read().getLike(langId: context.read().state.id); + model.totalLikes = (!isLike) + ? (int.parse(model.totalLikes.toString()) + 1).toString() + : (model.totalLikes!.isNotEmpty) + ? (int.parse(model.totalLikes.toString()) - 1).toString() + : "0"; + } + }), + builder: (context, state) { + isLike = context.read().isNewsLikeAndDisLike(model.newsId!); + return Positioned.directional( + textDirection: Directionality.of(context), + top: MediaQuery.of(context).size.height / 2.90, + end: MediaQuery.of(context).size.width / 10.8, + child: Column( + children: [ + InkWell( + splashColor: Colors.transparent, + onTap: () { + if (context.read().getUserId() != "0") { + if (state is UpdateLikeAndDisLikeStatusInProgress) { + return; + } + context.read().setLikeAndDisLikeNews(news: model, status: (isLike) ? "0" : "1"); + } else { + UiUtils.loginRequired(context); + } + }, + child: ClipRRect( + borderRadius: BorderRadius.circular(52.0), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: Container( + alignment: Alignment.center, + height: 39, + width: 39, + decoration: BoxDecoration(boxShadow: [ + BoxShadow(blurRadius: 6, offset: const Offset(5.0, 5.0), color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.4), spreadRadius: 0), + ], color: secondaryColor, shape: BoxShape.circle), + child: (state is UpdateLikeAndDisLikeStatusInProgress) + ? SizedBox(height: 20, width: 20, child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)) + : isLike + ? const Icon(Icons.thumb_up_alt, color: darkSecondaryColor) + : const Icon(Icons.thumb_up_off_alt, color: darkSecondaryColor), + ))), + ), + SizedBox( + width: MediaQuery.of(context).size.width / 7.5, + child: Padding( + padding: const EdgeInsetsDirectional.only(top: 5.0), + child: CustomTextLabel( + text: (int.tryParse(model.totalLikes!)! > 0) ? "${model.totalLikes!} ${UiUtils.getTranslatedLabel(context, 'likeLbl')}" : "", + maxLines: 2, + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)), + ), + ), + ) + ], + )); + }); + }); +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/relatedNewsList.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/relatedNewsList.dart new file mode 100644 index 00000000..9c1a3273 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/relatedNewsList.dart @@ -0,0 +1,166 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/relatedNewsCubit.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/ui/styles/colors.dart'; + +class RelatedNewsList extends StatefulWidget { + final NewsModel model; + + const RelatedNewsList({super.key, required this.model}); + + @override + RelatedNewsListState createState() => RelatedNewsListState(); +} + +class RelatedNewsListState extends State { + int sliderIndex = 0; + List relatedList = []; + + Widget getRelatedList() { + return BlocConsumer( + bloc: context.read(), + listener: (context, state) { + if (state is RelatedNewsFetchSuccess) { + setState(() { + relatedList.clear(); + relatedList.addAll(state.relatedNews); + relatedList.removeWhere((element) => (element.id == widget.model.id!) || (element.id == widget.model.newsId!)); + }); + } + }, + builder: (context, state) { + if (state is RelatedNewsFetchSuccess && relatedList.isNotEmpty) { + return Padding( + padding: const EdgeInsetsDirectional.only(top: 15.0, bottom: 15.0), + child: Column(children: [ + Align( + alignment: Alignment.topLeft, + child: Padding( + padding: const EdgeInsetsDirectional.only(bottom: 15.0), + child: CustomTextLabel( + text: 'relatedNews', + textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600)))), + showRelatedNews(relatedList) + ])); + } + return const SizedBox.shrink(); //state is RelatedNewsFetchInProgress || state is RelatedNewsInitial || state is RelatedNewsFetchFailure + }); + } + + Widget relatedNewsData(NewsModel model, List newsList) { + return Stack(children: [ + ClipRRect( + borderRadius: const BorderRadius.only(topLeft: Radius.circular(15.0), topRight: Radius.circular(15.0)), + child: ShaderMask( + shaderCallback: (rect) => const LinearGradient(begin: Alignment.center, end: Alignment.bottomCenter, colors: [Colors.transparent, secondaryColor]).createShader(rect), + blendMode: BlendMode.darken, + child: GestureDetector( + child: CustomNetworkImage(networkImageUrl: model.image!, width: double.maxFinite, height: MediaQuery.of(context).size.height / 2.9, isVideo: false, fit: BoxFit.cover), + onTap: () { + List addNewsList = []; + addNewsList.addAll(newsList); + addNewsList.remove(model); + UiUtils.showInterstitialAds(context: context); + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": model, "newsList": addNewsList, "isFromBreak": false, "fromShowMore": false}); + }))), + Align( + alignment: Alignment.bottomCenter, + child: Container( + padding: EdgeInsetsDirectional.only(bottom: MediaQuery.of(context).size.height / 18.9, start: MediaQuery.of(context).size.width / 20.0, end: 5.0), + width: MediaQuery.of(context).size.width, + child: CustomTextLabel( + text: model.title!, + textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: secondaryColor, fontWeight: FontWeight.normal, fontSize: 12.5, height: 1.0), + maxLines: 1, + softWrap: true, + overflow: TextOverflow.ellipsis))) + ]); + } + + List map(List list, Function handler) { + List result = []; + for (var i = 0; i < list.length; i++) { + result.add(handler(i, list[i])); + } + + return result; + } + + CustomTextLabel setCoverageText(BuildContext context) { + return CustomTextLabel( + text: 'viewFullCoverage', + textAlign: TextAlign.center, + textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.9), fontWeight: FontWeight.w600), + ); + } + + Icon setCoverageIcon(BuildContext context) { + return Icon(Icons.image, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.9)); + } + + Widget showRelatedNews(List relatedNews) { + return Column(children: [ + Stack(children: [ + SizedBox( + height: MediaQuery.of(context).size.height / 2.9, + width: double.infinity, + child: PageView.builder( + itemCount: relatedNews.length, + scrollDirection: Axis.horizontal, + physics: const AlwaysScrollableScrollPhysics(), + onPageChanged: (index) { + setState(() { + sliderIndex = index; + }); + }, + itemBuilder: (BuildContext context, int index) { + return relatedNewsData(relatedNews[index], relatedNews); + })), + Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: EdgeInsetsDirectional.only(top: MediaQuery.of(context).size.height / 3.3), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: map(relatedNews, (index, url) { + return AnimatedContainer( + duration: const Duration(milliseconds: 500), + width: sliderIndex == index ? MediaQuery.of(context).size.width / 15.0 : MediaQuery.of(context).size.width / 15.0, + height: 5.0, + margin: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 2.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5.0), + color: sliderIndex == index ? Theme.of(context).primaryColor : darkBackgroundColor.withOpacity(0.5), + )); + })))) + ]), + Container( + height: 38.0, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(15.0), bottomRight: Radius.circular(15.0)), + color: UiUtils.getColorScheme(context).surface, + boxShadow: [BoxShadow(color: borderColor.withOpacity(0.4), offset: const Offset(0.0, 2.0), blurRadius: 6.0, spreadRadius: 0)]), + child: ElevatedButton.icon( + icon: setCoverageIcon(context), + label: setCoverageText(context), + onPressed: () { + Navigator.of(context).pushNamed(Routes.showMoreRelatedNews, arguments: {"model": widget.model}); + }, + style: ElevatedButton.styleFrom(foregroundColor: Theme.of(context).primaryColor, backgroundColor: Colors.transparent, shadowColor: Colors.transparent), + )) + ]); + } + + @override + Widget build(BuildContext context) { + return getRelatedList(); + } +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/setBannderAds.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/setBannderAds.dart new file mode 100644 index 00000000..fecf317f --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/setBannderAds.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; +import 'package:unity_ads_plugin/unity_ads_plugin.dart' as unity; +import 'package:news/cubits/appSystemSettingCubit.dart'; + +setBannerAd(BuildContext context, BannerAd? bannerAd) { + if (context.read().bannerId() != "") { + switch (context.read().checkAdsType()) { + case "google": + return Padding( + padding: const EdgeInsetsDirectional.only(start: 5.0, end: 5.0), + child: SizedBox(width: double.maxFinite, height: bannerAd!.size.height.toDouble(), child: AdWidget(ad: bannerAd)), + ); + + case "unity": + return unity.UnityBannerAd( + placementId: context.read().bannerId()!, + onLoad: (placementId) => debugPrint('Banner loaded: $placementId'), + onClick: (placementId) => debugPrint('Banner clicked: $placementId'), + onFailed: (placementId, error, message) => debugPrint('Banner Ad $placementId failed: $error $message'), + ); + default: + return const SizedBox.shrink(); + } + } +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/tagView.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/tagView.dart new file mode 100644 index 00000000..c8b80f29 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/tagView.dart @@ -0,0 +1,63 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/uiUtils.dart'; + +Widget tagView({required NewsModel model, required BuildContext context, bool? isFromDetailsScreen = false}) { + List tagList = []; + + if (model.tagName! != "") { + final tagName = model.tagName!; + tagList = tagName.split(','); + } + + List tagId = []; + + if (model.tagId != null && model.tagId! != "") { + tagId = model.tagId!.split(","); + } + + return model.tagName! != "" + ? Padding( + padding: const EdgeInsetsDirectional.only(top: 15.0), + child: SizedBox( + height: 20.0, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: List.generate(tagList.length, (index) { + return Padding( + padding: EdgeInsetsDirectional.only(start: index == 0 ? 0 : 7), + child: InkWell( + child: ClipRRect( + borderRadius: BorderRadius.circular(3.0), + child: (isFromDetailsScreen != null && isFromDetailsScreen) + ? tagsContainer(context: context, tagList: tagList, index: index) + : BackdropFilter(filter: ImageFilter.blur(sigmaX: 30, sigmaY: 30), child: tagsContainer(context: context, tagList: tagList, index: index))), + onTap: () { + Navigator.of(context).pushNamed(Routes.tagScreen, arguments: {"tagId": tagId[index], "tagName": tagList[index]}); + }, + )); + }), + ), + ))) + : const SizedBox.shrink(); +} + +Widget tagsContainer({required BuildContext context, required List tagList, required int index}) { + return Container( + height: 20.0, + width: 65, + alignment: Alignment.center, + padding: const EdgeInsetsDirectional.only(start: 3.0, end: 3.0), + decoration: BoxDecoration( + borderRadius: const BorderRadius.only(topLeft: Radius.circular(10.0), bottomRight: Radius.circular(10.0)), color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)), + child: CustomTextLabel( + text: tagList[index], + textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: UiUtils.getColorScheme(context).secondary, fontSize: 11), + overflow: TextOverflow.ellipsis, + softWrap: true)); +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/titleView.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/titleView.dart new file mode 100644 index 00000000..23fd2e51 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/titleView.dart @@ -0,0 +1,9 @@ +import 'package:flutter/material.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/uiUtils.dart'; + +Widget titleView({required String title, required BuildContext context}) { + return Padding( + padding: const EdgeInsetsDirectional.only(top: 6.0), + child: CustomTextLabel(text: title, textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600))); +} diff --git a/news-app/lib/ui/screens/NewsDetail/Widgets/videoBtn.dart b/news-app/lib/ui/screens/NewsDetail/Widgets/videoBtn.dart new file mode 100644 index 00000000..c804378b --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetail/Widgets/videoBtn.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/app/routes.dart'; + +Widget videoBtn({required BuildContext context, required bool isFromBreak, NewsModel? model, BreakingNewsModel? breakModel}) { + if ((breakModel != null && breakModel.contentValue != "") || model != null && model.contentValue != "") { + return Positioned.directional( + textDirection: Directionality.of(context), + top: 35, + end: 20.0, + child: InkWell( + child: + Container(height: 39, width: 39, decoration: const BoxDecoration(color: secondaryColor, shape: BoxShape.circle), child: const Icon(Icons.play_arrow_rounded, color: darkSecondaryColor)), + onTap: () { + Navigator.of(context) + .pushNamed(Routes.newsVideo, arguments: (!isFromBreak) ? {"from": 1, "model": model} : {"from": 3, "breakModel": breakModel, "otherVideos": [], "otherBreakingVideos": []}); + }, + )); + } else { + return const SizedBox.shrink(); + } +} diff --git a/news-app/lib/ui/screens/NewsDetailsVideo.dart b/news-app/lib/ui/screens/NewsDetailsVideo.dart new file mode 100644 index 00000000..f96a5fb5 --- /dev/null +++ b/news-app/lib/ui/screens/NewsDetailsVideo.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +import 'package:news/utils/internetConnectivity.dart'; + +class NewsDetailsVideo extends StatefulWidget { + String? src; + String type; + + NewsDetailsVideo({super.key, this.src, required this.type}); + + @override + State createState() => StateNewsDetailsVideo(); +} + +class StateNewsDetailsVideo extends State { + final GlobalKey _scaffoldKey = GlobalKey(); + bool _isNetworkAvail = true; + var iframe; + + @override + void initState() { + super.initState(); + + checkNetwork(); + if ((widget.type == "1") || (widget.type == "3")) { + iframe = ''' + + + + '''; + } else { + iframe = ''' + + + + '''; + } + } + + checkNetwork() async { + if (await InternetConnectivity.isNetworkAvailable()) { + setState(() { + _isNetworkAvail = true; + }); + } else { + setState(() { + _isNetworkAvail = false; + }); + } + } + + @override + void dispose() { + // set screen back to portrait mode + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + // set screen to landscape mode bydefault + return SafeArea(child: Scaffold(key: _scaffoldKey, body: _isNetworkAvail ? viewVideo() : const SizedBox.shrink())); + } + + //news video link set + viewVideo() { + WebUri frm; + frm = WebUri.uri(Uri.dataFromString(iframe, mimeType: 'text/html')); + return Center( + child: InAppWebView(initialUrlRequest: URLRequest(url: frm)), + ); + } +} diff --git a/news-app/lib/ui/screens/NewsVideo.dart b/news-app/lib/ui/screens/NewsVideo.dart new file mode 100644 index 00000000..10e28355 --- /dev/null +++ b/news-app/lib/ui/screens/NewsVideo.dart @@ -0,0 +1,185 @@ +import 'package:flick_video_player/flick_video_player.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:video_player/video_player.dart'; +import 'package:youtube_parser/youtube_parser.dart'; +import 'package:youtube_player_flutter/youtube_player_flutter.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/utils/internetConnectivity.dart'; +import 'package:news/ui/screens/NewsDetailsVideo.dart'; +import 'package:news/data/models/LiveStreamingModel.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/models/BreakingNewsModel.dart'; + +class NewsVideo extends StatefulWidget { + int from; + LiveStreamingModel? liveModel; + NewsModel? model; + BreakingNewsModel? breakModel; + + NewsVideo({super.key, this.model, required this.from, this.liveModel, this.breakModel}); + + @override + State createState() => StateVideo(); + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute(builder: (_) => NewsVideo(from: arguments['from'], liveModel: arguments['liveModel'], model: arguments['model'], breakModel: arguments['breakModel'])); + } +} + +class StateVideo extends State { + FlickManager? flickManager; + YoutubePlayerController? _yc; + bool _isNetworkAvail = true; + + VideoPlayerController? _controller; + + @override + void initState() { + super.initState(); + checkNetwork(); + initialisePlayer(); + } + + initialisePlayer() { + switch (widget.from) { + case 1: + if (widget.model!.contentValue != "" || widget.model!.contentValue != null) { + if (widget.model!.contentType == "video_upload") { + _controller = VideoPlayerController.networkUrl(Uri.parse(widget.model!.contentValue!)); + flickManager = FlickManager(videoPlayerController: _controller!, autoPlay: true); + } else if (widget.model!.contentType == "video_youtube") { + _yc = YoutubePlayerController(initialVideoId: YoutubePlayer.convertUrlToId(widget.model!.contentValue!) ?? "", flags: const YoutubePlayerFlags(autoPlay: true)); + } + } + break; + case 2: + if (widget.liveModel!.type == "url_youtube") { + _yc = YoutubePlayerController(initialVideoId: getIdFromUrl(widget.liveModel!.url!) ?? "", flags: const YoutubePlayerFlags(autoPlay: true, isLive: true)); + } + break; + default: + if (widget.breakModel!.contentValue != "" || widget.breakModel!.contentValue != null) { + if (widget.breakModel!.contentType == "video_upload") { + _controller = VideoPlayerController.networkUrl(Uri.parse(widget.breakModel!.contentValue!)); + flickManager = FlickManager(videoPlayerController: _controller!, autoPlay: true); + } else if (widget.breakModel!.contentType == "video_youtube") { + _yc = YoutubePlayerController(initialVideoId: YoutubePlayer.convertUrlToId(widget.breakModel!.contentValue!) ?? "", flags: const YoutubePlayerFlags(autoPlay: true)); + } + } + } + } + + checkNetwork() async { + if (await InternetConnectivity.isNetworkAvailable()) { + setState(() => _isNetworkAvail = true); + } else { + setState(() => _isNetworkAvail = false); + } + } + + @override + void dispose() { + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values); + if (_controller != null && _controller!.value.isPlaying) _controller!.pause(); + switch (widget.from) { + case 1: + if (widget.model!.contentType == "video_upload") { + Future.delayed(const Duration(milliseconds: 10)).then((value) { + flickManager!.flickControlManager!.exitFullscreen(); + flickManager!.dispose(); + _controller!.dispose(); + _controller = null; + flickManager = null; + }); + } else if (widget.model!.contentType == "video_youtube") { + _yc!.dispose(); + } + break; + case 2: + if (widget.liveModel!.type == "url_youtube") { + _yc!.dispose(); + } + break; + default: + if (widget.breakModel!.contentType == "video_upload") { + _controller = null; + flickManager!.dispose(); + } else if (widget.breakModel!.contentType == "video_youtube") { + _yc!.dispose(); + } + } + Future.delayed(const Duration(milliseconds: 20)).then((value) { + super.dispose(); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + extendBodyBehindAppBar: true, //to show Landscape video fullscreen + appBar: AppBar( + backgroundColor: Colors.transparent, + leading: InkWell( + onTap: () { + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); + Navigator.of(context).pop(); + }, + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + child: Padding( + padding: const EdgeInsetsDirectional.only(start: 20), + child: ClipRRect( + borderRadius: BorderRadius.circular(22.0), + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration(color: UiUtils.getColorScheme(context).primaryContainer, shape: BoxShape.circle), + child: Icon(Icons.keyboard_backspace_rounded, color: UiUtils.getColorScheme(context).surface)))), + )), + body: PopScope( + canPop: true, + onPopInvoked: (val) async { + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); + if (widget.from == 1 && (widget.model!.contentType == "video_upload")) { + _controller!.pause(); + + Future.delayed(const Duration(milliseconds: 10)).then((value) { + flickManager!.flickControlManager!.exitFullscreen(); + flickManager!.dispose(); + _controller!.dispose(); + }); + } + }, + child: Padding( + padding: const EdgeInsetsDirectional.only(start: 15.0, end: 15.0, bottom: 5.0), + child: _isNetworkAvail + ? Container(alignment: Alignment.center, decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0)), child: viewVideo()) + : const Center(child: CustomTextLabel(text: 'internetmsg'))), + )); + } + + viewVideo() { + return widget.from == 1 + ? widget.model!.contentType == "video_upload" + ? FlickVideoPlayer(flickManager: flickManager!, flickVideoWithControlsFullscreen: const FlickVideoWithControls(videoFit: BoxFit.fitWidth)) + : widget.model!.contentType == "video_youtube" + ? YoutubePlayer(controller: _yc!, showVideoProgressIndicator: true, progressIndicatorColor: Theme.of(context).primaryColor) + : widget.model!.contentType == "video_other" + ? Center(child: NewsDetailsVideo(src: widget.model!.contentValue, type: "3")) + : const SizedBox.shrink() + : widget.from == 2 + ? widget.liveModel!.type == "url_youtube" + ? YoutubePlayer(controller: _yc!, showVideoProgressIndicator: true, progressIndicatorColor: Theme.of(context).primaryColor) + : Center(child: NewsDetailsVideo(src: widget.liveModel!.url, type: "3")) + : widget.breakModel!.contentType == "video_upload" + ? FlickVideoPlayer(flickManager: flickManager!) + : widget.breakModel!.contentType == "video_youtube" + ? YoutubePlayer(controller: _yc!, showVideoProgressIndicator: true, progressIndicatorColor: Theme.of(context).primaryColor) + : widget.breakModel!.contentType == "video_other" + ? Center(child: NewsDetailsVideo(src: widget.breakModel!.contentValue, type: "3")) + : const SizedBox.shrink(); + } +} diff --git a/news-app/lib/ui/screens/PrivacyPolicyScreen.dart b/news-app/lib/ui/screens/PrivacyPolicyScreen.dart new file mode 100644 index 00000000..8582c7e4 --- /dev/null +++ b/news-app/lib/ui/screens/PrivacyPolicyScreen.dart @@ -0,0 +1,55 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; +import 'package:news/ui/widgets/customAppBar.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class PrivacyPolicy extends StatefulWidget { + final String? title; + final String? from; + final String? desc; + + const PrivacyPolicy({super.key, this.title, this.from, this.desc}); + + @override + State createState() { + return StatePrivacy(); + } + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute( + builder: (_) => PrivacyPolicy( + from: arguments['from'], + title: arguments['title'], + desc: arguments['desc'], + )); + } +} + +class StatePrivacy extends State with TickerProviderStateMixin { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: CustomAppBar(height: 45, isBackBtn: true, label: widget.title!, horizontalPad: 15, isConvertText: false), + body: SingleChildScrollView( + padding: const EdgeInsetsDirectional.only(start: 20.0, end: 20.0, top: 10.0), + child: HtmlWidget( + widget.desc!, + onTapUrl: (String? url) async { + if (await canLaunchUrl(Uri.parse(url!))) { + await launchUrl(Uri.parse(url)); + return true; + } else { + throw 'Could not launch $url'; + } + }, + ), + )); + } +} diff --git a/news-app/lib/ui/screens/Profile/ProfileScreen.dart b/news-app/lib/ui/screens/Profile/ProfileScreen.dart new file mode 100644 index 00000000..44743361 --- /dev/null +++ b/news-app/lib/ui/screens/Profile/ProfileScreen.dart @@ -0,0 +1,555 @@ +import 'dart:io'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:in_app_review/in_app_review.dart'; +import 'package:news/app/app.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/cubits/Auth/deleteUserCubit.dart'; +import 'package:news/cubits/Auth/registerTokenCubit.dart'; +import 'package:news/cubits/Author/authorCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; +import 'package:news/cubits/languageJsonCubit.dart'; +import 'package:news/cubits/otherPagesCubit.dart'; +import 'package:news/cubits/settingCubit.dart'; +import 'package:news/data/repositories/Auth/authLocalDataSource.dart'; +import 'package:news/ui/screens/Profile/Widgets/customAlertDialog.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/customAppBar.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/internetConnectivity.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:news/cubits/themeCubit.dart'; +import 'package:news/ui/styles/appTheme.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; + +class ProfileScreen extends StatefulWidget { + const ProfileScreen({super.key}); + + @override + ProfileScreenState createState() => ProfileScreenState(); + + static Route route(RouteSettings routeSettings) { + return CupertinoPageRoute(builder: (_) => const ProfileScreen()); + } +} + +class ProfileScreenState extends State { + File? image; + String? name, mobile, email, profile; + TextEditingController? nameC, monoC, emailC = TextEditingController(); + AuthLocalDataSource authLocalDataSource = AuthLocalDataSource(); + bool isEditMono = false, isEditEmail = false, isAuthor = false; + final FirebaseAuth _auth = FirebaseAuth.instance; + final InAppReview _inAppReview = InAppReview.instance; + late AuthorStatus authorStatus; + + @override + void initState() { + getOtherPagesData(); + super.initState(); + } + + getOtherPagesData() { + Future.delayed(Duration.zero, () { + context.read().getOtherPage(langId: context.read().state.id); + }); + } + + Widget pagesBuild() { + return BlocBuilder(builder: (context, state) { + if (state is OtherPageFetchSuccess) { + return ScrollConfiguration( + behavior: GlobalScrollBehavior(), + child: ListView.builder( + shrinkWrap: true, + padding: EdgeInsets.zero, + physics: const NeverScrollableScrollPhysics(), + itemCount: state.otherPage.length, + itemBuilder: ((context, index) => + setDrawerItem(state.otherPage[index].title!, Icons.info_rounded, false, true, false, 8, image: state.otherPage[index].image!, desc: state.otherPage[index].pageContent))), + ); + } else { + //state is OtherPageFetchInProgress || state is OtherPageInitial || state is OtherPageFetchFailure + return const SizedBox.shrink(); + } + }); + } + + switchTheme(bool value) async { + if (value) { + if (await InternetConnectivity.isNetworkAvailable()) { + SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light); + context.read().changeTheme(AppTheme.Dark); + UiUtils.setUIOverlayStyle(appTheme: AppTheme.Dark); + //for non-appbar screens + } else { + showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context); + } + } else { + if (await InternetConnectivity.isNetworkAvailable()) { + SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark); + context.read().changeTheme(AppTheme.Light); + UiUtils.setUIOverlayStyle(appTheme: AppTheme.Light); + //for non-appbar screens + } else { + showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context); + } + } + } + + bool getTheme() { + return (context.read().state.appTheme == AppTheme.Dark) ? true : false; + } + + bool getNotification() { + if (context.read().state.settingsModel!.notification == true) { + return true; + } else { + return false; + } + } + + switchNotification(bool value) { + if (!value) { + FirebaseMessaging.instance.deleteToken().then((value) async { + context.read().registerToken(fcmId: '', context: context); + }); + } else { + FirebaseMessaging.instance.getToken().then((value) async { + context.read().registerToken(fcmId: value!, context: context); + context.read().changeFcmToken(value); + }); + } + + context.read().changeNotification(value); + setState(() {}); + } + + //set drawer item list + Widget setDrawerItem(String title, IconData? icon, bool isTrailing, bool isNavigate, bool isSwitch, int id, {String? image, String? desc, bool isDeleteAcc = false}) { + return ListTile( + dense: true, + contentPadding: EdgeInsets.zero, + leading: Container( + height: 30, + width: 30, + padding: EdgeInsets.all(3), + decoration: BoxDecoration(shape: BoxShape.rectangle, borderRadius: BorderRadius.circular(7), color: borderColor.withOpacity(0.2)), + child: (image != null && image != "") + ? ClipRRect( + borderRadius: BorderRadius.circular(7), + child: Image.network( + image, + width: 20, + height: 20, + color: (image.contains("png")) ? UiUtils.getColorScheme(context).primaryContainer : null, + errorBuilder: (context, error, stackTrace) { + return Icon(icon); + }, + ), + ) + : Icon(icon, size: 20)), + iconColor: (isDeleteAcc) ? (context.read().state.appTheme == AppTheme.Dark ? darkIconColor : iconColor) : UiUtils.getColorScheme(context).primaryContainer, + trailing: (isTrailing) + ? SizedBox( + height: 45, + width: 55, + child: FittedBox( + fit: BoxFit.fill, + child: Switch.adaptive( + onChanged: (id == 0) ? switchTheme : switchNotification, + value: (id == 0) ? getTheme() : getNotification(), + activeColor: Theme.of(context).primaryColor, + activeTrackColor: Theme.of(context).primaryColor, + inactiveThumbColor: Colors.grey, + inactiveTrackColor: Colors.grey))) + : const SizedBox.shrink(), + title: CustomTextLabel( + text: title, + textStyle: Theme.of(context) + .textTheme + .titleMedium + ?.copyWith(color: (isDeleteAcc) ? (context.read().state.appTheme == AppTheme.Dark ? darkIconColor : iconColor) : UiUtils.getColorScheme(context).primaryContainer)), + onTap: () { + if (isNavigate) { + switch (id) { + case 2: + Navigator.of(context).pushNamed(Routes.languageList, arguments: {"from": "profile"}); + break; + case 3: + Navigator.of(context).pushNamed(Routes.bookmark); + break; + + case 5: + Navigator.of(context).pushNamed(Routes.addNews, arguments: {"isEdit": false, "from": "profile"}); + break; + case 6: + Navigator.of(context).pushNamed(Routes.manageUserNews); + break; + case 7: + Navigator.of(context).pushNamed(Routes.managePref, arguments: {"from": 1}); + break; + case 8: + Navigator.of(context).pushNamed(Routes.privacy, arguments: {"from": "setting", "title": title, "desc": desc}); + break; + case 9: + _openStoreListing(); + break; + case 10: + String str = "$appName\n\n${context.read().getShareAppText()}\n\n${context.read().getAndroidAppLink()}\n"; + bool isIOSAppLive = context.read().getiOSAppLink()?.trim().isNotEmpty ?? false; + if (isIOSAppLive) str += "\n\n${context.read().getiOSAppLink()}"; + Share.share(str, sharePositionOrigin: Rect.fromLTWH(0, 0, MediaQuery.of(context).size.width, MediaQuery.of(context).size.height / 2)); + break; + case 12: + deleteAccount(); + break; + case 13: + Navigator.of(context).pushNamed(Routes.editUserProfile, arguments: {'from': 'profile'}); + default: + break; + } + } + }, + ); + } + + logOutDialog() async { + await showDialog( + context: context, + builder: (BuildContext context) { + return StatefulBuilder(builder: (BuildContext context, StateSetter setStater) { + return CustomAlertDialog( + isForceAppUpdate: false, + context: context, + yesButtonText: 'yesLbl', + yesButtonTextPostfix: 'logoutLbl', + noButtonText: 'noLbl', + imageName: 'logout', + titleWidget: CustomTextLabel( + text: 'logoutLbl', textStyle: Theme.of(this.context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w500, color: UiUtils.getColorScheme(context).primaryContainer)), + messageText: 'logoutTxt', + onYESButtonPressed: () async { + UiUtils.userLogOut(contxt: context); + }); + }); + }); + } + + //set Delete dialogue + deleteAccount() async { + await showDialog( + context: context, + builder: (BuildContext context) { + return StatefulBuilder(builder: (BuildContext context, StateSetter setStater) { + return CustomAlertDialog( + context: context, + isForceAppUpdate: false, + yesButtonText: (_auth.currentUser != null) ? 'yesLbl' : 'logoutLbl', + yesButtonTextPostfix: (_auth.currentUser != null) ? 'deleteTxt' : '', + noButtonText: (_auth.currentUser != null) ? 'noLbl' : 'cancelBtn', + imageName: 'deleteAccount', + titleWidget: (_auth.currentUser != null) + ? CustomTextLabel( + text: 'deleteAcc', textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w800, color: UiUtils.getColorScheme(context).primaryContainer)) + : CustomTextLabel( + text: 'deleteAlertTitle', textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w800, color: UiUtils.getColorScheme(context).primaryContainer)), + messageText: (_auth.currentUser != null) ? 'deleteConfirm' : 'deleteRelogin', + onYESButtonPressed: () async { + (_auth.currentUser != null) ? proceedToDeleteProfile() : askToLoginAgain(); + }); + }); + }); + } + + askToLoginAgain() { + showSnackBar(UiUtils.getTranslatedLabel(context, 'loginReqMsg'), context); + Navigator.of(context).pushNamedAndRemoveUntil(Routes.login, (route) => false); + } + + proceedToDeleteProfile() async { + //delete user from firebase + try { + await _auth.currentUser!.delete().then((value) { + //delete user prefs from App-local + context.read().deleteUser().then((value) { + showSnackBar(value["message"], context); + for (int i = 0; i < AuthProviders.values.length; i++) { + if (AuthProviders.values[i].name == context.read().getType()) { + context.read().signOut(AuthProviders.values[i]).then((value) { + Navigator.of(context).pushNamedAndRemoveUntil(Routes.login, (route) => false); + }); + } + } + }); + }); + } on FirebaseAuthException catch (error) { + if (error.code == "requires-recent-login") { + for (int i = 0; i < AuthProviders.values.length; i++) { + if (AuthProviders.values[i].name == context.read().getType()) { + context.read().signOut(AuthProviders.values[i]).then((value) { + Navigator.of(context).pushNamedAndRemoveUntil(Routes.login, (route) => false); + }); + } + } + } else { + throw showSnackBar('${error.message}', context); + } + } catch (e) {} + } + + Future _openStoreListing() => _inAppReview.openStoreListing(appStoreId: context.read().getAppstoreId(), microsoftStoreId: 'microsoftStoreId'); + + Widget setHeader() { + return BlocBuilder(builder: (context, authState) { + if (authState is Authenticated && context.read().getUserId() != "0") { + isAuthor = (authState.authModel.isAuthor == 1); + authorStatus = ((authState.authModel.authorDetails != null) ? authState.authModel.authorDetails?.status : AuthorStatus.rejected)!; + return Padding( + padding: const EdgeInsetsDirectional.only(start: 15.0, end: 15.0, top: 15.0), + child: Container( + padding: const EdgeInsetsDirectional.only(start: 20.0, end: 20.0, top: 10), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(15.0), color: Theme.of(context).colorScheme.surface), + child: Row(crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + CircleAvatar( + radius: 34, + backgroundColor: Colors.transparent, + child: ClipOval( + clipBehavior: Clip.antiAliasWithSaveLayer, + child: (authState.authModel.profile != null && authState.authModel.profile.toString().trim().isNotEmpty) + ? Image.network( + authState.authModel.profile!, + fit: BoxFit.fill, + width: 80, + height: 80, + filterQuality: FilterQuality.high, + errorBuilder: (context, error, stackTrace) { + return const Icon(Icons.person); + }, + ) + : Icon(Icons.person, color: UiUtils.getColorScheme(context).primaryContainer)), + ), + SizedBox( + width: MediaQuery.of(context).size.width * 0.6, + child: Padding( + padding: const EdgeInsetsDirectional.only(start: 5), + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (authState.authModel.name != null && authState.authModel.name != "") + CustomTextLabel( + text: authState.authModel.name!, + maxLines: 2, + overflow: TextOverflow.ellipsis, + textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w800, color: UiUtils.getColorScheme(context).primaryContainer)), + const SizedBox(height: 3), + if (authState.authModel.mobile != null && authState.authModel.mobile!.trim().isNotEmpty) + CustomTextLabel( + text: authState.authModel.mobile!, textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7))), + const SizedBox(height: 3), + if (authState.authModel.email != null && authState.authModel.email != "") + CustomTextLabel( + text: authState.authModel.email!, + maxLines: 2, + overflow: TextOverflow.ellipsis, + textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7))), + BlocConsumer( + listener: (context, state) { + if (state is AuthorRequestSent) showSnackBar(state.responseMessage, context); + }, + builder: (context, state) { + return buildAuthorButton(context, state); + }, + ) + ], + ), + ), + ), + ]), + ), + ); + } else { + return Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ + //For Guest User + Container( + margin: const EdgeInsets.all(15), + padding: const EdgeInsets.all(20), + decoration: BoxDecoration(shape: BoxShape.circle, border: Border.all(color: UiUtils.getColorScheme(context).primaryContainer)), + alignment: Alignment.center, + child: Icon(Icons.person, size: 25.0, color: UiUtils.getColorScheme(context).primaryContainer)), + Expanded(child: setGuestText()) + ]); + } + }); + } + + Widget buildAuthorButton(BuildContext context, AuthorState state) { + //check if user is author already + if (isAuthor || authorStatus == AuthorStatus.approved || state is AuthorApproved) { + return IntrinsicWidth( + child: Container( + margin: EdgeInsets.symmetric(vertical: 10), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration(color: backgroundColor, borderRadius: BorderRadius.circular(5), border: Border.all(color: authorApprovedColor)), + child: Row( + children: [ + Icon(Icons.verified_outlined, size: 18, color: authorApprovedColor), + SizedBox(width: 6), + const CustomTextLabel(text: 'authorLbl', textStyle: TextStyle(color: authorApprovedColor, fontWeight: FontWeight.w600)), + ], + ), + ), + ); + } + + if (authorStatus == AuthorStatus.pending || state is AuthorRequestSent) { + return IntrinsicWidth( + child: Container( + margin: EdgeInsets.symmetric(vertical: 10), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration(color: backgroundColor, borderRadius: BorderRadius.circular(8), border: Border.all(color: authorReviewColor)), + child: Row( + mainAxisSize: MainAxisSize.min, + children: const [ + Icon(Icons.info_outline, size: 18, color: authorReviewColor), + SizedBox(width: 6), + CustomTextLabel(text: 'authorReviewPendingLbl', textStyle: TextStyle(color: authorReviewColor, fontWeight: FontWeight.w500)), + ], + ), + ), + ); + } + + if (authorStatus == AuthorStatus.rejected || state is AuthorInitial) { + return InkWell( + onTap: () { + //redirect to update profile screen + Navigator.of(context).pushNamed(Routes.editUserProfile, arguments: {'from': 'becomeAuthor'}); + }, + child: Container( + margin: EdgeInsets.symmetric(vertical: 10), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration(color: backgroundColor, borderRadius: BorderRadius.circular(5), border: Border.all(color: authorRequestColor)), + child: const CustomTextLabel(text: 'becomeAuthorLbl', textStyle: TextStyle(color: authorRequestColor, fontWeight: FontWeight.w500))), + ); + } + + return const SizedBox.shrink(); + } + + Widget setGuestText() { + return BlocBuilder( + builder: (context, langState) { + return RichText( + text: TextSpan( + text: UiUtils.getTranslatedLabel(context, 'plzLbl'), + style: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, overflow: TextOverflow.ellipsis), + children: [ + TextSpan( + text: " ${UiUtils.getTranslatedLabel(context, 'loginBtn')} ", + style: Theme.of(context).textTheme.titleMedium?.copyWith(color: Theme.of(context).primaryColor, fontWeight: FontWeight.w600, overflow: TextOverflow.ellipsis), + recognizer: TapGestureRecognizer() + ..onTap = () { + Future.delayed(const Duration(milliseconds: 500), () { + setState(() { + Navigator.of(context).pushReplacementNamed(Routes.login); + }); + }); + }), + TextSpan( + text: "${UiUtils.getTranslatedLabel(context, 'firstAccLbl')} ", style: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer)), + TextSpan( + text: UiUtils.getTranslatedLabel(context, 'allFunLbl'), + style: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, overflow: TextOverflow.ellipsis)) + ], + ), + ); + }, + ); + } + + Widget setBody() { + return Padding( + padding: const EdgeInsetsDirectional.only(start: 15.0, end: 15.0, top: 15.0), + child: Container( + padding: const EdgeInsetsDirectional.only(start: 20.0, end: 20.0), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(15.0), color: Theme.of(context).colorScheme.surface), + child: ScrollConfiguration( + behavior: GlobalScrollBehavior(), + child: BlocBuilder( + builder: (context, state) { + return ListView( + padding: const EdgeInsetsDirectional.only(top: 10.0), + shrinkWrap: true, + physics: const BouncingScrollPhysics(), + children: [ + if (context.read().getUserId() != "0") setDrawerItem('editProfile', Icons.edit_outlined, false, true, false, 13), + setDrawerItem('darkModeLbl', Icons.swap_horizontal_circle, true, false, true, 0), + setDrawerItem('notificationLbl', Icons.notifications_rounded, true, false, true, 1), + setDrawerItem('changeLang', Icons.g_translate_rounded, false, true, false, 2), + Divider(thickness: 2), + if (context.read().getUserId() != "0") setDrawerItem('bookmarkLbl', Icons.bookmarks_rounded, false, true, false, 3), + // if (context.read().getUserId() != "0") setDrawerItem('notificationLbl', Icons.notifications, false, true, false, 4), + if (context.read().getUserId() != "0" && isAuthor) + // context.read().getRole() != "0") + setDrawerItem('createNewsLbl', Icons.add_box_rounded, false, true, false, 5), + if (context.read().getUserId() != "0" && isAuthor) + // context.read().getRole() != "0") + setDrawerItem('manageNewsLbl', Icons.edit_document, false, true, false, 6), + if (context.read().getUserId() != "0") setDrawerItem('managePreferences', Icons.thumbs_up_down_rounded, false, true, false, 7), + if (context.read().getUserId() != "0") Divider(thickness: 2), + pagesBuild(), + setDrawerItem('rateUs', Icons.stars_sharp, false, true, false, 9), + setDrawerItem('shareApp', Icons.share_rounded, false, true, false, 10), + if (context.read().getUserId() != "0") setDrawerItem('deleteAcc', Icons.delete_forever_rounded, false, true, false, 12, isDeleteAcc: true), + ], + ); + }, + ), + )), + ); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + appBar: CustomAppBar( + height: 45, + isBackBtn: false, + label: 'myProfile', + isConvertText: true, + actionWidget: [if (context.read().getUserId() != "0") logoutButton()], + ), + body: Stack( + children: [ + SingleChildScrollView( + padding: const EdgeInsetsDirectional.only(top: 15.0, bottom: 10.0), + child: Column(mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [setHeader(), setBody()])), + ], + ))); + } + + Widget logoutButton() { + return GestureDetector( + onTap: () => logOutDialog(), + child: Container( + margin: EdgeInsetsDirectional.only(end: 15, top: 12, bottom: 12), + height: 30, + width: 30, + padding: EdgeInsets.all(3), + decoration: BoxDecoration(shape: BoxShape.rectangle, borderRadius: BorderRadius.circular(7), color: Colors.transparent, border: Border.all(width: 2, color: borderColor.withOpacity(0.3))), + child: Icon(Icons.power_settings_new_rounded, color: UiUtils.getColorScheme(context).primaryContainer, size: 20)), + ); + } +} diff --git a/news-app/lib/ui/screens/Profile/Widgets/customAlertDialog.dart b/news-app/lib/ui/screens/Profile/Widgets/customAlertDialog.dart new file mode 100644 index 00000000..ef9fb32b --- /dev/null +++ b/news-app/lib/ui/screens/Profile/Widgets/customAlertDialog.dart @@ -0,0 +1,81 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/uiUtils.dart'; + +class CustomAlertDialog extends StatelessWidget { + final BuildContext context; + final String yesButtonText; + final String yesButtonTextPostfix; + final String noButtonText; + final String imageName; + final Widget titleWidget; + final String messageText; + final Function() onYESButtonPressed; + final bool isForceAppUpdate; + const CustomAlertDialog( + {super.key, + required this.context, + required this.yesButtonText, + required this.yesButtonTextPostfix, + required this.noButtonText, + required this.imageName, + required this.titleWidget, + required this.messageText, + required this.onYESButtonPressed, + required this.isForceAppUpdate}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + contentPadding: const EdgeInsets.all(20), + backgroundColor: UiUtils.getColorScheme(context).surface, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12.0))), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SvgPictureWidget(assetName: imageName), + const SizedBox(height: 15), + titleWidget, + const SizedBox(height: 5), + CustomTextLabel(text: messageText, textAlign: TextAlign.center, textStyle: Theme.of(this.context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer)), + ], + ), + actionsAlignment: MainAxisAlignment.spaceAround, + actionsOverflowButtonSpacing: 15, + actions: [ + MaterialButton( + minWidth: MediaQuery.of(context).size.width / 3.5, + elevation: 0.0, + highlightColor: Colors.transparent, + color: Colors.transparent, + splashColor: Colors.transparent, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4), side: BorderSide(color: UiUtils.getColorScheme(context).primaryContainer)), + onPressed: () => (isForceAppUpdate) ? exit(0) : Navigator.of(context).pop(false), + child: CustomTextLabel( + text: noButtonText, textStyle: Theme.of(this.context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w500)), + ), + MaterialButton( + elevation: 0.0, + color: UiUtils.getColorScheme(context).primaryContainer, + splashColor: Colors.transparent, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + onPressed: onYESButtonPressed, + child: RichText( + text: TextSpan( + text: UiUtils.getTranslatedLabel(context, yesButtonText), + style: Theme.of(this.context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).surface, fontWeight: FontWeight.w500), + children: [ + const TextSpan(text: " , "), + TextSpan( + text: UiUtils.getTranslatedLabel(context, yesButtonTextPostfix), + style: Theme.of(this.context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).surface, fontWeight: FontWeight.w500)) + ]), + )), + ], + ); + } +} diff --git a/news-app/lib/ui/screens/Profile/userProfile.dart b/news-app/lib/ui/screens/Profile/userProfile.dart new file mode 100644 index 00000000..5b3268f0 --- /dev/null +++ b/news-app/lib/ui/screens/Profile/userProfile.dart @@ -0,0 +1,401 @@ +import 'dart:io'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/cubits/Auth/updateUserCubit.dart'; +import 'package:news/cubits/Author/authorCubit.dart'; +import 'package:news/cubits/languageCubit.dart'; +import 'package:news/data/repositories/Auth/authLocalDataSource.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; + +import 'package:news/ui/widgets/customAppBar.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/utils/validators.dart'; + +class UserProfileScreen extends StatefulWidget { + final String from; + const UserProfileScreen({super.key, required this.from}); + + @override + State createState() => UserProfileScreenState(); + + static Route route(RouteSettings routeSettings) { + Map arguments = routeSettings.arguments as Map; + return CupertinoPageRoute(builder: (_) => UserProfileScreen(from: arguments['from'] as String)); + } +} + +class UserProfileScreenState extends State { + final _formKey = GlobalKey(); + AuthLocalDataSource authLocalDataSource = AuthLocalDataSource(); + dynamic size; + FocusNode nameFocus = FocusNode(); + FocusNode mobNoFocus = FocusNode(); + FocusNode emailFocus = FocusNode(); + FocusNode crntFocus = FocusNode(); + FocusNode authorBioFocus = FocusNode(); + FocusNode telegramLinkFocus = FocusNode(); + FocusNode facebookLinkFocus = FocusNode(); + FocusNode linkedInLinkFocus = FocusNode(); + FocusNode whatsappLinkFocus = FocusNode(); + + TextEditingController? nameC = TextEditingController(), monoC = TextEditingController(), emailC = TextEditingController(); + TextEditingController? authorBioC = TextEditingController(); + TextEditingController? telegramLinkC = TextEditingController(); + TextEditingController? facebookLinkC = TextEditingController(); + TextEditingController? whatsappLinkC = TextEditingController(); + TextEditingController? linkedInLinkC = TextEditingController(); + String? profile, mobile; + bool isEditMono = false, isEditEmail = false, isSaving = false, isThisUserAnAuthor = false; + String? updateValue; + File? image; + + @override + void initState() { + isThisUserAnAuthor = context.read().isAuthor(); + setControllers(); + if (widget.from == "login") getLanguageList(); + + super.initState(); + } + + setControllers() { + nameC = TextEditingController(text: authLocalDataSource.getName()); + emailC = TextEditingController(text: authLocalDataSource.getEmail()); + monoC = TextEditingController(text: authLocalDataSource.getMobile()); + profile = context.read().getProfile(); + if (isThisUserAnAuthor) { + authorBioC = TextEditingController(text: (authLocalDataSource.getAuthorBio().isEmpty) ? context.read().getAuthorBio() : authLocalDataSource.getAuthorBio()); + whatsappLinkC = + TextEditingController(text: (authLocalDataSource.getAuthorWhatsappLink()!.isEmpty) ? context.read().getAuthorWhatsappLink() : authLocalDataSource.getAuthorWhatsappLink()); + telegramLinkC = + TextEditingController(text: (authLocalDataSource.getAuthorTelegramLink()!.isEmpty) ? context.read().getAuthorTelegramLink() : authLocalDataSource.getAuthorTelegramLink()); + linkedInLinkC = + TextEditingController(text: (authLocalDataSource.getAuthorLinkedInLink()!.isEmpty) ? context.read().getAuthorLinkedInLink() : authLocalDataSource.getAuthorLinkedInLink()); + facebookLinkC = + TextEditingController(text: (authLocalDataSource.getAuthorFacebookLink()!.isEmpty) ? context.read().getAuthorFacebookLink() : authLocalDataSource.getAuthorFacebookLink()); + } + } + + getLanguageList() { + Future.delayed(Duration.zero, () { + context.read().getLanguage(); + }); + } + + @override + Widget build(BuildContext context) { + size = MediaQuery.of(context).size; + return Scaffold(appBar: CustomAppBar(height: 45, isBackBtn: (widget.from == "login") ? false : true, label: 'editProfile', isConvertText: true), body: buildProfileFields()); + } + + onBackPress(bool isTrue) { + (widget.from == "login") ? Navigator.of(context).popUntil((route) => route.isFirst) : Navigator.pop(context); + } + + setPendingStatusControllerValues() { + authorBioC?.clear(); + whatsappLinkC?.clear(); + telegramLinkC?.clear(); + linkedInLinkC?.clear(); + facebookLinkC?.clear(); + } + + buildProfileFields() { + return PopScope( + canPop: (widget.from != "login") ? true : false, + onPopInvoked: (bool isTrue) => onBackPress, + child: BlocConsumer( + listener: (context, state) { + //show snackbar incase of success & failure both + if (state is UpdateUserFetchSuccess && state.updatedUser != null) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'profileUpdateMsg'), context); + isSaving = false; + setState(() {}); + if (widget.from == "becomeAuthor") { + //call author request API here + context.read().requestToBecomeAuthor(); + setPendingStatusControllerValues(); + } + if (widget.from == "login") { + if (context.read().langList().length > 1) { + Navigator.of(context).pushNamedAndRemoveUntil(Routes.languageList, (route) => false, arguments: {"from": "firstLogin"}); + } else { + Navigator.of(context).pushNamedAndRemoveUntil(Routes.managePref, (route) => false, arguments: {"from": 2}); + } + } else { + Navigator.pop(context); + } + } + if (state is UpdateUserFetchFailure) { + isSaving = false; + showSnackBar(state.errorMessage, context); + } + }, + builder: (context, state) { + return SingleChildScrollView( + child: Form( + key: _formKey, + child: Column(children: [ + profileWidget(), + setTextField( + validatorMethod: (value) => Validators.nameValidation(value!, context), + focusNode: nameFocus, + nextFocus: (context.read().getType() != loginMbl) ? mobNoFocus : emailFocus, + textInputAction: TextInputAction.next, + keyboardType: TextInputType.name, + controller: nameC, + hintlbl: UiUtils.getTranslatedLabel(context, 'nameLbl')), + SizedBox(height: size.height * 0.03), + setMobileNumber(), + SizedBox(height: size.height * 0.03), + setTextField( + validatorMethod: (value) => value!.trim().isEmpty ? null : Validators.emailValidation(value, context), + focusNode: emailFocus, + nextFocus: isThisUserAnAuthor ? authorBioFocus : null, + textInputAction: isThisUserAnAuthor ? TextInputAction.next : TextInputAction.done, + keyboardType: TextInputType.emailAddress, + controller: emailC, + isenable: (context.read().getType() == loginMbl) ? true : false, + hintlbl: UiUtils.getTranslatedLabel(context, 'emailLbl')), + SizedBox(height: size.height * 0.05), + setAuthorBio(), + SizedBox(height: size.height * 0.05), + Container( + alignment: AlignmentGeometry.centerLeft, + padding: EdgeInsetsDirectional.only(start: 20), + child: CustomTextLabel( + text: 'socialMediaLinksLbl', + textStyle: Theme.of(this.context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w700))), + SizedBox(height: size.height * 0.03), + setSocialMediaLink(linkController: telegramLinkC, currFocus: telegramLinkFocus, nextFocus: whatsappLinkFocus, typeForHintText: 'Telegram'), + SizedBox(height: size.height * 0.03), + setSocialMediaLink(linkController: whatsappLinkC, currFocus: whatsappLinkFocus, nextFocus: facebookLinkFocus, typeForHintText: 'Whatsapp'), + SizedBox(height: size.height * 0.03), + setSocialMediaLink(linkController: facebookLinkC, currFocus: facebookLinkFocus, nextFocus: linkedInLinkFocus, typeForHintText: 'Facebook'), + SizedBox(height: size.height * 0.03), + setSocialMediaLink(linkController: linkedInLinkC, currFocus: linkedInLinkFocus, nextFocus: null, typeForHintText: 'LinkedIn'), + SizedBox(height: size.height * 0.03), + submitBtn(context) + ]))); + }, + ), + ); + } + + Widget setAuthorBio() { + return Container( + width: double.maxFinite, + child: setTextField( + hintlbl: UiUtils.getTranslatedLabel(context, 'addYourBioHintLbl'), + controller: authorBioC, + focusNode: authorBioFocus, + nextFocus: telegramLinkFocus, + textInputAction: TextInputAction.newline, + maxLines: 2)); + } + + Widget setSocialMediaLink({required TextEditingController? linkController, required FocusNode currFocus, required FocusNode? nextFocus, required String typeForHintText}) { + return setTextField( + hintlbl: getAddLinkHint(context, typeForHintText), + controller: linkController, + focusNode: currFocus, + nextFocus: nextFocus, + textInputAction: TextInputAction.next, + keyboardType: TextInputType.url); + } + + String getAddLinkHint(BuildContext context, String type) { + final template = UiUtils.getTranslatedLabel(context, 'addLinkHereHintLbl'); + return template.replaceAll("{type}", type); + } + + Widget setMobileNumber() { + return SizedBox( + height: 60, + child: setTextField( + validatorMethod: (value) => value!.trim().isEmpty ? null : Validators.mobValidation(value, context), + keyboardType: TextInputType.phone, + hintlbl: UiUtils.getTranslatedLabel(context, 'mobileLbl'), + textInputAction: TextInputAction.next, + controller: monoC, + focusNode: mobNoFocus, + nextFocus: isThisUserAnAuthor ? authorBioFocus : emailFocus, + isenable: (context.read().getType() != loginMbl) ? true : false)); + } + + //set image camera + getFromCamera() async { + try { + XFile? pickedFile = await ImagePicker().pickImage(source: ImageSource.camera); + if (pickedFile != null) { + Navigator.of(context).pop(); //pop dialog + setState(() { + image = File(pickedFile.path); + profile = image!.path; + }); + } + } catch (e) {} + } + +// set image gallery + _getFromGallery() async { + XFile? pickedFile = await ImagePicker().pickImage(source: ImageSource.gallery, maxWidth: 1800, maxHeight: 1800); + if (pickedFile != null) { + setState(() { + image = File(pickedFile.path); + profile = image!.path; + Navigator.of(context).pop(); //pop dialog + }); + } + } + + profileWidget() { + return Container(padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 15), child: Center(child: profileImgWidget())); + } + + Widget profilePictureWidget() { + final fallbackIcon = Container( + padding: EdgeInsets.all(20), + decoration: BoxDecoration(shape: BoxShape.circle, border: BoxBorder.all(color: UiUtils.getColorScheme(context).primaryContainer)), + child: Icon(Icons.person, color: UiUtils.getColorScheme(context).primaryContainer)); + + if (image != null) { + return Image.file(image!, fit: BoxFit.fill, width: 75, height: 75); + } + + if (profile == null || profile!.trim().isEmpty) { + return fallbackIcon; + } + + return Image.network(profile!, + fit: BoxFit.fill, width: 85, errorBuilder: (_, __, ___) => fallbackIcon, loadingBuilder: (_, child, loadingProgress) => loadingProgress == null ? child : fallbackIcon); + } + + profileImgWidget() { + return GestureDetector( + onTap: () => UiUtils().showUploadImageBottomsheet(context: context, onCamera: getFromCamera, onGallery: _getFromGallery), + child: Stack( + alignment: Alignment.center, + children: [ + CircleAvatar( + radius: 46, + backgroundColor: Colors.transparent, + child: CircleAvatar(radius: 44, backgroundColor: Colors.transparent, child: ClipOval(clipBehavior: Clip.antiAliasWithSaveLayer, child: profilePictureWidget()))), + Positioned( + right: 0, + bottom: 0, + child: Container( + padding: const EdgeInsets.all(5), + decoration: + BoxDecoration(color: UiUtils.getColorScheme(context).primaryContainer, shape: BoxShape.circle, border: Border.all(width: 2, color: UiUtils.getColorScheme(context).secondary)), + child: Icon(Icons.camera_alt_outlined, color: Theme.of(context).colorScheme.secondary, size: 20))) + ], + ), + ); + } + + submitBtn(BuildContext context) { + return Container( + alignment: Alignment.center, + padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + child: ElevatedButton( + style: ElevatedButton.styleFrom(backgroundColor: Theme.of(context).primaryColor, shadowColor: Colors.transparent, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5.0))), + child: Container( + height: 45.0, + width: MediaQuery.of(context).size.width * 0.8, + alignment: Alignment.center, + child: (isSaving) + ? UiUtils.showCircularProgress(true, UiUtils.getColorScheme(context).primaryContainer) + : CustomTextLabel(text: 'saveLbl', textStyle: Theme.of(this.context).textTheme.titleMedium?.copyWith(color: secondaryColor, fontWeight: FontWeight.w500, letterSpacing: 0.6)), + ), + onPressed: () async { + validateData(); + })); + } + + validateData() async { + if (_formKey.currentState!.validate()) { + profileupdateprocess(); + } else {} + } + + profileupdateprocess() async { + isSaving = true; + //in case of Clearing Existing mobile number -> set mobile to blank, so it can be passed to replace existing value of mobile number as NULL mobile number won't be passed to APi + mobile = monoC!.text.trim(); + + if (mobile == null && context.read().getType() != loginMbl && context.read().getMobile().isNotEmpty) { + mobile = " "; + } + try { + context.read().setUpdateUser( + context: context, + name: nameC!.text, + email: emailC!.text, + mobile: mobile, + filePath: (image != null) ? image!.path : "", + authorBio: authorBioC?.text, + whatsappLink: whatsappLinkC?.text, + facebookLink: facebookLinkC?.text, + telegramLink: telegramLinkC?.text, + linkedInLink: linkedInLinkC?.text); + } catch (e) { + showSnackBar(e.toString(), context); + } + } + + setTextField( + {String? Function(String?)? validatorMethod, + FocusNode? focusNode, + FocusNode? nextFocus, + TextInputAction? textInputAction, + TextInputType? keyboardType, + TextEditingController? controller, + String? hintlbl, + bool isenable = true, + int? maxLines}) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: TextFormField( + enabled: isenable, + decoration: InputDecoration( + hintText: hintlbl, + hintStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).outline.withOpacity(0.2)), + filled: true, + fillColor: Theme.of(context).colorScheme.surface, + contentPadding: const EdgeInsets.symmetric(horizontal: 17, vertical: 17), + focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: UiUtils.getColorScheme(context).outline.withOpacity(0.7)), borderRadius: BorderRadius.circular(5.0)), + enabledBorder: OutlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(5.0)), + disabledBorder: OutlineInputBorder(borderSide: BorderSide.none, borderRadius: BorderRadius.circular(5.0)), + ), + validator: validatorMethod, + inputFormatters: [(focusNode == mobNoFocus) ? FilteringTextInputFormatter.digitsOnly : FilteringTextInputFormatter.singleLineFormatter], + focusNode: focusNode, + textInputAction: textInputAction, + onEditingComplete: () { + crntFocus = FocusNode(); + (nextFocus != null) ? fieldFocusChange(context, focusNode!, nextFocus) : focusNode?.unfocus(); + }, + onChanged: (String value) { + setState(() => crntFocus = focusNode!); + }, + onTapOutside: (val) => FocusScope.of(context).unfocus(), + maxLines: (maxLines ?? 1), + textAlignVertical: TextAlignVertical.center, + style: Theme.of(context).textTheme.titleMedium!.copyWith(color: (isenable) ? UiUtils.getColorScheme(context).primaryContainer : borderColor), + keyboardType: keyboardType, + controller: controller)); + } + + fieldFocusChange(BuildContext context, FocusNode currentFocus, FocusNode nextFocus) { + currentFocus.unfocus(); + FocusScope.of(context).requestFocus(nextFocus); + } +} diff --git a/news-app/lib/ui/screens/PushNotificationService.dart b/news-app/lib/ui/screens/PushNotificationService.dart new file mode 100644 index 00000000..d47df17b --- /dev/null +++ b/news-app/lib/ui/screens/PushNotificationService.dart @@ -0,0 +1,189 @@ +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:news/cubits/getUserDataByIdCubit.dart'; +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; +import 'package:news/ui/screens/dashBoard/dashBoardScreen.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/strings.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; +import 'dart:io'; +import 'package:news/cubits/NewsByIdCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/app/app.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/utils/uiUtils.dart'; + +FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); +FirebaseMessaging messaging = FirebaseMessaging.instance; +SettingsLocalDataRepository settingsRepo = SettingsLocalDataRepository(); + +backgroundMessage(NotificationResponse notificationResponse) { + //for notification only +} + +@pragma('vm:entry-point') +Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async {} + +void redirectToNewsDetailsScreen(RemoteMessage message, BuildContext context) async { + var data = message.data; + if (data[TYPE] == "default" || data[TYPE] == "category") { + var payload = data[NEWS_ID]; + var lanId = data[LANGUAGE_ID] ?? "14"; + + if (lanId == context.read().state.id) { + //show only if Current language is Same as Notification Language + if (payload == null) { + Navigator.push(context, MaterialPageRoute(builder: (context) => const MyApp())); + } else { + context.read().getNewsById(newsId: payload, langId: context.read().state.id).then((value) { + UiUtils.rootNavigatorKey.currentState!.pushNamed(Routes.newsDetails, arguments: {"model": value[0], "isFromBreak": false, "fromShowMore": false}); + }).catchError((e) {}); + } + } + } +} + +class PushNotificationService { + late BuildContext context; + + PushNotificationService({required this.context}); + + Future initialise() async { + messaging.getToken(); + const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('notification_icon'); + const DarwinInitializationSettings initializationSettingsIOS = DarwinInitializationSettings(requestAlertPermission: true, requestBadgePermission: false, requestSoundPermission: true); + Future notificationHandler(RemoteMessage message) async { + if (settingsRepo.getNotification()) { + var data = message.data; + var notif = message.notification; + if (data.isNotEmpty) { + var title = (data[TITLE] != null) ? data[TITLE].toString() : appName; + var body = (data[MESSAGE] != null) ? data[MESSAGE].toString() : ""; + var image = data[IMAGE]; + var payload = data[NEWS_ID]; + String lanId = (data[LANGUAGE_ID] != null) ? data[LANGUAGE_ID].toString() : "1"; //1 = ENGLISH bydefault + (payload == null) ? payload = "" : payload = payload; + if ((data[TYPE] == "default" || data[TYPE] == "category" || data[TYPE] == "comment" || data[TYPE] == "comment_like" || data[TYPE] == "newlyadded")) { + if (lanId == context.read().state.id) { + //show only if Current language is Same as Notification Language + (image != null && image != "") ? generateImageNotification(title, body, image, payload) : generateSimpleNotification(title, body, payload); + } + } else if ((data[TYPE] == "author_approved") || (data[TYPE] == "author_rejected")) { + generateSimpleNotification(title, body, payload); + Future.delayed(Duration.zero, () { + context.read().getUserById(); // update Author status + }); + } + } else if (notif != null) { + //Direct Firebase Notification + RemoteNotification notification = notif; + String title = notif.title.toString(); + String msg = notif.body.toString(); + String iosImg = (notification.apple != null && notification.apple!.imageUrl != null) ? notification.apple!.imageUrl! : ""; + String androidImg = (notification.android != null && notification.android!.imageUrl != null) ? notification.android!.imageUrl! : ""; + if (title != '' && msg != '') { + if (Platform.isIOS) { + (iosImg != "") ? generateImageNotification(title, msg, notification.apple!.imageUrl!, '') : generateSimpleNotification(title, msg, ''); + } + if (Platform.isAndroid) { + (androidImg != "") ? generateImageNotification(title, msg, notification.android!.imageUrl!, '') : generateSimpleNotification(title, msg, ''); + } + } + } + } + } + + //for android 13 - notification permission + flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation()?.requestNotificationsPermission(); + + InitializationSettings initializationSettings = const InitializationSettings(android: initializationSettingsAndroid, iOS: initializationSettingsIOS); + + await flutterLocalNotificationsPlugin.initialize( + initializationSettings, + onDidReceiveNotificationResponse: (NotificationResponse notificationResponse) { + switch (notificationResponse.notificationResponseType) { + case NotificationResponseType.selectedNotification: + selectNotificationPayload(notificationResponse.payload!); + break; + case NotificationResponseType.selectedNotificationAction: + break; + } + }, + onDidReceiveBackgroundNotificationResponse: backgroundMessage, + ); + messaging.getInitialMessage().then((RemoteMessage? message) async { + if (message != null && message.data.isNotEmpty) { + isNotificationReceivedInbg = true; + notificationNewsId = message.data[NEWS_ID]; + saleNotification = message.data['sale'] ?? ''; + } + }); + + _startForegroundService(); + FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); + FirebaseMessaging.onMessage.listen((RemoteMessage message) async { + await notificationHandler(message); + }); + + FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { + redirectToNewsDetailsScreen(message, context); + }); + } + + Future _startForegroundService() async { + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails('YOUR_PACKAGE_NAME_HERE', 'news', channelDescription: 'your channel description', importance: Importance.max, priority: Priority.high, ticker: 'ticker'); + await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation() + ?.startForegroundService(1, 'plain title', 'plain body', notificationDetails: androidNotificationDetails, payload: ''); + } + + selectNotificationPayload(String? payload) async { + if (payload != null && payload.isNotEmpty && payload != "0") { + context.read().getNewsById(newsId: payload, langId: context.read().state.id).then((value) { + UiUtils.rootNavigatorKey.currentState!.pushNamed(Routes.newsDetails, arguments: {"model": value[0], "isFromBreak": false, "fromShowMore": false}); + }); + } + } +} + +Future _downloadAndSaveImage(String url, String fileName) async { + if (url.isNotEmpty && url != "null") { + final Directory directory = await getApplicationDocumentsDirectory(); + final String filePath = '${directory.path}/$fileName'; + final Response response = await get(Uri.parse(url)); + final File file = File(filePath); + await file.writeAsBytes(response.bodyBytes); + return filePath; + } else { + return ""; + } +} + +Future generateImageNotification(String title, String msg, String image, String type) async { + var largeIconPath = await _downloadAndSaveImage(image, Platform.isAndroid ? 'largeIcon' : 'largeIcon.png'); + var bigPicturePath = await _downloadAndSaveImage(image, Platform.isAndroid ? 'bigPicture' : 'bigPicture.png'); + var bigPictureStyleInformation = + BigPictureStyleInformation(FilePathAndroidBitmap(bigPicturePath), hideExpandedLargeIcon: true, contentTitle: title, htmlFormatContentTitle: true, summaryText: msg, htmlFormatSummaryText: true); + var androidPlatformChannelSpecifics = AndroidNotificationDetails('big text channel id', 'big text channel name', + channelDescription: 'big text channel description', largeIcon: FilePathAndroidBitmap(largeIconPath), styleInformation: bigPictureStyleInformation); + final DarwinNotificationDetails darwinNotificationDetails = DarwinNotificationDetails( + categoryIdentifier: "", presentAlert: true, presentSound: true, attachments: [DarwinNotificationAttachment(bigPicturePath, hideThumbnail: false)]); + var platformChannelSpecifics = NotificationDetails(android: androidPlatformChannelSpecifics, iOS: darwinNotificationDetails); + if (msg != "") await flutterLocalNotificationsPlugin.show(1, title, msg, platformChannelSpecifics, payload: type); +} + +Future generateSimpleNotification(String title, String msg, String type) async { + var androidPlatformChannelSpecifics = const AndroidNotificationDetails( + 'YOUR_PACKAGE_NAME_HERE', //your package name + 'news', + importance: Importance.max, + priority: Priority.high, + ticker: 'ticker'); + DarwinNotificationDetails darwinNotificationDetails = const DarwinNotificationDetails(categoryIdentifier: "", presentAlert: true, presentSound: true); + var platformChannelSpecifics = NotificationDetails(android: androidPlatformChannelSpecifics, iOS: darwinNotificationDetails); + if (msg.isNotEmpty) await flutterLocalNotificationsPlugin.show(1, title, msg, platformChannelSpecifics, payload: type); +} diff --git a/news-app/lib/ui/screens/RSSFeedDetailsScreen.dart b/news-app/lib/ui/screens/RSSFeedDetailsScreen.dart new file mode 100644 index 00000000..b88a8071 --- /dev/null +++ b/news-app/lib/ui/screens/RSSFeedDetailsScreen.dart @@ -0,0 +1,124 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/errorContainerWidget.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import 'package:news/utils/ErrorMessageKeys.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:xml2json/xml2json.dart'; +import 'dart:convert'; + +class RSSFeedDetailsScreen extends StatefulWidget { + String feedUrl; + + RSSFeedDetailsScreen({Key? key, required this.feedUrl}) : super(key: key); + + @override + _RSSFeedDetailsScreenState createState() => _RSSFeedDetailsScreenState(); + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute(builder: (_) => RSSFeedDetailsScreen(feedUrl: arguments['feedUrl'])); + } +} + +class _RSSFeedDetailsScreenState extends State { + List _feedItems = []; + bool isFeedLoaded = false; + + @override + void initState() { + super.initState(); + fetchFeed(); + } + + Widget buildFeedItem(Map item) { + return GestureDetector( + onTap: () => _launchURL(item['link'] ?? ''), + child: Card( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + margin: EdgeInsets.all(8.0), + child: Padding( + padding: EdgeInsets.all(16.0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ((item.containsKey('image') && item['image'] != null) || (item.containsKey('url') && item['image']['url'] != null)) + ? Padding( + padding: const EdgeInsetsDirectional.only(end: 10.0), + child: CustomNetworkImage( + networkImageUrl: (item['image'] != null) ? item['image'] : item['image']['url'], + height: MediaQuery.of(context).size.height * 0.13, + width: MediaQuery.of(context).size.width * 0.23, + fit: BoxFit.cover), + ) + : SizedBox.shrink(), + Expanded( + flex: 1, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomTextLabel(text: item['title'] ?? 'No Title', maxLines: 2, textStyle: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), + SizedBox(height: 8), + CustomTextLabel(text: item['description'] ?? 'No Description', maxLines: 3, textStyle: TextStyle(color: borderColor)), + SizedBox(height: 8), + CustomTextLabel(text: item['pubDate'] ?? 'Unknown Date', textStyle: TextStyle(fontSize: 12, color: Colors.grey[500])) + ], + ), + ), + ], + ), + ), + ), + ); + } + + Future _launchURL(String url) async { + if (await canLaunch(url)) { + await launch(url); + } else { + throw 'Could not launch $url'; + } + } + + Future fetchFeed() async { + final url = widget.feedUrl; + try { + final response = await http.get(Uri.parse(url)); + if (response.statusCode == 200) { + final xml2json = Xml2Json(); + xml2json.parse(response.body); + + final jsonString = xml2json.toParker(); + final jsonData = jsonDecode(jsonString); + + _feedItems = jsonData['rss']['channel']['item'] ?? []; + } else { + throw Exception('Failed to load feed'); + } + } catch (e) {} + isFeedLoaded = true; + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: RefreshIndicator( + onRefresh: fetchFeed, + child: !isFeedLoaded + ? Center(child: CircularProgressIndicator()) + : _feedItems.isEmpty + ? ErrorContainerWidget(errorMsg: ErrorMessageKeys.noDataMessage, onRetry: fetchFeed) + : ListView.builder( + itemCount: _feedItems.length, + itemBuilder: (context, index) { + return buildFeedItem(_feedItems[index]); + }, + )), + ); + } +} diff --git a/news-app/lib/ui/screens/RSSFeedScreen.dart b/news-app/lib/ui/screens/RSSFeedScreen.dart new file mode 100644 index 00000000..95baa493 --- /dev/null +++ b/news-app/lib/ui/screens/RSSFeedScreen.dart @@ -0,0 +1,335 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/categoryCubit.dart'; +import 'package:news/cubits/rssFeedCubit.dart'; +import 'package:news/data/models/CategoryModel.dart'; +import 'package:news/data/models/RSSFeedModel.dart'; +import 'package:news/ui/screens/AddEditNews/Widgets/customBottomsheet.dart'; +import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart'; +import 'package:news/ui/styles/colors.dart'; + +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/errorContainerWidget.dart'; +import 'package:news/utils/ErrorMessageKeys.dart'; +import 'package:news/utils/internetConnectivity.dart'; +import 'package:news/utils/uiUtils.dart'; + +class RSSFeedScreen extends StatefulWidget { + @override + RSSFeedScreenState createState() => RSSFeedScreenState(); +} + +class RSSFeedScreenState extends State { + final Set selectedTopics = {}; + final Random random = Random(); + late final ScrollController _controller = ScrollController()..addListener(hasMoreRssFeedScrollListener); + String? catSel = "", subCatSel = "", subCatSelId, catSelId; + int? catIndex, subCatIndex; + bool isFilter = true, showSubcat = false; + + @override + void initState() { + super.initState(); + getRSSFeed(); + } + + setAppBar() { + return PreferredSize( + preferredSize: const Size(double.infinity, 52), + child: UiUtils.applyBoxShadow( + context: context, + child: Padding( + padding: EdgeInsetsDirectional.only(top: MediaQuery.of(context).padding.top + 10.0, start: 25, end: 25, bottom: 15), + child: Row(children: [ + CustomTextLabel( + text: 'rssFeed', + textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600, letterSpacing: 0.5), + ), + Spacer(), + GestureDetector(child: Icon(Icons.filter_list_rounded), onTap: () => setCategorySubcategoryFilter(context)) + ]), + ))); + } + + setCategorySubcategoryFilter(BuildContext context) { + showModalBottomSheet( + context: context, + elevation: 5.0, + isScrollControlled: true, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.only(topLeft: Radius.circular(50), topRight: Radius.circular(50))), + builder: (BuildContext context) { + return StatefulBuilder(builder: (context, setBottomSheetState) { + return Container( + height: MediaQuery.of(context).size.height * 0.35, + padding: const EdgeInsets.only(top: 10.0, bottom: 5, left: 10, right: 10), + child: Column(children: [ + titleWithDropdownButton(), + const SizedBox(height: 5), + (isFilter) ? setCategoryFilter(setBottomSheetState) : SizedBox.shrink(), + (isFilter) ? setSubCategoryFilter(setBottomSheetState) : SizedBox.shrink(), + ])); + }); + }); + } + + void getRSSFeed() async { + if (await InternetConnectivity.isNetworkAvailable()) { + Future.delayed(Duration.zero, () { + context.read().getRSSFeed(langId: context.read().state.id, categoryId: catSelId, subCategoryId: subCatSelId); + }); + } + } + + void hasMoreRssFeedScrollListener() { + if (_controller.position.maxScrollExtent == _controller.offset) { + if (context.read().hasMoreRSSFeed()) { + context.read().getMoreRSSFeed(langId: context.read().state.id); + } + } + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + Widget titleWithDropdownButton() { + return InkWell( + onTap: () { + isFilter = !isFilter; + setState(() {}); + }, + child: Container( + width: double.maxFinite, + alignment: Alignment.topLeft, + padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 15), + child: Row( + children: [ + CustomTextLabel(text: 'FilterBy', textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.bold, color: UiUtils.getColorScheme(context).primaryContainer)), + Spacer(), + if (catSelId != null || subCatSelId != null) (isFilter) ? clearFilterButton() : SizedBox.shrink(), + ], + ), + )); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: setAppBar(), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: BlocBuilder( + builder: (context, state) { + if (state is RSSFeedFetchSuccess) { + return RefreshIndicator( + onRefresh: () async { + getRSSFeed(); + }, + child: ListView.builder( + padding: EdgeInsets.only(top: 15), + itemCount: state.RSSFeed.length, + controller: _controller, + itemBuilder: (context, index) { + return Container( + alignment: Alignment.center, + margin: EdgeInsets.symmetric(vertical: 5), + child: buildRSSFeedItem(state.RSSFeed[index], index, state.RSSFeed.length, state.hasMoreFetchError, state.hasMore)); + }), + ); + } + if (state is RSSFeedFetchFailure) { + return ErrorContainerWidget( + errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage, + onRetry: getRSSFeed); //Add category & subcategory id if selected + } + if (state is RSSFeedFetchInProgress || state is RSSFeedInitial) { + return Center(child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)); + } + return SizedBox.shrink(); + }, + ), + ), + ); + } + + Widget buildRSSFeedItem(RSSFeedModel feed, int index, int totalCurrentFeeds, bool hasMoreFeedsFetchError, bool hasMore) { + if (index == totalCurrentFeeds - 1 && index != 0) { + if (hasMore) { + if (hasMoreFeedsFetchError) { + return const SizedBox.shrink(); + } else { + return Center(child: Padding(padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0), child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor))); + } + } + } + return GestureDetector( + onTap: () async { + Navigator.of(context).pushNamed(Routes.rssFeedDetails, arguments: {"feedUrl": feed.feedUrl}); + }, + child: Container( + height: MediaQuery.of(context).size.height * 0.08, + decoration: BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(10)), border: Border.all(color: borderColor.withAlpha((0.3 * 255).round()))), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Center(child: SvgPictureWidget(assetName: 'rss_feed', height: 20, width: 10, fit: BoxFit.fill)), + const SizedBox(width: 12), + Expanded( + child: Align( + alignment: Alignment.centerLeft, + child: Text(feed.feedName!, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: TextStyle(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.bold, fontSize: 16), + textAlign: TextAlign.start), + ), + ) + ]), + ), + ), + ); + } + + Widget setCategoryFilter(void Function(void Function()) setStater) { + return BlocConsumer(listener: (context, state) { + if (catSel != "") catIndex = context.read().getCategoryIndex(categoryName: catSel!); + }, builder: (context, state) { + if (state is CategoryFetchSuccess) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 5), + child: InkWell( + onTap: () => showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) { + return CustomBottomsheet( + context: context, + titleTxt: 'selCatLbl', + language_id: context.read().state.id, + listLength: context.read().getCatList().length, + listViewChild: (context, index) { + return catListItem(index, context.read().getCatList(), setStater); + }); + }), + child: UiUtils.setRowWithContainer( + context: context, + firstChild: CustomTextLabel( + text: (catSel == "" || catSel == null) ? 'catLbl' : catSel!, + textStyle: Theme.of(context) + .textTheme + .titleMedium! + .copyWith(color: catSel == "" ? UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6) : UiUtils.getColorScheme(context).primaryContainer)), + isContentTypeUpload: false), + ), + ); + } + return SizedBox.shrink(); + }); + } + + Widget catListItem(int index, List catList, void Function(void Function()) setStater) { + return Padding( + padding: EdgeInsets.only(top: 10.0), + child: InkWell( + onTap: () { + setState(() { + subCatSel = ""; + subCatSelId = null; + catSel = catList[index].categoryName!; + catIndex = index; + catSelId = catList[index].id!; + showSubcat = true; + }); + setStater(() {}); + getRSSFeed(); + Navigator.pop(context); + }, + child: UiUtils.setBottomsheetContainer(entryId: catSelId ?? "0", listItem: catList[index].categoryName!, compareTo: catList[index].id!, context: context), + ), + ); + } + + Widget subCatListItem(int index, List catList, void Function(void Function()) setStater) { + return Padding( + padding: EdgeInsets.only(top: 10.0), + child: InkWell( + onTap: () { + setState(() { + subCatSel = catList[catIndex!].subData![index].subCatName!; + subCatSelId = catList[catIndex!].subData![index].id!; + }); + setStater(() {}); + getRSSFeed(); + Navigator.pop(context); + }, + child: UiUtils.setBottomsheetContainer( + entryId: subCatSelId ?? "0", listItem: catList[catIndex!].subData![index].subCatName!, compareTo: catList[catIndex!].subData![index].id!, context: context)), + ); + } + + Widget setSubCategoryFilter(void Function(void Function()) setStater) { + if ((catSel != "" && catSel != null) && + (catIndex != null) && + (!catIndex!.isNegative && context.read().getCatList().isNotEmpty) && + (context.read().getCatList()[catIndex!].subData!.isNotEmpty)) { + return BlocBuilder( + builder: (context, state) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 5), + child: InkWell( + onTap: () => showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) { + return CustomBottomsheet( + context: context, + titleTxt: 'selSubCatLbl', + language_id: context.read().state.id, + listLength: context.read().getCatList()[catIndex!].subData!.length, + listViewChild: (context, index) => subCatListItem(index, context.read().getCatList(), setStater)); + }), + child: UiUtils.setRowWithContainer( + context: context, + firstChild: CustomTextLabel( + text: (subCatSel == "" || subCatSel == null) ? 'subcatLbl' : subCatSel!, + textStyle: Theme.of(context) + .textTheme + .titleMedium! + .copyWith(color: subCatSel == "" ? UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6) : UiUtils.getColorScheme(context).primaryContainer)), + isContentTypeUpload: false), + ), + ); + }, + ); + } else { + return const SizedBox.shrink(); + } + } + + Widget clearFilterButton() { + return Center( + child: TextButton( + onPressed: () { + setState(() { + catSelId = null; + catSel = null; + subCatSelId = null; + subCatSel = null; + showSubcat = false; + }); + getRSSFeed(); //call API without Ids + Navigator.of(context).pop(); + }, + child: CustomTextLabel( + text: 'clearFilter', + textStyle: TextStyle(decoration: TextDecoration.underline, decorationThickness: 2), + ))); + } +} diff --git a/news-app/lib/ui/screens/Search.dart b/news-app/lib/ui/screens/Search.dart new file mode 100644 index 00000000..317bf5e4 --- /dev/null +++ b/news-app/lib/ui/screens/Search.dart @@ -0,0 +1,488 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; +import 'package:news/cubits/categoryCubit.dart'; +import 'package:news/cubits/tagCubit.dart'; +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/models/CategoryModel.dart'; +import 'package:news/data/models/TagModel.dart'; +import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart'; +import 'package:news/ui/screens/filter/FilterBottomSheet.dart'; +import 'package:news/ui/screens/filter/widgets/duration_filter_widget.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; +import 'package:news/ui/widgets/customBackBtn.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/hiveBoxKeys.dart'; +import 'package:news/utils/api.dart'; +import 'package:news/utils/strings.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/utils/internetConnectivity.dart'; +import 'dart:async'; + +class Search extends StatefulWidget { + const Search({super.key}); + + @override + SearchState createState() => SearchState(); +} + +bool buildResult = false; //used in 2 classes here _SearchState & _SuggestionList + +class SearchState extends State with TickerProviderStateMixin { + final TextEditingController _controller = TextEditingController(); + final GlobalKey _scaffoldKey = GlobalKey(); + int pos = 0; + List searchList = []; + final List _controllerList = []; + bool isNetworkAvail = true; + + String query = ""; + int notificationoffset = 0; + ScrollController? notificationcontroller; + bool notificationisloadmore = true, notificationisgettingdata = false, notificationisnodata = false; + + // Filter related variables + bool isFilterApplied = false; + List filterSelectedCategories = []; + List filterSelectedTags = []; + DateTime? filterSelectedDate; + DurationFilter? filterDurationFilter; + + Timer? _debounce; + List history = []; + + List hisList = []; + + // Apply filters to search results + void applyFilters() { + notificationoffset = 0; + + getSearchNews(isFilter: true); + } + + @override + void initState() { + super.initState(); + searchList.clear(); + + // Load categories and tags if not already loaded + context.read().loadIfFailed(langId: context.read().state.id); + context.read().loadIfFailed(langId: context.read().state.id); + + notificationoffset = 0; + + notificationcontroller = ScrollController(keepScrollOffset: true); + notificationcontroller!.addListener(_searchScrollListener); + + _controller.addListener(() { + if (_controller.text.isEmpty) { + if (mounted) { + setState(() { + query = ""; + }); + } + } else { + query = _controller.text.trim(); + notificationoffset = 0; + notificationisnodata = false; + buildResult = false; + if (query.isNotEmpty) { + if (_debounce?.isActive ?? false) _debounce!.cancel(); + _debounce = Timer(const Duration(milliseconds: 500), () { + notificationisloadmore = true; + notificationoffset = 0; + getSearchNews(); + }); + } + } + }); + } + + _searchScrollListener() { + if (notificationcontroller!.offset >= notificationcontroller!.position.maxScrollExtent && !notificationcontroller!.position.outOfRange) { + if (mounted) { + setState(() { + getSearchNews(); + }); + } + } + } + + Future> getHistory() async { + hisList = UiUtils.getDynamicListValue(historyListKey); + return hisList; + } + + @override + void dispose() { + notificationcontroller!.dispose(); + _controller.dispose(); + for (int i = 0; i < _controllerList.length; i++) { + _controllerList[i].dispose(); + } + + super.dispose(); + } + + PreferredSizeWidget appbar() { + return AppBar( + leading: const CustomBackButton(horizontalPadding: 15), + backgroundColor: Theme.of(context).canvasColor, + title: TextField( + controller: _controller, + autofocus: true, + decoration: InputDecoration( + contentPadding: const EdgeInsets.fromLTRB(0, 15.0, 0, 15.0), + hintText: UiUtils.getTranslatedLabel(context, 'search'), + hintStyle: TextStyle(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)), + enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: UiUtils.getColorScheme(context).primaryContainer)), + focusedBorder: UnderlineInputBorder(borderSide: BorderSide(color: UiUtils.getColorScheme(context).primaryContainer)), + fillColor: secondaryColor)), + titleSpacing: 0, + actions: [ + IconButton( + onPressed: () { + _controller.text = ''; + }, + icon: Icon(Icons.close, color: UiUtils.getColorScheme(context).primaryContainer)), + IconButton( + padding: EdgeInsetsDirectional.only(end: 7), + onPressed: () async { + FocusManager.instance.rootScope.unfocus(); + final NewsFilterData? result = await showFilterBottomSheet( + context: context, + isCategoryModeON: (context.read().getCategoryMode() == "1"), + initialFilters: NewsFilterData( + selectedCategories: filterSelectedCategories, + selectedTags: filterSelectedTags, + selectedDate: filterSelectedDate, + durationFilter: filterDurationFilter, + ), + ); + + if (result != null) { + setState(() { + buildResult = true; + isFilterApplied = true; + filterSelectedCategories = result.selectedCategories; + filterSelectedTags = result.selectedTags; + filterSelectedDate = result.selectedDate; + filterDurationFilter = result.durationFilter; + applyFilters(); + }); + } else if (result == null) { + setState(() { + buildResult = false; + isFilterApplied = false; + filterSelectedCategories = []; + filterSelectedTags = []; + filterSelectedDate = null; + filterDurationFilter = null; + }); + } + }, + icon: Icon( + Icons.filter_list, + color: UiUtils.getColorScheme(context).primaryContainer, + )) + ], + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + key: _scaffoldKey, + appBar: appbar(), + body: _showContent(), + ); + } + + Widget listItem(int index) { + if (_controllerList.length < index + 1) { + _controllerList.add(TextEditingController()); + } + return Padding( + padding: const EdgeInsetsDirectional.only(bottom: 7.0), + child: ListTile( + title: CustomTextLabel( + text: searchList[index].title!, + textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.bold), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + leading: ClipRRect( + borderRadius: BorderRadius.circular(7.0), child: CustomNetworkImage(networkImageUrl: searchList[index].image ?? "", height: 80, width: 80, fit: BoxFit.cover, isVideo: false)), + onTap: () async { + FocusScope.of(context).requestFocus(FocusNode()); + List addNewsList = []; + addNewsList.addAll(searchList); + addNewsList.removeAt(index); + UiUtils.showInterstitialAds(context: context); + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": searchList[index], "newsList": addNewsList, "isFromBreak": false, "fromShowMore": false}); + })); + } + + Future getSearchNews({bool? isFilter}) async { + if (await InternetConnectivity.isNetworkAvailable()) { + try { + String latitude = SettingsLocalDataRepository().getLocationCityValues().first; + String longitude = SettingsLocalDataRepository().getLocationCityValues().last; + + if (notificationisloadmore || (isFilter ?? false)) { + if (mounted) { + setState(() { + notificationisloadmore = false; + notificationisgettingdata = true; + if (notificationoffset == 0) { + searchList = []; + } + }); + } + + var parameter = { + if (query.isNotEmpty) SEARCH: query.trim(), + LIMIT: "20", + OFFSET: notificationoffset.toString(), + LANGUAGE_ID: context.read().state.id, + }; + if (latitude != "null" && longitude != "null") { + parameter[LATITUDE] = latitude; + parameter[LONGITUDE] = longitude; + } + + // Apply filters if present + if (filterSelectedCategories.isNotEmpty) { + parameter[CATEGORY_ID] = filterSelectedCategories.map((cat) => cat.id).join(','); + } + if (filterSelectedTags.isNotEmpty) { + parameter[TAG_ID] = filterSelectedTags.map((tag) => tag.id).join(','); + } + if (filterSelectedDate != null) { + parameter[DATE] = "${filterSelectedDate!.year}-${filterSelectedDate!.month.toString().padLeft(2, '0')}-${filterSelectedDate!.day.toString().padLeft(2, '0')}"; + } + if (filterDurationFilter != null) { + ///To map will be called so automatic it will add the parm of selected type + parameter.addAll(filterDurationFilter!.toMap()); + } + + final result = await Api.sendApiRequest(body: parameter, url: Api.getNewsApi); + + bool error = result["error"]; + int totalResult = result[TOTAL]; + notificationisgettingdata = false; + if (notificationoffset == 0) { + if (error == false && totalResult > 0) { + notificationisnodata = false; + } else { + notificationisnodata = true; + } + } + if (error == false && totalResult > 0) { + if (mounted) { + Future.delayed( + Duration.zero, + () => setState(() { + List mainlist = result[DATA]; + if (mainlist.isNotEmpty) { + List items = []; + List allItems = []; + items.addAll(mainlist.map((data) => NewsModel.fromJson(data)).toList()); + allItems.addAll(items); + + if (notificationoffset == 0 && (isFilter ?? !buildResult)) { + NewsModel element = NewsModel(title: '${UiUtils.getTranslatedLabel(context, 'searchForLbl')} "$query"', image: "", history: false); + searchList.insert(0, element); + for (int i = 0; i < history.length; i++) { + if (history[i].title == query) { + searchList.insert(0, history[i]); + } + } + } + + for (NewsModel item in items) { + searchList.where((i) => i.id == item.id).map((obj) { + allItems.remove(item); + return obj; + }).toList(); + } + searchList.addAll(allItems); + notificationisloadmore = false; + notificationoffset = notificationoffset + limitOfAPIData; + } else { + notificationisloadmore = false; + } + })); + } + } else { + notificationisloadmore = false; + setState(() {}); + } + } + } on TimeoutException catch (_) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'somethingMSg'), context); + setState(() { + notificationisloadmore = false; + }); + } catch (e) { + setState(() { + notificationisnodata = true; + notificationisloadmore = false; + }); + } + } else { + setState(() { + isNetworkAvail = false; + }); + } + } + + clearAll() { + setState(() { + query = _controller.text; + notificationoffset = 0; + notificationisloadmore = true; + searchList.clear(); + }); + } + + _showContent() { + if (!isFilterApplied && _controller.text == "") { + return FutureBuilder>( + future: getHistory(), + builder: (BuildContext context, AsyncSnapshot> snapshot) { + if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) { + final List entities = snapshot.data!; + final List itemList = []; + for (int i = 0; i < entities.length; i++) { + NewsModel item = NewsModel.history(entities[i]); + itemList.add(item); + } + history.clear(); + history.addAll(itemList); + + return SingleChildScrollView( + padding: const EdgeInsetsDirectional.only(top: 15.0), + child: Column( + children: [ + _SuggestionList( + textController: _controller, + suggestions: itemList, + notificationController: notificationcontroller, + getProduct: getSearchNews, + clearAll: clearAll, + ), + ], + ), + ); + } else { + return const Column(); + } + }); + } else if (buildResult) { + return notificationisnodata + ? const Center(child: CustomTextLabel(text: 'noNews')) + : Padding( + padding: const EdgeInsetsDirectional.only(top: 15.0), + child: Column( + children: [ + Expanded( + child: ListView.builder( + padding: const EdgeInsetsDirectional.only(bottom: 5, start: 10, end: 10, top: 12), + controller: notificationcontroller, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: searchList.length, + itemBuilder: (context, index) { + NewsModel? item; + try { + item = searchList.isEmpty ? null : searchList[index]; + if (notificationisloadmore && index == (searchList.length - 1) && notificationcontroller!.position.pixels <= 0) { + getSearchNews(); + } + } on Exception catch (_) {} + return item == null ? const SizedBox.shrink() : listItem(index); + }), + ), + notificationisgettingdata ? const Padding(padding: EdgeInsetsDirectional.only(top: 5, bottom: 5), child: CircularProgressIndicator()) : const SizedBox.shrink() + ], + )); + } + return notificationisnodata + ? const Center(child: CustomTextLabel(text: 'noNews')) + : Padding( + padding: const EdgeInsetsDirectional.only(top: 15.0), + child: Column( + children: [ + Expanded(child: _SuggestionList(textController: _controller, suggestions: searchList, notificationController: notificationcontroller, getProduct: getSearchNews, clearAll: clearAll)), + notificationisgettingdata ? const Padding(padding: EdgeInsetsDirectional.only(top: 5, bottom: 5), child: CircularProgressIndicator()) : const SizedBox.shrink() + ], + )); + } +} + +class _SuggestionList extends StatelessWidget { + const _SuggestionList({this.suggestions, this.textController, this.notificationController, this.getProduct, this.clearAll}); + + final List? suggestions; + final TextEditingController? textController; + + final notificationController; + final Function? getProduct, clearAll; + + @override + Widget build(BuildContext context) { + return ListView.separated( + itemCount: suggestions!.length, + shrinkWrap: true, + controller: notificationController, + separatorBuilder: (BuildContext context, int index) => const Divider(), + itemBuilder: (BuildContext context, int i) { + final NewsModel suggestion = suggestions![i]; + + return ListTile( + title: CustomTextLabel( + text: suggestion.title!, + textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.bold), + maxLines: 2, + overflow: TextOverflow.ellipsis), + leading: textController!.text.toString().trim().isEmpty || suggestion.history! + ? const Icon(Icons.history) + : ClipRRect( + borderRadius: BorderRadius.circular(7.0), + child: suggestion.image == '' + ? Image.asset(UiUtils.getPlaceholderPngPath(), height: 80, width: 80) + : CustomNetworkImage(networkImageUrl: suggestion.image!, height: 80, width: 80, fit: BoxFit.cover, isVideo: false)), + trailing: SvgPictureWidget( + assetName: "searchbar_arrow", + height: 11, + width: 11, + fit: BoxFit.contain, + assetColor: ColorFilter.mode(UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), BlendMode.srcIn)), + onTap: () async { + if (suggestion.title!.startsWith('${UiUtils.getTranslatedLabel(context, 'searchForLbl')} ')) { + UiUtils.setDynamicListValue(historyListKey, textController!.text.toString().trim()); + buildResult = true; + clearAll!(); + getProduct!(); + } else if (suggestion.history!) { + clearAll!(); + buildResult = true; + textController!.text = suggestion.title!; + textController!.selection = TextSelection.fromPosition(TextPosition(offset: textController!.text.length)); + } else { + UiUtils.setDynamicListValue(historyListKey, textController!.text.trim()); + buildResult = false; + UiUtils.showInterstitialAds(context: context); + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": suggestion, "isFromBreak": false, "fromShowMore": false}); + } + }); + }, + ); + } +} diff --git a/news-app/lib/ui/screens/SectionMoreNews/SectionMoreBreakNewsList.dart b/news-app/lib/ui/screens/SectionMoreNews/SectionMoreBreakNewsList.dart new file mode 100644 index 00000000..d3bb6197 --- /dev/null +++ b/news-app/lib/ui/screens/SectionMoreNews/SectionMoreBreakNewsList.dart @@ -0,0 +1,104 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; +import 'package:news/ui/widgets/breakingVideoItem.dart'; +import 'package:news/ui/widgets/errorContainerWidget.dart'; +import 'package:news/ui/widgets/shimmerNewsList.dart'; +import 'package:news/utils/ErrorMessageKeys.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/sectionByIdCubit.dart'; +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/ui/widgets/breakingNewsItem.dart'; +import 'package:news/ui/widgets/customAppBar.dart'; + +class SectionMoreBreakingNewsList extends StatefulWidget { + final String sectionId; + final String title; + + const SectionMoreBreakingNewsList({super.key, required this.sectionId, required this.title}); + + @override + State createState() { + return _SectionBreakingNewsState(); + } + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute(builder: (_) => SectionMoreBreakingNewsList(sectionId: arguments['sectionId'], title: arguments['title'])); + } +} + +class _SectionBreakingNewsState extends State { + late final ScrollController controller = ScrollController()..addListener(hasMoreSectionScrollListener); + Set get locationValue => SettingsLocalDataRepository().getLocationCityValues(); + + @override + void initState() { + getSectionByData(); + super.initState(); + } + + void getSectionByData() { + Future.delayed(Duration.zero, () { + context.read().getSectionById(langId: context.read().state.id, sectionId: widget.sectionId, latitude: locationValue.first, longitude: locationValue.last); + }); + } + + void hasMoreSectionScrollListener() { + if (controller.position.maxScrollExtent == controller.offset) { + if (context.read().hasMoreSections() && !(context.read().state is SectionByIdFetchInProgress)) { + context.read().getMoreSectionById(langId: context.read().state.id, sectionId: widget.sectionId); + } + } + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + _buildSectionBreakingNewsContainer({required BreakingNewsModel model, required String type, required int index, required List newsList}) { + return type == 'breaking_news' ? BreakNewsItem(model: model, index: index, breakNewsList: newsList) : BreakVideoItem(model: model); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: CustomAppBar(height: 45, isBackBtn: true, label: widget.title, horizontalPad: 15, isConvertText: false), + body: BlocBuilder( + builder: (context, state) { + if (state is SectionByIdFetchSuccess) { + return Padding( + padding: const EdgeInsetsDirectional.all(10.0), + child: RefreshIndicator( + onRefresh: () async { + context + .read() + .getSectionById(sectionId: widget.sectionId, langId: context.read().state.id, latitude: locationValue.first, longitude: locationValue.last); + }, + child: ListView.builder( + controller: controller, + physics: const AlwaysScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: state.breakNewsModel.length, + itemBuilder: (context, index) { + return _buildSectionBreakingNewsContainer( + model: (state).breakNewsModel[index], type: (state).type, index: index, newsList: (state).type == 'breaking_news' ? state.breakNewsModel : []); + }), + ), + ); + } + if (state is SectionByIdFetchFailure) { + return ErrorContainerWidget( + errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage, onRetry: getSectionByData); + } + //state is SectionByIdFetchInProgress || state is SectionByIdInitial + return ShimmerNewsList(isNews: false); + }, + ), + ); + } +} diff --git a/news-app/lib/ui/screens/SectionMoreNews/SectionMoreNewsList.dart b/news-app/lib/ui/screens/SectionMoreNews/SectionMoreNewsList.dart new file mode 100644 index 00000000..bad6e4a9 --- /dev/null +++ b/news-app/lib/ui/screens/SectionMoreNews/SectionMoreNewsList.dart @@ -0,0 +1,104 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/sectionByIdCubit.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; +import 'package:news/ui/widgets/NewsItem.dart'; +import 'package:news/ui/widgets/customAppBar.dart'; +import 'package:news/ui/widgets/errorContainerWidget.dart'; +import 'package:news/utils/ErrorMessageKeys.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/ui/widgets/shimmerNewsList.dart'; +import 'package:news/ui/widgets/videoItem.dart'; + +class SectionMoreNewsList extends StatefulWidget { + final String sectionId; + final String title; + + const SectionMoreNewsList({super.key, required this.sectionId, required this.title}); + + @override + State createState() { + return _SectionNewsState(); + } + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute(builder: (_) => SectionMoreNewsList(sectionId: arguments['sectionId'], title: arguments['title'])); + } +} + +class _SectionNewsState extends State { + late final ScrollController controller = ScrollController()..addListener(hasMoreSectionScrollListener); + Set get locationValue => SettingsLocalDataRepository().getLocationCityValues(); + + @override + void initState() { + getSectionByData(); + super.initState(); + } + + void getSectionByData() { + Future.delayed(Duration.zero, () { + context.read().getSectionById(langId: context.read().state.id, sectionId: widget.sectionId, latitude: locationValue.first, longitude: locationValue.last); + }); + } + + void hasMoreSectionScrollListener() { + if (controller.position.maxScrollExtent == controller.offset) { + if (context.read().hasMoreSections() && !(context.read().state is SectionByIdFetchInProgress)) { + context.read().getMoreSectionById(langId: context.read().state.id, sectionId: widget.sectionId); + } + } + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + _buildSectionNewsContainer({required NewsModel model, required String type, required int index, required List newsList}) { + return (type == 'news' || type == 'user_choice') ? NewsItem(model: model, index: index, newslist: newsList, fromShowMore: false) : VideoItem(model: model); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: CustomAppBar(height: 45, isBackBtn: true, label: widget.title, horizontalPad: 15, isConvertText: false), + body: BlocBuilder( + builder: (context, state) { + if (state is SectionByIdFetchSuccess) { + return Padding( + padding: const EdgeInsetsDirectional.symmetric(vertical: 10), + child: RefreshIndicator( + onRefresh: () async { + context + .read() + .getSectionById(sectionId: widget.sectionId, langId: context.read().state.id, latitude: locationValue.first, longitude: locationValue.last); + }, + child: ListView.builder( + controller: controller, + physics: const AlwaysScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: state.newsModel.length, + itemBuilder: (context, index) { + return _buildSectionNewsContainer( + model: (state).newsModel[index], type: (state).type, index: index, newsList: ((state).type == 'news' || state.type == 'user_choice') ? state.newsModel : []); + }), + ), + ); + } + if (state is SectionByIdFetchFailure) { + return ErrorContainerWidget( + errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage, onRetry: getSectionByData); + } + //state is SectionByIdFetchInProgress || state is SectionByIdInitial + return ShimmerNewsList(isNews: true); + }, + ), + ); + } +} diff --git a/news-app/lib/ui/screens/SubCategory/SubCategoryScreen.dart b/news-app/lib/ui/screens/SubCategory/SubCategoryScreen.dart new file mode 100644 index 00000000..6fd672d4 --- /dev/null +++ b/news-app/lib/ui/screens/SubCategory/SubCategoryScreen.dart @@ -0,0 +1,138 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/customBackBtn.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/shimmerNewsList.dart'; +import 'package:news/ui/screens/SubCategory/Widgets/SubCatNewsList.dart'; +import 'package:news/ui/screens/SubCategory/Widgets/categoryShimmer.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/cubits/subCategoryCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; +import 'package:news/cubits/subCatNewsCubit.dart'; +import 'package:news/data/repositories/SubCatNews/subCatRepository.dart'; + +class SubCategoryScreen extends StatefulWidget { + final String catId; + final String catName; + + const SubCategoryScreen({super.key, required this.catId, required this.catName}); + + @override + SubCategoryScreenState createState() => SubCategoryScreenState(); + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute(builder: (_) => SubCategoryScreen(catId: arguments['catId'], catName: arguments['catName'])); + } +} + +class SubCategoryScreenState extends State with TickerProviderStateMixin { + final List> _tabs = []; + TabController? _tc; + bool isSubcatExist = true; + + @override + void initState() { + getSubCatData(); + super.initState(); + } + + @override + void dispose() { + if (_tc != null) { + _tc!.dispose(); + } + super.dispose(); + } + + Future getSubCatData() async { + Future.delayed(Duration.zero, () { + context.read().getSubCategory(context: context, langId: context.read().state.id, catId: widget.catId); + }); + } + + Widget subcatData() { + return BlocConsumer( + bloc: context.read(), + listener: (context, state) { + if (state is SubCategoryFetchSuccess) { + setState(() { + for (int i = 0; i < state.subCategory.length; i++) { + _tabs.add({'text': state.subCategory[i].subCatName, 'subCatId': state.subCategory[i].id}); + } + _tc = TabController(vsync: this, length: state.subCategory.length)..addListener(() {}); + }); + } + if (state is SubCategoryFetchFailure) { + isSubcatExist = false; + setState(() {}); + } + }, + builder: (context, state) { + if (state is SubCategoryFetchSuccess) { + return DefaultTabController( + length: state.subCategory.length, + child: _tabs.isNotEmpty + ? TabBar( + controller: _tc, + labelColor: secondaryColor, + indicatorColor: Colors.transparent, + isScrollable: true, + padding: const EdgeInsets.only(top: 10, bottom: 5, left: 3, right: 3), + physics: const AlwaysScrollableScrollPhysics(), + unselectedLabelColor: UiUtils.getColorScheme(context).primaryContainer, + indicator: BoxDecoration(borderRadius: BorderRadius.circular(5.0), color: UiUtils.getColorScheme(context).secondaryContainer), + tabs: _tabs + .map((tab) => AnimatedContainer( + height: 30, duration: const Duration(milliseconds: 600), padding: const EdgeInsetsDirectional.only(top: 5.0, bottom: 5.0), child: Tab(text: tab['text']))) + .toList()) + : subcatShimmer()); + } + if (state is SubCategoryFetchFailure) { + return const SizedBox.shrink(); + } + //state is SubCategoryFetchInProgress || state is SubCategoryInitial + return subcatShimmer(); + }); + } + + setAppBar() { + return PreferredSize( + preferredSize: Size(double.infinity, (isSubcatExist) ? 80 : 40), + child: AppBar( + leading: const CustomBackButton(horizontalPadding: 15), + titleSpacing: 0.0, + centerTitle: false, + backgroundColor: Colors.transparent, + title: CustomTextLabel( + text: widget.catName, + textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600, letterSpacing: 0.5)), + bottom: PreferredSize(preferredSize: Size(MediaQuery.of(context).size.width, 30), child: subcatData()), + )); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: setAppBar(), + body: BlocBuilder(builder: (context, state) { + if (state is SubCategoryFetchFailure || context.read().getSubCatMode() != "1") { + return BlocProvider(create: (_) => SubCatNewsCubit(SubCatNewsRepository()), child: SubCatNewsList(from: 1, catId: widget.catId)); + } + if (state is SubCategoryFetchSuccess && _tc != null && _tc!.length > 0) { + return TabBarView( + controller: _tc, + children: List.generate(_tc!.length, (int index) { + return BlocProvider( + create: (_) => SubCatNewsCubit(SubCatNewsRepository()), + child: SubCatNewsList(from: index == 0 ? 1 : 2, subCatId: index == 0 ? null : _tabs[index]['subCatId'], catId: widget.catId)); + })); + } + return ShimmerNewsList(isNews: true); + }), + ); + } +} diff --git a/news-app/lib/ui/screens/SubCategory/Widgets/SubCatNewsList.dart b/news-app/lib/ui/screens/SubCategory/Widgets/SubCatNewsList.dart new file mode 100644 index 00000000..008b43ab --- /dev/null +++ b/news-app/lib/ui/screens/SubCategory/Widgets/SubCatNewsList.dart @@ -0,0 +1,197 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/subCatNewsCubit.dart'; +import 'package:news/cubits/surveyQuestionCubit.dart'; +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; +import 'package:news/ui/widgets/NewsItem.dart'; +import 'package:news/ui/screens/SubCategory/Widgets/showSurveyQuestion.dart'; +import 'package:news/ui/screens/SubCategory/Widgets/showSurveyResult.dart'; +import 'package:news/ui/widgets/errorContainerWidget.dart'; +import 'package:news/ui/widgets/shimmerNewsList.dart'; +import 'package:news/utils/ErrorMessageKeys.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/utils/uiUtils.dart'; + +class SubCatNewsList extends StatefulWidget { + final int from; + final String catId; + final String? subCatId; + + const SubCatNewsList({super.key, required this.from, required this.catId, this.subCatId}); + + @override + SubCatNewsListState createState() => SubCatNewsListState(); + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute(builder: (_) => SubCatNewsList(from: arguments['from'], catId: arguments['catId'], subCatId: arguments['subCatId'])); + } +} + +class SubCatNewsListState extends State with AutomaticKeepAliveClientMixin { + List combineList = []; + int totalSurveyQue = 0; + Set get locationValue => SettingsLocalDataRepository().getLocationCityValues(); + late final ScrollController controller = ScrollController()..addListener(hasMoreNotiScrollListener); + + @override + void initState() { + getSubCatNewsData(); + super.initState(); + } + + @override + void dispose() { + totalSurveyQue = 0; + controller.dispose(); + super.dispose(); + } + + Future getSubCatNewsData() async { + Future.delayed(Duration.zero, () { + if (context.read().getUserId() != "0") { + context.read().getSurveyQuestion(langId: context.read().state.id).whenComplete(() { + getSubcategoryNews(); + }); + } else { + getSubcategoryNews(); + } + }); + } + + void hasMoreNotiScrollListener() { + if (controller.position.maxScrollExtent == controller.offset) { + if (context.read().hasMoreSubCatNews()) { + context.read().getMoreSubCatNews( + langId: context.read().state.id, + subCatId: widget.from == 2 ? widget.subCatId : null, + catId: widget.from == 1 ? widget.catId : null, + latitude: locationValue.first, + longitude: locationValue.last); + } else {} + } + } + + updateCombineList(NewsModel model, int index) { + setState(() { + combineList[index] = model; + }); + } + + Widget getNewsList() { + return BlocConsumer( + bloc: context.read(), + listener: (context, state) { + if (state is SubCatNewsFetchSuccess) { + combineList.clear(); + int cur = 0; + for (int i = 0; i < (state).subCatNews.length; i++) { + if (i != 0 && i % surveyShow == 0) { + if (context.read().surveyList().isNotEmpty && context.read().surveyList().length > cur) { + combineList.add(context.read().surveyList()[cur]); + cur++; + } + } + combineList.add((state).subCatNews[i]); + } + } + }, + builder: (context, stateSubCat) { + if (stateSubCat is SubCatNewsFetchSuccess) { + return combineNewsList(stateSubCat, stateSubCat.subCatNews); + } + if (stateSubCat is SubCatNewsFetchFailure) { + return ErrorContainerWidget( + errorMsg: (stateSubCat.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : stateSubCat.errorMessage, onRetry: getSubCatNewsData); + } + //stateSubCat is SubCatNewsFetchInProgress || stateSubCat is SubCatNewsInitial + return ShimmerNewsList(isNews: true); + }); + } + + setTotalSurveyQueCount() { + for (var element in combineList) { + if (element.type == "survey") totalSurveyQue += 1; + } + } + + Widget combineNewsList(SubCatNewsFetchSuccess state, List newsList) { + setTotalSurveyQueCount(); + return RefreshIndicator( + onRefresh: () async { + combineList.clear(); + if (context.read().getUserId() != "0") { + context.read().getSurveyQuestion(langId: context.read().state.id).whenComplete(() { + getSubcategoryNews(); + }); + } else { + getSubcategoryNews(); + } + setTotalSurveyQueCount(); + setState(() {}); + }, + child: ListView.builder( + padding: const EdgeInsetsDirectional.symmetric(vertical: 15), + physics: const AlwaysScrollableScrollPhysics(), + controller: controller, + itemCount: combineList.length, + itemBuilder: (context, index) { + return _buildNewsContainer( + model: combineList[index], hasMore: state.hasMore, hasMoreNewsFetchError: state.hasMoreFetchError, index: index, totalCurrentNews: combineList.length, newsList: newsList); + }), + ); + } + + void getSubcategoryNews() { + context.read().getSubCatNews( + langId: context.read().state.id, + subCatId: widget.from == 2 ? widget.subCatId : null, + catId: widget.from == 1 ? widget.catId : null, + latitude: locationValue.first, + longitude: locationValue.last); + } + + _buildNewsContainer({required NewsModel model, required int index, required int totalCurrentNews, required bool hasMoreNewsFetchError, required bool hasMore, required List newsList}) { + if (index == totalCurrentNews - 1 && index != 0) { + if (hasMore) { + if (hasMoreNewsFetchError) { + return Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0), + child: IconButton( + onPressed: () { + context.read().getMoreSubCatNews( + langId: context.read().state.id, + subCatId: widget.from == 2 ? widget.subCatId : null, + catId: widget.from == 1 ? widget.catId : null, + latitude: locationValue.first, + longitude: locationValue.last); + }, + icon: Icon(Icons.error, color: Theme.of(context).primaryColor)), + ), + ); + } else { + return Center(child: Padding(padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0), child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor))); + } + } + } + return model.type == "survey" + ? model.from == 2 + ? showSurveyQueResult(model, context, true) + : ShowSurveyQue(model: model, index: index, surveyId: context.read().getSurveyQuestionIndex(questionTitle: model.question!), updateList: updateCombineList) + : NewsItem(model: model, index: (index <= totalSurveyQue) ? index : (index - totalSurveyQue), newslist: newsList, fromShowMore: false); + } + + @override + Widget build(BuildContext context) { + super.build(context); + return getNewsList(); + } + + @override + bool get wantKeepAlive => true; +} diff --git a/news-app/lib/ui/screens/SubCategory/Widgets/categoryShimmer.dart b/news-app/lib/ui/screens/SubCategory/Widgets/categoryShimmer.dart new file mode 100644 index 00000000..ed45c7e3 --- /dev/null +++ b/news-app/lib/ui/screens/SubCategory/Widgets/categoryShimmer.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; + +subcatShimmer() { + return Shimmer.fromColors( + baseColor: Colors.grey.withOpacity(0.4), + highlightColor: Colors.grey.withOpacity(0.4), + child: SingleChildScrollView( + padding: const EdgeInsets.only(top: 10), + scrollDirection: Axis.horizontal, + child: Row( + children: [0, 1, 2, 3, 4, 5, 6] + .map((i) => Padding( + padding: const EdgeInsetsDirectional.only(start: 15, top: 0), + child: Container( + decoration: BoxDecoration(borderRadius: BorderRadius.circular(5.0), color: const Color.fromARGB(255, 59, 49, 49)), + height: 32.0, + width: 70.0, + ))) + .toList()), + )); +} diff --git a/news-app/lib/ui/screens/SubCategory/Widgets/showSurveyQuestion.dart b/news-app/lib/ui/screens/SubCategory/Widgets/showSurveyQuestion.dart new file mode 100644 index 00000000..6a13ac8f --- /dev/null +++ b/news-app/lib/ui/screens/SubCategory/Widgets/showSurveyQuestion.dart @@ -0,0 +1,134 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/getSurveyAnswerCubit.dart'; +import 'package:news/cubits/setSurveyAnswerCubit.dart'; +import 'package:news/cubits/surveyQuestionCubit.dart'; +import 'package:news/ui/styles/colors.dart'; + +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; + +class ShowSurveyQue extends StatefulWidget { + final NewsModel model; + final int index; + final String surveyId; + final bool isPaddingRequired; + final Function(NewsModel, int) updateList; + + const ShowSurveyQue({super.key, required this.model, required this.index, required this.surveyId, required this.updateList, this.isPaddingRequired = true}); + + @override + ShowSurveyQueState createState() => ShowSurveyQueState(); +} + +class ShowSurveyQueState extends State { + String? optSel; + + @override + Widget build(BuildContext context) { + return showSurveyQue(); + } + + Widget showSurveyQue() { + return BlocConsumer( + bloc: context.read(), + listener: (context, state) { + if (state is SetSurveyAnsInitial || state is SetSurveyAnsFetchInProgress) { + Center(child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)); + } + if (state is SetSurveyAnsFetchSuccess) { + context.read().removeQuestion(widget.surveyId); + context.read().getSurveyAns(langId: context.read().state.id); + } + }, + builder: (context, state) { + return BlocConsumer( + bloc: context.read(), + listener: (context, state) { + if (state is GetSurveyAnsInitial || state is GetSurveyAnsFetchInProgress) { + Center(child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)); + } + if (state is GetSurveyAnsFetchSuccess) { + for (var element in state.getSurveyAns) { + if (element.id == widget.model.id) { + NewsModel newModel = element; + newModel.from = 2; + widget.updateList(newModel, widget.index); + } + } + } + }, + builder: (context, state) { + return Padding( + padding: EdgeInsets.only(top: 15.0, left: (widget.isPaddingRequired) ? 15 : 0, right: (widget.isPaddingRequired) ? 15 : 0), + child: Container( + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: UiUtils.getColorScheme(context).surface), + padding: const EdgeInsets.all(10.0), + child: Column( + children: [ + CustomTextLabel( + text: widget.model.question!, textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, height: 1.0)), + Padding( + padding: const EdgeInsetsDirectional.only(top: 15.0, start: 7.0, end: 7.0), + child: ListView.builder( + shrinkWrap: true, + scrollDirection: Axis.vertical, + physics: const NeverScrollableScrollPhysics(), + itemCount: widget.model.optionDataList!.length, + itemBuilder: (context, j) { + return Padding( + padding: const EdgeInsets.only(bottom: 10.0), + child: InkWell( + child: Container( + height: 50, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.0), + color: optSel == widget.model.optionDataList![j].id ? Theme.of(context).primaryColor.withOpacity(0.1) : UiUtils.getColorScheme(context).secondary), + child: CustomTextLabel( + text: widget.model.optionDataList![j].options!, + textStyle: Theme.of(context).textTheme.titleSmall?.copyWith( + color: optSel == widget.model.optionDataList![j].id ? Theme.of(context).primaryColor : UiUtils.getColorScheme(context).primaryContainer, + height: 1.0), + textAlign: TextAlign.center)), + onTap: () { + setState(() { + optSel = widget.model.optionDataList![j].id; + }); + }, + ), + ); + })), + Padding( + padding: const EdgeInsets.only(top: 15.0), + child: InkWell( + child: Container( + height: 40.0, + width: MediaQuery.of(context).size.width * 0.35, + alignment: Alignment.center, + decoration: BoxDecoration(color: secondaryColor, borderRadius: BorderRadius.circular(7.0)), + child: CustomTextLabel( + text: 'submitBtn', + textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: Theme.of(context).primaryColor, fontWeight: FontWeight.w600, letterSpacing: 0.6), + ), + ), + onTap: () async { + if (optSel != null) { + //get survey id from survey question + String currentIndex = context.read().getSurveyQuestionIndex(questionTitle: widget.model.question!); + context.read().setSurveyAns(optId: optSel!, queId: currentIndex); + await Future.delayed(Duration(seconds: 2)); + } else { + showSnackBar(UiUtils.getTranslatedLabel(context, 'optSel'), context); + } + }), + ) + ], + ))); + }); + }); + } +} diff --git a/news-app/lib/ui/screens/SubCategory/Widgets/showSurveyResult.dart b/news-app/lib/ui/screens/SubCategory/Widgets/showSurveyResult.dart new file mode 100644 index 00000000..139cc33c --- /dev/null +++ b/news-app/lib/ui/screens/SubCategory/Widgets/showSurveyResult.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:percent_indicator/linear_percent_indicator.dart'; +import 'package:news/data/models/NewsModel.dart'; + +showSurveyQueResult(NewsModel model, BuildContext context, bool isPaddingRequired) { + return Padding( + padding: EdgeInsets.only(top: 15.0, left: (isPaddingRequired) ? 15 : 0, right: (isPaddingRequired) ? 15 : 0), + child: Container( + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: UiUtils.getColorScheme(context).surface), + padding: const EdgeInsets.all(10.0), + child: Column( + children: [ + CustomTextLabel(text: model.question!, textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, height: 1.0)), + Padding( + padding: const EdgeInsetsDirectional.only(top: 15.0, start: 7.0, end: 7.0), + child: ListView.builder( + shrinkWrap: true, + scrollDirection: Axis.vertical, + physics: const NeverScrollableScrollPhysics(), + itemCount: model.optionDataList!.length, + itemBuilder: (context, j) { + return Padding( + padding: const EdgeInsetsDirectional.only(bottom: 10.0, start: 15.0, end: 15.0), + child: LinearPercentIndicator( + animation: true, + animationDuration: 1000, + lineHeight: 40.0, + percent: model.optionDataList![j].percentage! / 100, + center: CustomTextLabel(text: "${(model.optionDataList![j].percentage!).toStringAsFixed(2)}%", textStyle: Theme.of(context).textTheme.titleSmall), + barRadius: const Radius.circular(16), + progressColor: Theme.of(context).primaryColor, + isRTL: false, + padding: const EdgeInsets.only(top: 10.0, bottom: 10.0))); + })), + ], + ))); +} diff --git a/news-app/lib/ui/screens/TagNewsScreen.dart b/news-app/lib/ui/screens/TagNewsScreen.dart new file mode 100644 index 00000000..a54c3374 --- /dev/null +++ b/news-app/lib/ui/screens/TagNewsScreen.dart @@ -0,0 +1,281 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/ui/widgets/customAppBar.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; + +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/shimmerNewsList.dart'; +import 'package:news/ui/widgets/errorContainerWidget.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import 'package:news/utils/ErrorMessageKeys.dart'; +import 'package:news/utils/internetConnectivity.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/cubits/tagNewsCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/Bookmark/bookmarkCubit.dart'; +import 'package:news/cubits/Bookmark/UpdateBookmarkCubit.dart'; +import 'package:news/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart'; +import 'package:news/cubits/LikeAndDislikeNews/updateLikeAndDislikeCubit.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/Bookmark/bookmarkRepository.dart'; +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; +import 'package:news/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsRepository.dart'; + +class NewsTag extends StatefulWidget { + final String tagId; + final String tagName; + + const NewsTag({super.key, required this.tagId, required this.tagName}); + + @override + NewsTagState createState() => NewsTagState(); + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute(builder: (_) => NewsTag(tagId: arguments['tagId'], tagName: arguments['tagName'])); + } +} + +class NewsTagState extends State { + @override + void initState() { + super.initState(); + getTagWiseNews(); + } + + getTagWiseNews() { + Future.delayed(Duration.zero, () { + context.read().getTagNews( + tagId: widget.tagId, + langId: context.read().state.id, + latitude: SettingsLocalDataRepository().getLocationCityValues().first, + longitude: SettingsLocalDataRepository().getLocationCityValues().last); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold(appBar: CustomAppBar(isConvertText: false, height: 45, isBackBtn: true, label: widget.tagName, horizontalPad: 15), body: viewContent()); + } + + newsItem(NewsModel model, int index) { + List tagList = []; + List tagId = []; + + DateTime time1 = DateTime.parse(model.publishDate ?? model.date!); + + if (model.tagName! != "") { + final tagName = model.tagName!; + tagList = tagName.split(','); + } + + if (model.tagId! != "") tagId = model.tagId!.split(","); + + return Builder(builder: (context) { + bool isLike = context.read().isNewsLikeAndDisLike(model.id!); + return Padding( + padding: EdgeInsetsDirectional.only(top: index == 0 ? 0 : MediaQuery.of(context).size.height / 25.0), + child: Column(children: [ + InkWell( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: MediaQuery.of(context).size.height / 4.2, + child: Stack( + alignment: Alignment.bottomLeft, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(10.0), + child: CustomNetworkImage(networkImageUrl: model.image!, width: double.maxFinite, height: MediaQuery.of(context).size.height / 4.2, isVideo: false, fit: BoxFit.cover)), + if (model.tagName! != "") + Container( + margin: const EdgeInsets.only(bottom: 5.0, left: 5.0, right: 5.0), + child: SizedBox( + height: 16, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: tagList.length, + itemBuilder: (context, index) { + return Padding( + padding: EdgeInsetsDirectional.only(start: index == 0 ? 0 : 5.5), + child: InkWell( + child: Container( + height: 20.0, + width: 65, + alignment: Alignment.center, + padding: const EdgeInsetsDirectional.only(start: 3.0, end: 3.0, top: 1.0, bottom: 1.0), + decoration: BoxDecoration( + borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(10.0), topRight: Radius.circular(10.0)), + color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.85), + ), + child: CustomTextLabel( + text: tagList[index], + textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: UiUtils.getColorScheme(context).secondary, fontSize: 9.5), + overflow: TextOverflow.ellipsis, + softWrap: true)), + onTap: () async { + Navigator.of(context).pushNamed(Routes.tagScreen, arguments: {"tagId": tagId[index], "tagName": tagList[index]}); + }, + )); + })), + ) + ], + ), + ), + Container( + padding: const EdgeInsetsDirectional.only(top: 4.0, start: 5.0, end: 5.0), + child: CustomTextLabel( + text: model.title!, + textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: Theme.of(context).colorScheme.primaryContainer.withOpacity(0.9)), + maxLines: 2, + softWrap: true, + overflow: TextOverflow.ellipsis), + ), + Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsetsDirectional.only(top: 4.0, start: 5.0, end: 5.0), + child: CustomTextLabel( + text: UiUtils.convertToAgo(context, time1, 0)!, + textStyle: Theme.of(context).textTheme.bodySmall!.copyWith(color: Theme.of(context).colorScheme.primaryContainer.withOpacity(0.6)))), + ), + Row( + children: [ + Padding( + padding: const EdgeInsetsDirectional.only(start: 13.0), + child: InkWell( + child: const Icon(Icons.share_rounded), + onTap: () async { + if (await InternetConnectivity.isNetworkAvailable()) { + UiUtils.shareNews(context: context, slug: model.slug!, title: model.title!, isVideo: false, videoId: "", isBreakingNews: false, isNews: true); + } else { + showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context); + } + }, + ), + ), + SizedBox(width: MediaQuery.of(context).size.width / 99.0), + BlocProvider( + create: (context) => UpdateBookmarkStatusCubit(BookmarkRepository()), + child: BlocBuilder( + bloc: context.read(), + builder: (context, bookmarkState) { + bool isBookmark = context.read().isNewsBookmark(model.id!); + return BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is UpdateBookmarkStatusSuccess) { + if (state.wasBookmarkNewsProcess) { + context.read().addBookmarkNews(state.news); + } else { + context.read().removeBookmarkNews(state.news); + } + } + }), + builder: (context, state) { + return InkWell( + onTap: () { + if (context.read().getUserId() != "0") { + if (state is UpdateBookmarkStatusInProgress) { + return; + } + context.read().setBookmarkNews(news: model, status: (isBookmark) ? "0" : "1"); + } else { + UiUtils.loginRequired(context); + } + }, + child: Padding( + padding: EdgeInsetsDirectional.zero, + child: InkWell( + child: state is UpdateBookmarkStatusInProgress + ? SizedBox(height: 15, width: 15, child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)) + : Icon(isBookmark ? Icons.bookmark_added_rounded : Icons.bookmark_add_outlined)))); + }); + }), + ), + SizedBox(width: MediaQuery.of(context).size.width / 99.0), + BlocProvider( + create: (context) => UpdateLikeAndDisLikeStatusCubit(LikeAndDisLikeRepository()), + child: BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + isLike = context.read().isNewsLikeAndDisLike(model.id!); + }), + builder: (context, likeAndDislikeState) { + return BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is UpdateLikeAndDisLikeStatusSuccess) { + context.read().getLike(langId: context.read().state.id); + state.news.totalLikes = (!isLike) + ? (int.parse(state.news.totalLikes.toString()) + 1).toString() + : (state.news.totalLikes!.isNotEmpty) + ? (int.parse(state.news.totalLikes.toString()) - 1).toString() + : "0"; + } + }), + builder: (context, state) { + isLike = context.read().isNewsLikeAndDisLike(model.id!); + return InkWell( + splashColor: Colors.transparent, + onTap: () { + if (context.read().getUserId() != "0") { + if (state is UpdateLikeAndDisLikeStatusInProgress) { + return; + } + context.read().setLikeAndDisLikeNews(news: model, status: (isLike) ? "0" : "1"); + } else { + UiUtils.loginRequired(context); + } + }, + child: (state is UpdateLikeAndDisLikeStatusInProgress) + ? SizedBox(height: 20, width: 20, child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)) + : isLike + ? const Icon(Icons.thumb_up_alt) + : const Icon(Icons.thumb_up_off_alt)); + }); + })), + ], + ), + ], + ), + ], + ), + onTap: () { + if (index % 3 == 0) UiUtils.showInterstitialAds(context: context); + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": model, "isFromBreak": false, "fromShowMore": false}); + }, + ), + ])); + }); + } + + viewContent() { + return BlocBuilder(builder: (context, state) { + if (state is TagNewsFetchSuccess) { + return Padding( + padding: const EdgeInsetsDirectional.only(top: 10.0, bottom: 10.0, start: 13.0, end: 13.0), + child: ListView.builder( + itemCount: state.tagNews.length, + shrinkWrap: true, + padding: const EdgeInsets.only(bottom: 20), + physics: const AlwaysScrollableScrollPhysics(), + itemBuilder: ((context, index) { + return newsItem((state).tagNews[index], index); + }))); + } + if (state is TagNewsFetchFailure) { + return ErrorContainerWidget( + errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage, onRetry: getTagWiseNews); + } + return ShimmerNewsList(isNews: true); + }); + } +} diff --git a/news-app/lib/ui/screens/Videos/VideoScreen.dart b/news-app/lib/ui/screens/Videos/VideoScreen.dart new file mode 100644 index 00000000..8b0ed1f6 --- /dev/null +++ b/news-app/lib/ui/screens/Videos/VideoScreen.dart @@ -0,0 +1,229 @@ +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; +import 'package:news/cubits/videosCubit.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/customAppBar.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/errorContainerWidget.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import 'package:news/ui/widgets/videoItem.dart'; +import 'package:news/utils/ErrorMessageKeys.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/uiUtils.dart'; + +class VideoScreen extends StatefulWidget { + const VideoScreen({super.key}); + + @override + VideoScreenState createState() => VideoScreenState(); + + static Route route(RouteSettings routeSettings) { + return CupertinoPageRoute(builder: (_) => const VideoScreen()); + } +} + +class VideoScreenState extends State { + late final PageController _videoScrollController = PageController()..addListener(hasMoreVideoScrollListener); + + int currentIndex = 0; + int totalItems = 0; + String? initializedVideoId; + late String latitude, longitude; + VideoViewType? videoViewType; + void getVideos() { + Future.delayed(Duration.zero, () { + context.read().getVideo(langId: context.read().state.id, latitude: latitude, longitude: longitude); + }); + } + + @override + void initState() { + videoViewType = context.read().getVideoTypePreference(); + + setLatitudeLongitude(); + getVideos(); + super.initState(); + } + + @override + void dispose() { + _videoScrollController.dispose(); + super.dispose(); + } + + void setLatitudeLongitude() { + latitude = SettingsLocalDataRepository().getLocationCityValues().first; + longitude = SettingsLocalDataRepository().getLocationCityValues().last; + } + + void hasMoreVideoScrollListener() { + if (_videoScrollController.offset >= _videoScrollController.position.maxScrollExtent && !_videoScrollController.position.outOfRange) { + if (context.read().hasMoreVideo()) { + context.read().getMoreVideo(langId: context.read().state.id, latitude: latitude, longitude: longitude); + } else {} + } + } + + @override + Widget build(BuildContext context) { + return Scaffold(appBar: CustomAppBar(height: 44, isBackBtn: false, label: 'videosLbl', isConvertText: true), body: _buildVideos(videoViewType ?? VideoViewType.normal)); + } + + Widget _buildVideos(VideoViewType type) { + return BlocBuilder(builder: (context, state) { + if (state is VideoFetchSuccess) { + totalItems = state.video.length; + if (type == VideoViewType.page) { + return RefreshIndicator( + onRefresh: () async { + getVideos(); + }, + child: PageView.builder( + controller: _videoScrollController, + scrollDirection: Axis.vertical, + physics: PageScrollPhysics(), + itemCount: totalItems, + itemBuilder: (context, index) { + return _buildVideoContainer( + video: state.video[index], hasMore: state.hasMore, hasMoreVideoFetchError: state.hasMoreFetchError, index: index, totalCurrentVideo: state.video.length); + })); + } else { + return Padding( + padding: const EdgeInsets.only(top: 15.0), + child: ListView.separated( + controller: _videoScrollController, + itemBuilder: (context, index) { + return _buildHorizontalViewContainer(videosList: state.video, video: state.video[index], index: index, totalCurrentVideo: state.video.length); + }, + separatorBuilder: (context, index) { + return SizedBox(height: 16); + }, + itemCount: state.video.length), + ); + } + } + if (state is VideoFetchFailure) { + return ErrorContainerWidget(errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage, onRetry: getVideos); + } + return SizedBox.shrink(); + }); + } + + _buildVideoContainer({required NewsModel video, required int index, required int totalCurrentVideo, required bool hasMoreVideoFetchError, required bool hasMore}) { + if (index == totalCurrentVideo - 1 && index != 0) { + if (hasMore) { + if (hasMoreVideoFetchError) { + return Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0), + child: IconButton( + onPressed: () { + context.read().getMoreVideo( + langId: context.read().state.id, + latitude: SettingsLocalDataRepository().getLocationCityValues().first, + longitude: SettingsLocalDataRepository().getLocationCityValues().last); + }, + icon: Icon(Icons.error, color: Theme.of(context).primaryColor), + ), + ), + ); + } else { + return Center(child: Padding(padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 8.0), child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor))); + } + } + } + + return VideoItem(model: video); + } + + Widget _buildHorizontalViewContainer({required List videosList, required NewsModel video, required int index, required int totalCurrentVideo}) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: VideoNewsCard( + video: video, + videosList: videosList, + onInitializeVideo: () { + initializedVideoId = video.id; + setState(() {}); + }, + ), + ); + } +} + +class VideoNewsCard extends StatefulWidget { + final NewsModel video; + final List videosList; + const VideoNewsCard({super.key, required this.videosList, required this.video, required this.onInitializeVideo}); + final void Function() onInitializeVideo; + + @override + VideoNewsCardState createState() => VideoNewsCardState(); +} + +class VideoNewsCardState extends State { + @override + Widget build(BuildContext context) { + return Container( + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration(color: UiUtils.getColorScheme(context).surface, borderRadius: BorderRadius.circular(8)), + child: GestureDetector( + onTap: () { + List videosList = List.from(widget.videosList)..removeWhere((x) => x.id == widget.video.id); + Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 1, "model": widget.video, "otherVideos": videosList}); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8, + children: [ + Container(height: 192, color: borderColor, child: _buildThumbnail()), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8, + children: [ + CustomTextLabel(text: widget.video.title!, textStyle: TextStyle(fontWeight: FontWeight.bold)), + if (widget.video.date != null && widget.video.date!.isNotEmpty) + Row( + spacing: 8, + children: [Icon(Icons.calendar_month_rounded), CustomTextLabel(text: UiUtils.formatDate(widget.video.date ?? ''))], + ), + ], + ), + ) + ], + ), + ), + ); + } + + Widget _buildThumbnail() { + return Container( + child: Stack( + fit: StackFit.expand, + children: [ + ShaderMask( + shaderCallback: (rect) => + LinearGradient(begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [darkSecondaryColor.withOpacity(0.6), darkSecondaryColor.withOpacity(0.6)]).createShader(rect), + blendMode: BlendMode.darken, + child: Container( + color: primaryColor.withAlpha(5), + width: double.maxFinite, + height: MediaQuery.of(context).size.height / 3.3, + child: CustomNetworkImage(width: double.maxFinite, networkImageUrl: widget.video.image ?? ''), + )), + Center( + child: Icon(Icons.play_circle_outline_rounded, size: 50, color: backgroundColor), + ) + ], + ), + ); + } +} diff --git a/news-app/lib/ui/screens/Videos/Widgets/otherVideosCard.dart b/news-app/lib/ui/screens/Videos/Widgets/otherVideosCard.dart new file mode 100644 index 00000000..67bd6c98 --- /dev/null +++ b/news-app/lib/ui/screens/Videos/Widgets/otherVideosCard.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/data/models/LiveStreamingModel.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import 'package:news/utils/uiUtils.dart'; + +class OtherVideosCard extends StatelessWidget { + final NewsModel? model; + final BreakingNewsModel? brModel; + final LiveStreamingModel? liveModel; + OtherVideosCard({super.key, this.model, this.brModel, this.liveModel}); + + @override + Widget build(BuildContext context) { + bool isLive = liveModel != null; + return InkWell( + splashColor: Colors.transparent, + onTap: () { + bool isNews = (model != null) ? true : false; + bool isBreakingNews = (brModel != null) ? true : false; + int fromVal = (isNews) ? 1 : (isBreakingNews ? 2 : 3); // VAL 1 = news, 2 = liveNews , 3 = breakingNews + // and pass current model accordingly.Do not pass other videos - set it from newsVideo screen only + Map videoArguments = {"from": fromVal}; + if (fromVal == 1) { + videoArguments.addAll({"model": model}); + } else if (fromVal == 2) { + videoArguments.addAll({"brModel": brModel}); + } else { + videoArguments.addAll({"liveModel": liveModel}); + } + Navigator.pushReplacementNamed(context, Routes.newsVideo, arguments: videoArguments); + }, + child: Container( + margin: const EdgeInsets.symmetric(vertical: 6), + decoration: BoxDecoration(color: UiUtils.getColorScheme(context).surface, borderRadius: BorderRadius.circular(12), border: Border.all(color: dividerColor.withAlpha(70))), + padding: const EdgeInsets.all(8), + child: Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(6), + child: CustomNetworkImage( + networkImageUrl: (isLive) + ? liveModel!.image! + : (model != null) + ? model!.image! + : brModel!.image!, + height: 60, + width: 90, + fit: BoxFit.cover)), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (!isLive && (model != null && model!.categoryName != null && model!.categoryName!.isNotEmpty)) + ? Container( + height: 25.0, + width: 65, + alignment: Alignment.center, + margin: const EdgeInsetsDirectional.only(start: 2.0, end: 2.0, top: 1.0, bottom: 1.0), + decoration: BoxDecoration( + color: UiUtils.getColorScheme(context).onPrimary.withAlpha(40), + border: Border.all(color: UiUtils.getColorScheme(context).secondary.withOpacity(0.85)), + borderRadius: BorderRadius.circular(5)), + child: CustomTextLabel( + text: model!.categoryName ?? "", + textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: UiUtils.getColorScheme(context).onPrimary, fontWeight: FontWeight.bold, fontSize: 12), + overflow: TextOverflow.ellipsis, + softWrap: true)) + : SizedBox.shrink(), + SizedBox(height: 4), + CustomTextLabel( + text: (isLive) + ? liveModel!.title! + : (model != null) + ? model!.title ?? "" + : brModel!.title ?? "", + textStyle: TextStyle(color: UiUtils.getColorScheme(context).onPrimary), + maxLines: 2, + overflow: TextOverflow.ellipsis) + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/news-app/lib/ui/screens/Videos/Widgets/videoCard.dart b/news-app/lib/ui/screens/Videos/Widgets/videoCard.dart new file mode 100644 index 00000000..4d71b737 --- /dev/null +++ b/news-app/lib/ui/screens/Videos/Widgets/videoCard.dart @@ -0,0 +1,271 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:intl/intl.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/cubits/Bookmark/UpdateBookmarkCubit.dart'; +import 'package:news/cubits/Bookmark/bookmarkCubit.dart'; +import 'package:news/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart'; +import 'package:news/cubits/LikeAndDislikeNews/updateLikeAndDislikeCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/data/models/LiveStreamingModel.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/Bookmark/bookmarkRepository.dart'; +import 'package:news/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsRepository.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/videoPlayContainer.dart'; +import 'package:news/utils/internetConnectivity.dart'; +import 'package:news/utils/strings.dart'; +import 'package:news/utils/uiUtils.dart'; + +class VideoCard extends StatefulWidget { + final NewsModel? model; + final BreakingNewsModel? brModel; + final LiveStreamingModel? liveModel; + VideoCard({super.key, this.model, this.liveModel, this.brModel}); + + @override + State createState() => VideoCardState(); +} + +class VideoCardState extends State { + late NewsModel? model; + late BreakingNewsModel? brModel; + late LiveStreamingModel? liveModel; + bool isLiveVideo = false, isBreakingVideo = false; + List? tagList = []; + List? tagId = []; + String formattedDate = "", contentType = "", contentValue = "", titleTxt = ""; + + @override + void initState() { + super.initState(); + model = widget.model; + brModel = widget.brModel; + liveModel = widget.liveModel; + isLiveVideo = (liveModel != null); + isBreakingVideo = (brModel != null); + setFormattedDate(); + setTitle(); + setContentValueAndContentType(); + setTags(); + } + + void setTitle() { + titleTxt = (isLiveVideo) + ? liveModel?.title ?? "" + : (isBreakingVideo) + ? brModel?.title ?? "" + : model?.title ?? ""; + } + + void setTags() { + if (model != null && model?.tagName != null && (model!.sourceType != null && model!.sourceType != BREAKING_NEWS)) { + if (model?.tagId != null && model!.tagId!.isNotEmpty) { + tagId = model?.tagId?.split(","); + } + + if (model!.tagName!.isNotEmpty) { + final tagName = model?.tagName!; + tagList = tagName?.split(','); + } + } + } + + void setFormattedDate() { + String dateVal = (isLiveVideo) ? liveModel!.updatedDate ?? "" : (model?.publishDate ?? model?.date ?? ""); + if (dateVal.isNotEmpty) { + DateTime parsedDate = DateFormat("yyyy-MM-dd").parse(dateVal); + formattedDate = DateFormat("MMM dd, yyyy").format(parsedDate); + } + } + + void setContentValueAndContentType() { + contentType = (isLiveVideo) ? liveModel?.type ?? "" : ((model != null) ? model?.contentType ?? "" : brModel!.contentType ?? ""); + contentValue = (isLiveVideo) + ? liveModel?.url ?? "" + : (model != null) + ? model?.contentValue ?? "" + : brModel!.contentValue ?? ""; + } + + @override + void dispose() { + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); + super.dispose(); + } + + Widget likeButton() { + bool isLike = context.read().isNewsLikeAndDisLike(widget.model?.newsId ?? "0"); + + return BlocProvider( + create: (context) => UpdateLikeAndDisLikeStatusCubit(LikeAndDisLikeRepository()), + child: BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is LikeAndDisLikeFetchSuccess) { + isLike = context.read().isNewsLikeAndDisLike(model?.newsId ?? "0"); + } else { + isLike = false; //in case of failue - no other likes found + } + }), + builder: (context, likeAndDislikeState) { + return BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is UpdateLikeAndDisLikeStatusSuccess) { + context.read().getLike(langId: context.read().state.id); + } + }), + builder: (context, state) { + return InkWell( + splashColor: Colors.transparent, + onTap: () { + if (context.read().getUserId() != "0") { + if (state is UpdateLikeAndDisLikeStatusInProgress) { + return; + } + context.read().setLikeAndDisLikeNews(news: model ?? NewsModel(), status: (isLike) ? "0" : "1"); + } else { + UiUtils.loginRequired(context); + } + }, + child: designButtons( + childWidget: (state is UpdateLikeAndDisLikeStatusInProgress) + ? SizedBox(height: 15, width: 15, child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)) + : ((isLike) + ? Icon(Icons.thumb_up_alt, size: 25, color: UiUtils.getColorScheme(context).onPrimary) + : Icon(Icons.thumb_up_off_alt, size: 25, color: UiUtils.getColorScheme(context).onPrimary)))); + }); + })); + } + + @override + Widget build(BuildContext context) { + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); + return Container( + decoration: BoxDecoration(color: UiUtils.getColorScheme(context).surface, borderRadius: BorderRadius.circular(12)), + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(10), + child: AspectRatio(aspectRatio: 16 / 9, child: VideoPlayContainer(contentType: contentType, contentValue: contentValue)), + ), + const SizedBox(height: 10), + (model != null && model!.sourceType != BREAKING_NEWS) + ? Wrap( + spacing: 8, + children: tagList! + .map((tag) => tag.trim()) + .where((tag) => tag.isNotEmpty) + .map( + (tag) => InkWell( + onTap: () async { + Navigator.of(context).pushNamed(Routes.tagScreen, arguments: {"tagId": tagId, "tagName": tagList}); + }, + child: Container( + height: 25.0, + width: 65, + alignment: Alignment.center, + padding: const EdgeInsetsDirectional.only(start: 3.0, end: 3.0, top: 1.0, bottom: 1.0), + decoration: BoxDecoration(borderRadius: const BorderRadius.all(Radius.circular(15)), color: UiUtils.getColorScheme(context).secondary.withOpacity(0.85)), + child: CustomTextLabel( + text: tag, + textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontSize: 12), + overflow: TextOverflow.ellipsis, + softWrap: true)), + ), + ) + .toList(), + ) + : SizedBox.shrink(), + const SizedBox(height: 10), + CustomTextLabel(text: titleTxt, textStyle: TextStyle(color: UiUtils.getColorScheme(context).onPrimary)), + const SizedBox(height: 10), + Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + (formattedDate.isNotEmpty) + ? Row( + children: [ + Icon(Icons.calendar_month_rounded, size: 20), + SizedBox(width: 4), + CustomTextLabel(text: formattedDate, textStyle: TextStyle(fontSize: 12, color: UiUtils.getColorScheme(context).onPrimary)), + ], + ) + : SizedBox.shrink(), + Row( + children: [ + if ((model?.sourceType != null && model?.newsId != null || model?.id != null) || (model?.sourceType == NEWS || model?.sourceType == VIDEOS)) + Row(children: [ + InkWell( + onTap: () async { + (await InternetConnectivity.isNetworkAvailable()) + ? UiUtils.shareNews( + context: context, slug: model?.slug ?? "", title: model?.title ?? "", isVideo: true, videoId: model?.id ?? "0", isBreakingNews: false, isNews: false) + : showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context); + }, + splashColor: Colors.transparent, + child: designButtons(childWidget: Icon(Icons.share_rounded, color: UiUtils.getColorScheme(context).onPrimary))), + const SizedBox(height: 15), + BlocProvider( + create: (context) => UpdateBookmarkStatusCubit(BookmarkRepository()), + child: BlocBuilder( + bloc: context.read(), + builder: (context, bookmarkState) { + bool isBookmark = context.read().isNewsBookmark(model?.id ?? "0"); + return BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is UpdateBookmarkStatusSuccess) { + (state.wasBookmarkNewsProcess) ? context.read().addBookmarkNews(state.news) : context.read().removeBookmarkNews(state.news); + setState(() {}); + } + }), + builder: (context, state) { + return InkWell( + onTap: () { + if (context.read().getUserId() != "0") { + if (state is UpdateBookmarkStatusInProgress) return; + context.read().setBookmarkNews(news: model!, status: (isBookmark) ? "0" : "1"); + } else { + UiUtils.loginRequired(context); + } + }, + child: state is UpdateBookmarkStatusInProgress + ? SizedBox(height: 15, width: 15, child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)) + : designButtons( + childWidget: Icon(isBookmark ? Icons.bookmark_added_rounded : Icons.bookmark_add_outlined, color: UiUtils.getColorScheme(context).onPrimary))); + }); + }), + ), + const SizedBox(height: 15), + likeButton() + ]), + ], + ), + ], + ), + Divider(), + ], + ), + ); + } + + Widget designButtons({required Widget childWidget}) { + return Container( + height: 30, + width: 30, + margin: EdgeInsets.symmetric(horizontal: 5), + padding: EdgeInsets.all(3), + decoration: BoxDecoration(shape: BoxShape.rectangle, borderRadius: BorderRadius.circular(7), color: borderColor.withOpacity(0.2)), + child: childWidget); + } +} diff --git a/news-app/lib/ui/screens/Videos/videoDetailsScreen.dart b/news-app/lib/ui/screens/Videos/videoDetailsScreen.dart new file mode 100644 index 00000000..6a399807 --- /dev/null +++ b/news-app/lib/ui/screens/Videos/videoDetailsScreen.dart @@ -0,0 +1,104 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/data/models/LiveStreamingModel.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/ui/screens/Videos/Widgets/otherVideosCard.dart'; +import 'package:news/ui/screens/Videos/Widgets/videoCard.dart'; +import 'package:news/ui/widgets/customAppBar.dart'; +import 'package:news/utils/uiUtils.dart'; + +class VideoDetailsScreen extends StatefulWidget { + int from; + LiveStreamingModel? liveModel; + NewsModel? model; + BreakingNewsModel? breakModel; + List? otherVideos; + List? otherBreakingVideos; + List? otherLiveVideos; + + VideoDetailsScreen({super.key, this.model, required this.from, this.liveModel, this.breakModel, required this.otherVideos, this.otherLiveVideos, this.otherBreakingVideos}); + + @override + State createState() => VideoDetailsState(); + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute( + builder: (_) => VideoDetailsScreen( + from: arguments['from'], + liveModel: arguments['liveModel'], + model: arguments['model'], + breakModel: arguments['breakModel'], + otherVideos: arguments['otherVideos'], + otherLiveVideos: arguments['otherLiveVideos'], + otherBreakingVideos: arguments['otherBreakingVideos'])); + } +} + +class VideoDetailsState extends State { + bool isLiveVideo = false, isBreakingNewsVideo = false, isNewsVideo = true; + //FROM VAL 1 = news, 2 = liveNews , 3 = breakingNews + + @override + void initState() { + isLiveVideo = (widget.from == 2); + isBreakingNewsVideo = (widget.from == 3); + isNewsVideo = !(isLiveVideo || isBreakingNewsVideo); + + super.initState(); + } + + @override + void dispose() { + // set screen back to portrait mode + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); + + return Scaffold( + backgroundColor: UiUtils.getColorScheme(context).surface, + appBar: CustomAppBar(height: 45, isBackBtn: true, label: 'videosLbl', isConvertText: true), + body: SingleChildScrollView( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + VideoCard( + model: (widget.model != null) ? widget.model : null, brModel: (widget.breakModel != null) ? widget.breakModel : null, liveModel: (widget.liveModel != null) ? widget.liveModel : null), + const SizedBox(height: 16), + ((isLiveVideo && widget.otherLiveVideos != null && widget.otherLiveVideos!.isNotEmpty) || + ((widget.otherVideos != null && widget.otherVideos!.isNotEmpty) || (widget.otherBreakingVideos != null && widget.otherBreakingVideos!.isNotEmpty))) + ? Text( + UiUtils.getTranslatedLabel(context, 'recentVidLbl'), + style: TextStyle(color: UiUtils.getColorScheme(context).onPrimary, fontWeight: FontWeight.bold, fontSize: 18), + ) + : SizedBox.shrink(), + const SizedBox(height: 8), + ((isLiveVideo && widget.otherLiveVideos != null && widget.otherLiveVideos!.isNotEmpty) || + ((widget.otherVideos != null && widget.otherVideos!.isNotEmpty) || (widget.otherBreakingVideos != null && widget.otherBreakingVideos!.isNotEmpty))) + ? ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: (isLiveVideo) + ? widget.otherLiveVideos!.length + : (widget.otherVideos != null && widget.otherVideos!.isNotEmpty) + ? widget.otherVideos!.length + : widget.otherBreakingVideos!.length, + itemBuilder: (context, index) => OtherVideosCard( + brModel: (widget.otherBreakingVideos != null && widget.otherBreakingVideos!.isNotEmpty) ? widget.otherBreakingVideos![index] : null, + model: (widget.otherVideos != null && widget.otherVideos!.isNotEmpty) ? widget.otherVideos![index] : null, + liveModel: (widget.otherLiveVideos != null && widget.otherLiveVideos!.isNotEmpty) ? widget.otherLiveVideos![index] : null), + ) + : SizedBox.shrink(), + ], + ), + ), + ); + } +} diff --git a/news-app/lib/ui/screens/auth/ForgotPassword.dart b/news-app/lib/ui/screens/auth/ForgotPassword.dart new file mode 100644 index 00000000..3f4a17be --- /dev/null +++ b/news-app/lib/ui/screens/auth/ForgotPassword.dart @@ -0,0 +1,137 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart'; +import 'package:news/ui/widgets/customBackBtn.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/internetConnectivity.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; +import 'package:news/ui/screens/auth/Widgets/setEmail.dart'; +import 'package:news/ui/screens/auth/Widgets/setLoginAndSignUpBtn.dart'; + +class ForgotPassword extends StatefulWidget { + const ForgotPassword({super.key}); + + @override + FrgtPswdState createState() => FrgtPswdState(); +} + +class FrgtPswdState extends State { + TextEditingController emailC = TextEditingController(); + final FirebaseAuth _auth = FirebaseAuth.instance; + final GlobalKey _formkey = GlobalKey(); + int durationInMiliSeconds = 2500; + + @override + void dispose() { + emailC.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold(body: screenContent()); + } + + Widget backBtn() { + return const Padding(padding: EdgeInsets.only(top: 20.0, left: 10.0), child: CustomBackButton()); + } + + Widget forgotIcon() { + return Container( + padding: const EdgeInsets.all(20.0), + child: Center( + child: SvgPictureWidget( + assetName: "forgot", + width: 120, + height: 120, + fit: BoxFit.fill, + assetColor: ColorFilter.mode(UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), BlendMode.srcIn), + ), + ), + ); + } + + Widget forgotPassLbl() { + return Center( + child: CustomTextLabel( + text: 'forgotPassLbl', + maxLines: 3, + textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600, fontSize: 22, color: UiUtils.getColorScheme(context).primaryContainer), + ), + ); + } + + Widget forgotPassHead() { + return Padding( + padding: const EdgeInsetsDirectional.only(top: 20.0), + child: CustomTextLabel( + text: 'frgtPassHead', + maxLines: 3, + textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500, fontSize: 16, color: UiUtils.getColorScheme(context).primaryContainer), + )); + } + + Widget forgotPassSubHead() { + return Padding( + padding: const EdgeInsetsDirectional.only(top: 30.0), + child: CustomTextLabel( + text: 'forgotPassSub', + maxLines: 3, + textStyle: Theme.of(context).textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.normal, fontSize: 14.0, color: UiUtils.getColorScheme(context).primaryContainer), + )); + } + + Widget emailTextCtrl() { + return SetEmail(emailC: emailC, email: emailC.text, topPad: 25); + } + + Widget submitBtn() { + return SetLoginAndSignUpBtn( + onTap: () async { + FocusScope.of(context).unfocus(); //dismiss keyboard + + if (await InternetConnectivity.isNetworkAvailable()) { + Future.delayed(const Duration(seconds: 1)).then((_) async { + if (emailC.text.isEmpty) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'emailValid'), context, durationInMiliSeconds: durationInMiliSeconds); + } else { + try { + await _auth.sendPasswordResetEmail(email: emailC.text.trim()); + final form = _formkey.currentState; + form!.save(); + if (form.validate()) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'passReset'), context, durationInMiliSeconds: durationInMiliSeconds); + Navigator.pop(context); + } + } on FirebaseAuthException catch (e) { + if (e.code == "user-not-found") { + showSnackBar(UiUtils.getTranslatedLabel(context, 'userNotFound'), context, durationInMiliSeconds: durationInMiliSeconds); + } else { + showSnackBar(e.message!, context, durationInMiliSeconds: durationInMiliSeconds); + } + } + } + }); + } else { + showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context, durationInMiliSeconds: durationInMiliSeconds); + } + }, + text: 'submitBtn', + topPad: 30); + } + + Widget screenContent() { + return Container( + padding: const EdgeInsetsDirectional.all(20.0), + child: SingleChildScrollView( + child: Form( + key: _formkey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [backBtn(), const SizedBox(height: 50), forgotIcon(), forgotPassLbl(), forgotPassHead(), forgotPassSubHead(), emailTextCtrl(), submitBtn()], + ), + ))); + } +} diff --git a/news-app/lib/ui/screens/auth/RequestOtpScreen.dart b/news-app/lib/ui/screens/auth/RequestOtpScreen.dart new file mode 100644 index 00000000..4690cdbb --- /dev/null +++ b/news-app/lib/ui/screens/auth/RequestOtpScreen.dart @@ -0,0 +1,263 @@ +import 'package:country_code_picker/country_code_picker.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; +import 'package:news/ui/styles/colors.dart'; + +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/utils/internetConnectivity.dart'; +import 'package:news/utils/validators.dart'; + +class RequestOtp extends StatefulWidget { + const RequestOtp({super.key}); + + @override + RequestOtpState createState() => RequestOtpState(); +} + +class RequestOtpState extends State { + TextEditingController phoneC = TextEditingController(); + String? phone, conCode; + final GlobalKey _formkey = GlobalKey(); + bool isLoading = false; + CountryCode? code; + String? verificationId; + String errorMessage = ''; + final FirebaseAuth _auth = FirebaseAuth.instance; + int forceResendingToken = 0; + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Stack( + children: [ + SafeArea( + child: showContent(), + ), + UiUtils.showCircularProgress(isLoading, Theme.of(context).primaryColor) + ], + )); + } + + //show form content + showContent() { + return Container( + padding: const EdgeInsetsDirectional.all(20.0), + child: SingleChildScrollView( + child: Form( + key: _formkey, + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Align( + //backButton + alignment: Alignment.topLeft, + child: InkWell(onTap: () => Navigator.of(context).pop(), splashColor: Colors.transparent, child: const Icon(Icons.keyboard_backspace_rounded))), + const SizedBox(height: 50), + otpVerifySet(), + enterMblSet(), + receiveDigitSet(), + setCodeWithMono(), + reqOtpBtn() + ]))), + ); + } + + otpVerifySet() { + return CustomTextLabel( + text: 'loginLbl', + textStyle: Theme.of(context).textTheme.headlineSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w800, letterSpacing: 0.5), + textAlign: TextAlign.center); + } + + enterMblSet() { + return Padding( + padding: const EdgeInsets.only(top: 35.0), + child: CustomTextLabel( + text: 'enterMblLbl', + textStyle: Theme.of(context).textTheme.bodyLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w500), + ), + ); + } + + receiveDigitSet() { + return Container( + padding: const EdgeInsets.only(top: 20.0), + child: CustomTextLabel( + text: 'receiveDigitLbl', + textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), fontSize: 14), + textAlign: TextAlign.left), + ); + } + + setCodeWithMono() { + return Padding( + padding: const EdgeInsets.only(top: 30.0), + child: Container( + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: Theme.of(context).colorScheme.surface), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [setCountryCode(), setMono()], + ))); + } + + setCountryCode() { + double width = MediaQuery.of(context).size.width; + double height = MediaQuery.of(context).size.height; + return SizedBox( + height: 45, + child: CountryCodePicker( + boxDecoration: BoxDecoration(color: Theme.of(context).colorScheme.surface), + searchDecoration: InputDecoration(hintStyle: TextStyle(color: UiUtils.getColorScheme(context).primaryContainer), fillColor: UiUtils.getColorScheme(context).primaryContainer), + initialSelection: context.read().getCountryCode(), + dialogSize: Size(width - 50, height - 50), + builder: (CountryCode? code) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsetsDirectional.only(top: 10.0, bottom: 10.0, start: 20.0, end: 4.0), + child: + ClipRRect(borderRadius: BorderRadius.circular(3.0), child: Image.asset(code!.flagUri.toString(), package: 'country_code_picker', height: 30, width: 30, fit: BoxFit.cover))), + Container( + //CountryCode + width: 55.0, + height: 55.0, + alignment: Alignment.center, + child: CustomTextLabel( + text: code.dialCode.toString(), + textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)), + overflow: TextOverflow.ellipsis, + softWrap: true)), + ], + ); + }, + onChanged: (CountryCode countryCode) { + conCode = countryCode.dialCode; + }, + onInit: (CountryCode? code) { + conCode = code?.dialCode; + })); + } + + setMono() { + return Expanded( + child: Padding( + padding: const EdgeInsetsDirectional.only(top: 5.0, bottom: 15.0), + child: Container( + height: 40, + width: MediaQuery.of(context).size.width * 0.57, + alignment: Alignment.center, + child: TextFormField( + keyboardType: const TextInputType.numberWithOptions(signed: true, decimal: true), + controller: phoneC, + style: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7)), + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + validator: (val) => Validators.mobValidation(val!, context), + onSaved: (String? value) => phone = value, + decoration: InputDecoration( + hintText: UiUtils.getTranslatedLabel(context, 'enterMblLbl'), + hintStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5)), + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + ), + )), + ), + ); + } + + Future verifyPhone(BuildContext context) async { + try { + await _auth.verifyPhoneNumber( + phoneNumber: (conCode!.contains("+")) ? "$conCode${phoneC.text.trim()}" : "+$conCode${phoneC.text.trim()}", + verificationCompleted: (AuthCredential phoneAuthCredential) { + showSnackBar(phoneAuthCredential.toString(), context); + }, + verificationFailed: (FirebaseAuthException exception) { + setState(() => isLoading = false); + if (exception.code == "invalid-phone-number") { + //invalidPhoneNumber + showSnackBar(UiUtils.getTranslatedLabel(context, 'invalidPhoneNumber'), context); + } else { + showSnackBar('${exception.message}', context); + } + }, + forceResendingToken: forceResendingToken, + codeAutoRetrievalTimeout: (String verId) => verificationId = verId, + codeSent: processCodeSent(), + //smsOTPSent + timeout: const Duration(seconds: 60)); + } on FirebaseAuthException catch (authError) { + setState(() => isLoading = false); + showSnackBar(authError.message!, context); + } on FirebaseException catch (e) { + setState(() => isLoading = false); + showSnackBar(e.toString(), context); + } catch (e) { + setState(() => isLoading = false); + showSnackBar(e.toString(), context); + } + } + + processCodeSent() { + try { + smsOTPSent(String? verId, [int? forceCodeResend]) async { + if (forceCodeResend != null) forceResendingToken = forceCodeResend; + verificationId = verId; + setState(() => isLoading = false); + showSnackBar(UiUtils.getTranslatedLabel(context, 'codeSent'), context); + await Navigator.of(context).pushNamed(Routes.verifyOtp, arguments: {"verifyId": verificationId, "countryCode": conCode, "mono": phoneC.text.trim()}); + } + + return smsOTPSent; + } catch (e) {} + } + + reqOtpBtn() { + return Container( + alignment: Alignment.center, + padding: const EdgeInsets.only(top: 60.0), + child: InkWell( + child: Container( + height: 45.0, + width: MediaQuery.of(context).size.width * 0.9, + alignment: Alignment.center, + decoration: BoxDecoration(color: Theme.of(context).primaryColor, borderRadius: BorderRadius.circular(7.0)), + child: + CustomTextLabel(text: 'reqOtpLbl', textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: secondaryColor, fontWeight: FontWeight.w600, letterSpacing: 0.5, fontSize: 16))), + onTap: () async { + FocusScope.of(context).unfocus(); //dismiss keyboard + if (validateAndSave()) { + if (await InternetConnectivity.isNetworkAvailable()) { + setState(() => isLoading = true); + verifyPhone(context); + } else { + showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context); + } + } + }, + ), + ); + } + + //check validation of form data + bool validateAndSave() { + final form = _formkey.currentState; + form!.save(); + return (form.validate()) ? true : false; + } +} diff --git a/news-app/lib/ui/screens/auth/VerifyOtpScreen.dart b/news-app/lib/ui/screens/auth/VerifyOtpScreen.dart new file mode 100644 index 00000000..bf6061d9 --- /dev/null +++ b/news-app/lib/ui/screens/auth/VerifyOtpScreen.dart @@ -0,0 +1,268 @@ +import 'dart:async'; + +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/Auth/socialSignUpCubit.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:sms_autofill/sms_autofill.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/utils/internetConnectivity.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; + +class VerifyOtp extends StatefulWidget { + String? verifyId, countryCode, mono; + + VerifyOtp({super.key, this.verifyId, this.countryCode, this.mono}); + + @override + VerifyOtpState createState() => VerifyOtpState(); + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute( + builder: (_) => VerifyOtp( + verifyId: arguments['verifyId'], + countryCode: arguments['countryCode'], + mono: arguments['mono'], + )); + } +} + +class VerifyOtpState extends State { + final GlobalKey _scaffoldKey = GlobalKey(); + String? otp; + final GlobalKey _formkey = GlobalKey(); + int secondsRemaining = 60; + bool enableResend = false; + Timer? timer; + final FirebaseAuth _auth = FirebaseAuth.instance; + String? verifId; + + @override + void initState() { + super.initState(); + timer = Timer.periodic(const Duration(seconds: 1), (_) { + if (secondsRemaining != 0) { + setState(() { + secondsRemaining--; + }); + } else { + setState(() { + enableResend = true; + }); + } + }); + } + + @override + void dispose() { + timer?.cancel(); + super.dispose(); + } + + void _resendCode() { + otp = ""; + _onVerifyCode(); + setState(() { + secondsRemaining = 60; + enableResend = false; + }); + } + + void _onVerifyCode() async { + verificationCompleted(AuthCredential phoneAuthCredential) { + _auth.signInWithCredential(phoneAuthCredential).then((UserCredential value) { + if (value.user != null) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'otpMsg'), context); + } else { + showSnackBar(UiUtils.getTranslatedLabel(context, 'otpError'), context); + } + }).catchError((error) { + showSnackBar(error.toString(), context); + }); + } + + verificationFailed(FirebaseAuthException authException) { + if (authException.code == 'invalidVerificationCode') { + showSnackBar(UiUtils.getTranslatedLabel(context, 'invalidVerificationCode'), context); + } else { + showSnackBar(authException.message.toString(), context); + } + } + + codeSent(String? verificationId, [int? forceResendingToken]) async { + verifId = verificationId; + if (mounted) { + setState(() { + verifId = verificationId; + }); + } + } + + codeAutoRetrievalTimeout(String verificationId) { + verifId = verificationId; + setState(() { + verifId = verificationId; + }); + showSnackBar(UiUtils.getTranslatedLabel(context, 'otpTimeoutLbl'), context); + } + + await _auth.verifyPhoneNumber( + phoneNumber: "+${widget.countryCode}${widget.mono}", + timeout: const Duration(seconds: 60), + verificationCompleted: verificationCompleted, + verificationFailed: verificationFailed, + codeSent: codeSent, + codeAutoRetrievalTimeout: codeAutoRetrievalTimeout); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + key: _scaffoldKey, + body: BlocConsumer( + bloc: context.read(), + listener: (context, state) async { + if (state is SocialSignUpFailure) { + showSnackBar(state.errorMessage, context); + } + }, + builder: (context, state) { + return Stack( + children: [SafeArea(child: showContent()), if (state is SocialSignUpProgress) Center(child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor))], + ); + })); + } + + //show form content + showContent() { + return Container( + padding: const EdgeInsetsDirectional.all(20.0), + child: SingleChildScrollView( + child: Form( + key: _formkey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [const SizedBox(height: 50), otpVerifySet(), otpSentSet(), mblSet(), otpFillSet(), buildTimer(), submitBtn(), if (secondsRemaining == 0) showResendOTPButton()]))), + ); + } + + otpVerifySet() { + return CustomTextLabel( + text: 'otpVerifyLbl', + textStyle: Theme.of(context).textTheme.headlineSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w800, letterSpacing: 0.5), + textAlign: TextAlign.left); + } + + otpSentSet() { + return Padding( + padding: const EdgeInsets.only(top: 25.0), + child: CustomTextLabel(text: 'otpSentLbl', textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w500)), + ); + } + + mblSet() { + return Padding( + padding: const EdgeInsets.only(top: 10.0), + child: CustomTextLabel( + text: "${widget.countryCode} ${widget.mono}", textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8))), + ); + } + + otpFillSet() { + return Container( + alignment: Alignment.center, + padding: const EdgeInsets.only(top: 30.0), + child: PinFieldAutoFill( + decoration: BoxLooseDecoration( + strokeColorBuilder: PinListenColorBuilder(Theme.of(context).colorScheme.surface, Theme.of(context).colorScheme.surface), + bgColorBuilder: PinListenColorBuilder(Theme.of(context).colorScheme.surface, Theme.of(context).colorScheme.surface), + gapSpace: 7.0), + currentCode: otp, + codeLength: 6, + keyboardType: const TextInputType.numberWithOptions(signed: true, decimal: true), + onCodeChanged: (String? code) { + otp = code; + }, + onCodeSubmitted: (String code) { + otp = code; + })); + } + + showResendOTPButton() { + return Container( + padding: const EdgeInsets.only(top: 20.0), + alignment: Alignment.center, + child: Text.rich( + TextSpan( + text: "${UiUtils.getTranslatedLabel(context, 'didntGetCode')} ", + style: TextStyle(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8), fontWeight: FontWeight.bold, letterSpacing: 0.5), + children: [ + TextSpan( + text: UiUtils.getTranslatedLabel(context, 'resendLbl'), + style: TextStyle(color: UiUtils.getColorScheme(context).secondaryContainer, fontWeight: FontWeight.bold, letterSpacing: 0.5, decoration: TextDecoration.underline), + recognizer: TapGestureRecognizer()..onTap = enableResend ? _resendCode : null, + ), + ], + ), + textAlign: TextAlign.center, + )); + } + + buildTimer() { + return Container( + alignment: AlignmentDirectional.bottomStart, + padding: const EdgeInsets.only(top: 30.0), + child: secondsRemaining != 0 + ? Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + CustomTextLabel(text: 'resendCodeLbl', textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.8))), + CustomTextLabel(text: ' 00:${secondsRemaining.toString().padLeft(2, '0')}', textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: Theme.of(context).primaryColor)), + ], + ) + : SizedBox.shrink(), + ); + } + + submitBtn() { + return Container( + padding: const EdgeInsets.only(top: 45.0), + child: InkWell( + child: Container( + height: 45.0, + width: MediaQuery.of(context).size.width * 0.9, + alignment: Alignment.center, + decoration: BoxDecoration(color: Theme.of(context).primaryColor, borderRadius: BorderRadius.circular(7.0)), + child: + CustomTextLabel(text: 'submitBtn', textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: secondaryColor, fontWeight: FontWeight.w600, fontSize: 16, letterSpacing: 0.6)), + ), + onTap: () async { + if (otp!.trim().isEmpty) return showSnackBar(UiUtils.getTranslatedLabel(context, 'enterOtpTxt'), context); + FocusScope.of(context).unfocus(); //dismiss keyboard + + if (validateAndSave()) { + if (await InternetConnectivity.isNetworkAvailable()) { + context.read().socialSignUpUser(authProvider: AuthProviders.mobile, verifiedId: widget.verifyId, otp: otp, context: context); + } else { + showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context); + } + } + })); + } + + //check validation of form data + bool validateAndSave() { + final form = _formkey.currentState; + form!.save(); + if (form.validate()) { + return true; + } + return false; + } +} diff --git a/news-app/lib/ui/screens/auth/Widgets/bottomComBtn.dart b/news-app/lib/ui/screens/auth/Widgets/bottomComBtn.dart new file mode 100644 index 00000000..ef9c114f --- /dev/null +++ b/news-app/lib/ui/screens/auth/Widgets/bottomComBtn.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/uiUtils.dart'; + +class BottomCommButton extends StatelessWidget { + final Function onTap; + final String img; + final Color? btnColor; + final String btnCaption; + + const BottomCommButton({super.key, required this.onTap, required this.img, this.btnColor, required this.btnCaption}); + + @override + Widget build(BuildContext context) { + String textLbl = "${UiUtils.getTranslatedLabel(context, 'continueWith')} ${UiUtils.getTranslatedLabel(context, btnCaption)}"; + return InkWell( + splashColor: Colors.transparent, + child: Container( + height: 45.0, + width: MediaQuery.of(context).size.width * 0.9, + alignment: Alignment.center, + decoration: BoxDecoration(color: UiUtils.getColorScheme(context).surface, borderRadius: BorderRadius.circular(7.0)), + padding: const EdgeInsets.all(9.0), + margin: EdgeInsets.symmetric(vertical: 10), + child: Wrap( + spacing: 15, + children: [ + SvgPictureWidget(assetName: img, width: 20, height: 20, fit: BoxFit.contain, assetColor: btnColor != null ? ColorFilter.mode(btnColor!, BlendMode.srcIn) : null), + CustomTextLabel(text: textLbl, textStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.w600)) + ], + )), + onTap: () => onTap()); + } +} diff --git a/news-app/lib/ui/screens/auth/Widgets/fieldFocusChange.dart b/news-app/lib/ui/screens/auth/Widgets/fieldFocusChange.dart new file mode 100644 index 00000000..dae4eac5 --- /dev/null +++ b/news-app/lib/ui/screens/auth/Widgets/fieldFocusChange.dart @@ -0,0 +1,6 @@ +import 'package:flutter/cupertino.dart'; + +fieldFocusChange(BuildContext context, FocusNode currentFocus, FocusNode nextFocus) { + currentFocus.unfocus(); + FocusScope.of(context).requestFocus(nextFocus); +} diff --git a/news-app/lib/ui/screens/auth/Widgets/setConfimPass.dart b/news-app/lib/ui/screens/auth/Widgets/setConfimPass.dart new file mode 100644 index 00000000..42ebb9cc --- /dev/null +++ b/news-app/lib/ui/screens/auth/Widgets/setConfimPass.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:news/utils/uiUtils.dart'; + +class SetConfirmPass extends StatefulWidget { + final FocusNode currFocus; + final TextEditingController confPassC; + late String confPass; + late String pass; + + SetConfirmPass({super.key, required this.currFocus, required this.confPassC, required this.confPass, required this.pass}); + + @override + State createState() { + return _SetConfPassState(); + } +} + +class _SetConfPassState extends State { + bool isObscure = true; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(top: 20), + child: TextFormField( + focusNode: widget.currFocus, + textInputAction: TextInputAction.done, + controller: widget.confPassC, + obscureText: isObscure, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: UiUtils.getColorScheme(context).primaryContainer, + ), + validator: (value) { + if (value!.isEmpty) { + return UiUtils.getTranslatedLabel(context, 'confPassRequired'); + } + if (value.trim() != widget.pass.trim()) { + return UiUtils.getTranslatedLabel(context, 'confPassNotMatch'); + } else { + return null; + } + }, + onChanged: (String value) { + widget.confPass = value; + }, + decoration: InputDecoration( + hintText: UiUtils.getTranslatedLabel(context, 'confpassLbl'), + hintStyle: Theme.of(context).textTheme.titleMedium?.copyWith( + color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5), + ), + suffixIcon: Padding( + padding: const EdgeInsetsDirectional.only(end: 12.0), + child: IconButton( + icon: isObscure + ? Icon(Icons.visibility_rounded, size: 20, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6)) + : Icon(Icons.visibility_off_rounded, size: 20, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6)), + splashColor: Colors.transparent, + onPressed: () { + setState(() => isObscure = !isObscure); + }, + )), + filled: true, + fillColor: Theme.of(context).colorScheme.surface, + contentPadding: const EdgeInsets.symmetric(horizontal: 25, vertical: 17), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: UiUtils.getColorScheme(context).outline.withOpacity(0.7)), + borderRadius: BorderRadius.circular(10.0), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(10.0), + ), + ), + ), + ); + } +} diff --git a/news-app/lib/ui/screens/auth/Widgets/setDivider.dart b/news-app/lib/ui/screens/auth/Widgets/setDivider.dart new file mode 100644 index 00000000..04510b3f --- /dev/null +++ b/news-app/lib/ui/screens/auth/Widgets/setDivider.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/uiUtils.dart'; + +class SetDividerOR extends StatelessWidget { + const SetDividerOR({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + Color color = UiUtils.getColorScheme(context).outline.withOpacity(0.9); + return Padding( + padding: const EdgeInsetsDirectional.only(top: 30.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded(child: Divider(indent: 10, endIndent: 10, color: color)), + CustomTextLabel( + text: 'orLbl', + textStyle: Theme.of(context).textTheme.titleMedium?.merge(TextStyle(color: color, fontSize: 12.0)), + ), + Expanded(child: Divider(indent: 10, endIndent: 10, color: color)), + ], + )); + } +} diff --git a/news-app/lib/ui/screens/auth/Widgets/setEmail.dart b/news-app/lib/ui/screens/auth/Widgets/setEmail.dart new file mode 100644 index 00000000..559a8f7a --- /dev/null +++ b/news-app/lib/ui/screens/auth/Widgets/setEmail.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/utils/validators.dart'; + +import 'fieldFocusChange.dart'; + +class SetEmail extends StatelessWidget { + final FocusNode? currFocus; + final FocusNode? nextFocus; + final TextEditingController emailC; + late String email; + final double topPad; + + SetEmail({super.key, this.currFocus, this.nextFocus, required this.emailC, required this.email, required this.topPad}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.only(top: topPad), + child: TextFormField( + focusNode: currFocus, + textInputAction: TextInputAction.next, + controller: emailC, + style: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer), + validator: (val) => Validators.emailValidation(val!, context), + onFieldSubmitted: (v) { + if (currFocus != null || nextFocus != null) fieldFocusChange(context, currFocus!, nextFocus!); + }, + decoration: InputDecoration( + hintText: UiUtils.getTranslatedLabel(context, 'emailLbl'), + hintStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5)), + filled: true, + fillColor: Theme.of(context).colorScheme.surface, + contentPadding: const EdgeInsets.symmetric(horizontal: 25, vertical: 17), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: UiUtils.getColorScheme(context).outline.withOpacity(0.7)), + borderRadius: BorderRadius.circular(10.0), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(10.0), + ), + ), + ), + ); + } +} diff --git a/news-app/lib/ui/screens/auth/Widgets/setForgotPass.dart b/news-app/lib/ui/screens/auth/Widgets/setForgotPass.dart new file mode 100644 index 00000000..24d127b1 --- /dev/null +++ b/news-app/lib/ui/screens/auth/Widgets/setForgotPass.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; +import 'package:news/ui/widgets/customTextBtn.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/app/routes.dart'; + +setForgotPass(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(top: 5.0), + child: Align( + alignment: Alignment.topRight, + child: CustomTextButton( + onTap: () => Navigator.of(context).pushNamed(Routes.forgotPass), + buttonStyle: ButtonStyle(overlayColor: WidgetStateProperty.all(Colors.transparent), foregroundColor: WidgetStateProperty.all(UiUtils.getColorScheme(context).outline.withOpacity(0.7))), + textWidget: const CustomTextLabel(text: 'forgotPassLbl')))); +} diff --git a/news-app/lib/ui/screens/auth/Widgets/setLoginAndSignUpBtn.dart b/news-app/lib/ui/screens/auth/Widgets/setLoginAndSignUpBtn.dart new file mode 100644 index 00000000..d1c5d750 --- /dev/null +++ b/news-app/lib/ui/screens/auth/Widgets/setLoginAndSignUpBtn.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; + +class SetLoginAndSignUpBtn extends StatelessWidget { + final Function onTap; + final String text; + final double topPad; + + const SetLoginAndSignUpBtn({super.key, required this.onTap, required this.text, required this.topPad}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.only(top: topPad), + child: InkWell( + splashColor: Colors.transparent, + child: Container( + height: 45.0, + width: MediaQuery.of(context).size.width * 0.9, + alignment: Alignment.center, + decoration: BoxDecoration(color: Theme.of(context).primaryColor, borderRadius: BorderRadius.circular(7.0)), + child: CustomTextLabel( + text: text, + textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: secondaryColor, fontWeight: FontWeight.w600, fontSize: 16, letterSpacing: 0.6), + ), + ), + onTap: () => onTap()), + ); + } +} diff --git a/news-app/lib/ui/screens/auth/Widgets/setName.dart b/news-app/lib/ui/screens/auth/Widgets/setName.dart new file mode 100644 index 00000000..5cfc7ebe --- /dev/null +++ b/news-app/lib/ui/screens/auth/Widgets/setName.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/utils/validators.dart'; +import 'package:news/ui/screens/auth/Widgets/fieldFocusChange.dart'; + +class SetName extends StatelessWidget { + final FocusNode currFocus; + final FocusNode nextFocus; + final TextEditingController nameC; + late String name; + + SetName({super.key, required this.currFocus, required this.nextFocus, required this.nameC, required this.name}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(top: 40), + child: TextFormField( + focusNode: currFocus, + textInputAction: TextInputAction.next, + controller: nameC, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: UiUtils.getColorScheme(context).primaryContainer, + ), + validator: (val) => Validators.nameValidation(val!, context), + onFieldSubmitted: (v) { + fieldFocusChange(context, currFocus, nextFocus); + }, + decoration: InputDecoration( + hintText: UiUtils.getTranslatedLabel(context, 'nameLbl'), + hintStyle: Theme.of(context).textTheme.titleMedium?.copyWith( + color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5), + ), + filled: true, + fillColor: Theme.of(context).colorScheme.surface, + contentPadding: const EdgeInsets.symmetric(horizontal: 25, vertical: 17), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: UiUtils.getColorScheme(context).outline.withOpacity(0.7)), + borderRadius: BorderRadius.circular(10.0), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(10.0), + ), + ), + ), + ); + } +} diff --git a/news-app/lib/ui/screens/auth/Widgets/setPassword.dart b/news-app/lib/ui/screens/auth/Widgets/setPassword.dart new file mode 100644 index 00000000..47c6ceb1 --- /dev/null +++ b/news-app/lib/ui/screens/auth/Widgets/setPassword.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/utils/validators.dart'; + +import 'fieldFocusChange.dart'; + +class SetPassword extends StatefulWidget { + final FocusNode currFocus; + final FocusNode? nextFocus; + final TextEditingController passC; + late String pass; + final double topPad; + final bool isLogin; + + SetPassword({super.key, required this.currFocus, this.nextFocus, required this.passC, required this.pass, required this.topPad, required this.isLogin}); + + @override + State createState() { + return _SetPassState(); + } +} + +class _SetPassState extends State { + bool isObscure = true; + + @override + Widget build(BuildContext context) { + return StatefulBuilder(builder: (context, StateSetter setStater) { + return Padding( + padding: EdgeInsets.only(top: widget.topPad), + child: TextFormField( + focusNode: widget.currFocus, + textInputAction: widget.isLogin ? TextInputAction.done : TextInputAction.next, + controller: widget.passC, + obscureText: isObscure, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: UiUtils.getColorScheme(context).primaryContainer, + ), + validator: (val) => Validators.passValidation(val!, context), + onFieldSubmitted: (v) { + if (!widget.isLogin) { + fieldFocusChange(context, widget.currFocus, widget.nextFocus!); + } + }, + onChanged: (String value) { + widget.pass = value; + setStater(() {}); + }, + decoration: InputDecoration( + hintText: UiUtils.getTranslatedLabel(context, 'passLbl'), + hintStyle: Theme.of(context).textTheme.titleMedium?.copyWith( + color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5), + ), + suffixIcon: Padding( + padding: const EdgeInsetsDirectional.only(end: 12.0), + child: IconButton( + icon: isObscure + ? Icon(Icons.visibility_rounded, size: 20, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6)) + : Icon(Icons.visibility_off_rounded, size: 20, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6)), + splashColor: Colors.transparent, + onPressed: () { + setState(() => isObscure = !isObscure); + }, + )), + filled: true, + fillColor: Theme.of(context).colorScheme.surface, + contentPadding: const EdgeInsets.symmetric(horizontal: 25, vertical: 17), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: UiUtils.getColorScheme(context).outline.withOpacity(0.7)), + borderRadius: BorderRadius.circular(10.0), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(10.0), + ), + ), + ), + ); + }); + } +} diff --git a/news-app/lib/ui/screens/auth/Widgets/setTermPolicy.dart b/news-app/lib/ui/screens/auth/Widgets/setTermPolicy.dart new file mode 100644 index 00000000..3a20d5c7 --- /dev/null +++ b/news-app/lib/ui/screens/auth/Widgets/setTermPolicy.dart @@ -0,0 +1,40 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/privacyTermsCubit.dart'; +import 'package:news/utils/uiUtils.dart'; + +setTermPolicyTxt(BuildContext context, PrivacyTermsFetchSuccess state) { + return Container( + alignment: AlignmentDirectional.bottomCenter, + padding: const EdgeInsets.only(top: 20.0), + child: RichText( + textAlign: TextAlign.center, + text: TextSpan(children: [ + TextSpan( + text: "${UiUtils.getTranslatedLabel(context, 'agreeTermPolicyLbl')}\n", + style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), overflow: TextOverflow.ellipsis), + ), + TextSpan( + text: UiUtils.getTranslatedLabel(context, 'termLbl'), + style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: Theme.of(context).primaryColor, decoration: TextDecoration.underline, overflow: TextOverflow.ellipsis), + recognizer: TapGestureRecognizer() + ..onTap = (() { + Navigator.of(context).pushNamed(Routes.privacy, arguments: {"from": "login", "title": state.termsPolicy.title, "desc": state.termsPolicy.pageContent}); + }), + ), + TextSpan( + text: "\t${UiUtils.getTranslatedLabel(context, 'andLbl')}\t", + style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), overflow: TextOverflow.ellipsis), + ), + TextSpan( + text: UiUtils.getTranslatedLabel(context, 'priPolicy'), + style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: Theme.of(context).primaryColor, decoration: TextDecoration.underline, overflow: TextOverflow.ellipsis), + recognizer: TapGestureRecognizer() + ..onTap = (() { + Navigator.of(context).pushNamed(Routes.privacy, arguments: {"from": "login", "title": state.privacyPolicy.title, "desc": state.privacyPolicy.pageContent}); + }), + ), + ]), + )); +} diff --git a/news-app/lib/ui/screens/auth/Widgets/svgPictureWidget.dart b/news-app/lib/ui/screens/auth/Widgets/svgPictureWidget.dart new file mode 100644 index 00000000..d33a55e6 --- /dev/null +++ b/news-app/lib/ui/screens/auth/Widgets/svgPictureWidget.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:news/utils/uiUtils.dart'; + +class SvgPictureWidget extends StatelessWidget { + String assetName; + ColorFilter? assetColor; + double? height, width; + BoxFit? fit; + + SvgPictureWidget({Key? key, required this.assetName, this.assetColor, this.height, this.width, this.fit}) : super(key: key); + + @override + Widget build(BuildContext context) { + return SvgPicture.asset( + placeholderBuilder: (_) => Center(child: CircularProgressIndicator()), UiUtils.getSvgImagePath(assetName), colorFilter: assetColor, height: height, width: width, fit: fit ?? BoxFit.fill); + } +} diff --git a/news-app/lib/ui/screens/auth/loginScreen.dart b/news-app/lib/ui/screens/auth/loginScreen.dart new file mode 100644 index 00000000..b93cb027 --- /dev/null +++ b/news-app/lib/ui/screens/auth/loginScreen.dart @@ -0,0 +1,455 @@ +import 'dart:io'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/cubits/Auth/registerTokenCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; +import 'package:news/cubits/privacyTermsCubit.dart'; +import 'package:news/cubits/settingCubit.dart'; +import 'package:news/ui/screens/auth/Widgets/bottomComBtn.dart'; +import 'package:news/ui/screens/auth/Widgets/fieldFocusChange.dart'; +import 'package:news/ui/screens/auth/Widgets/setConfimPass.dart'; +import 'package:news/ui/screens/auth/Widgets/setDivider.dart'; +import 'package:news/ui/screens/auth/Widgets/setEmail.dart'; +import 'package:news/ui/screens/auth/Widgets/setForgotPass.dart'; +import 'package:news/ui/screens/auth/Widgets/setLoginAndSignUpBtn.dart'; +import 'package:news/ui/screens/auth/Widgets/setName.dart'; +import 'package:news/ui/screens/auth/Widgets/setPassword.dart'; +import 'package:news/ui/screens/auth/Widgets/setTermPolicy.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; + +import 'package:news/ui/widgets/customTextBtn.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/internetConnectivity.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/Auth/socialSignUpCubit.dart'; +import 'package:news/utils/validators.dart'; + +class LoginScreen extends StatefulWidget { + final bool? isFromApp; + const LoginScreen({super.key, this.isFromApp}); + + @override + LoginScreenState createState() => LoginScreenState(); + + static Route route(RouteSettings routeSettings) { + if (routeSettings.arguments == null) { + return CupertinoPageRoute(builder: (_) => const LoginScreen()); + } else { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute(builder: (_) => LoginScreen(isFromApp: arguments['isFromApp'] ?? false)); + } + } +} + +class LoginScreenState extends State with TickerProviderStateMixin { + final GlobalKey _formkey = GlobalKey(); + TabController? _tabController; + FocusNode emailFocus = FocusNode(); + FocusNode passFocus = FocusNode(); + FocusNode nameFocus = FocusNode(); + FocusNode emailSFocus = FocusNode(); + FocusNode passSFocus = FocusNode(); + FocusNode confPassFocus = FocusNode(); + TextEditingController? emailC, passC, sEmailC, sPassC, sNameC, sConfPassC; + String? name, email, pass, mobile, profile, confPass; + bool isPolicyAvailable = false; + bool isObscure = true; //setPassword widget + + @override + void initState() { + Future.delayed(Duration.zero, () { + context.read().getPrivacyTerms(langId: context.read().state.id); + }); + + _tabController = TabController(length: 2, vsync: this, initialIndex: 0); + assignAllTextController(); + _tabController!.addListener(() { + FocusScope.of(context).unfocus(); //dismiss keyboard + clearLoginTextFields(); + clearSignUpTextFields(); + }); + super.initState(); + } + + assignAllTextController() { + emailC = TextEditingController(); + passC = TextEditingController(); + sEmailC = TextEditingController(); + sPassC = TextEditingController(); + sNameC = TextEditingController(); + sConfPassC = TextEditingController(); + } + + clearSignUpTextFields() { + setState(() { + sNameC!.clear(); + sEmailC!.clear(); + sPassC!.clear(); + sConfPassC!.clear(); + }); + } + + clearLoginTextFields() { + setState(() { + emailC!.clear(); + passC!.clear(); + }); + } + + disposeAllTextController() { + emailC!.dispose(); + passC!.dispose(); + sEmailC!.dispose(); + sPassC!.dispose(); + sNameC!.dispose(); + sConfPassC!.dispose(); + } + + @override + void dispose() { + _tabController?.dispose(); + disposeAllTextController(); + + super.dispose(); + } + + showContent() { + return BlocConsumer( + bloc: context.read(), + listener: (context, state) async { + if (state is SocialSignUpFailure) { + showSnackBar(state.errorMessage, context); + } + if (state is SocialSignUpSuccess) { + context.read().checkAuthStatus(); + if (state.authModel.status == "0") { + showSnackBar(UiUtils.getTranslatedLabel(context, 'deactiveMsg'), context); + } else { + FirebaseMessaging.instance.getToken().then((token) async { + if (token != null) { + context.read().registerToken(fcmId: token, context: context); + if (token != context.read().getSettings().token) { + context.read().changeFcmToken(token); + } + if (state.authModel.isFirstLogin != null && state.authModel.isFirstLogin!.isNotEmpty && state.authModel.isFirstLogin == "0" && state.authModel.type != loginApple) { + Navigator.of(context).pushNamedAndRemoveUntil(Routes.editUserProfile, (route) => false, arguments: {"from": "login"}); + } else if (widget.isFromApp == true) { + Navigator.pop(context); + } else { + Navigator.pushNamedAndRemoveUntil(context, Routes.home, (route) => false); + } + } + }); + } + } + }, + builder: (context, state) { + return Form( + key: _formkey, + child: Stack( + children: [ + Container( + padding: const EdgeInsetsDirectional.only(top: 30.0, bottom: 5.0, start: 20.0, end: 20.0), + width: MediaQuery.of(context).size.width, + child: Column(crossAxisAlignment: CrossAxisAlignment.center, children: [skipBtn(), showTabs(), showTabBarView()]), + ), + if (state is SocialSignUpProgress) UiUtils.showCircularProgress(true, Theme.of(context).primaryColor), + ], + )); + }); + } + + skipBtn() { + return Align( + alignment: AlignmentDirectional.topEnd, + child: CustomTextButton( + onTap: () { + Navigator.of(context).pushReplacementNamed(Routes.home, arguments: false); + }, + text: UiUtils.getTranslatedLabel(context, 'skip'), + color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), + )); + } + + showTabs() { + return Align( + alignment: Alignment.centerLeft, + child: DefaultTabController( + length: 2, + child: Container( + padding: const EdgeInsetsDirectional.only(start: 10.0), + child: TabBar( + overlayColor: WidgetStateProperty.all(Colors.transparent), + controller: _tabController, + labelStyle: Theme.of(context).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600, letterSpacing: 0.5), + labelPadding: EdgeInsets.zero, + labelColor: backgroundColor, + unselectedLabelColor: UiUtils.getColorScheme(context).primaryContainer, + indicator: BoxDecoration(borderRadius: BorderRadius.circular(50), color: UiUtils.getColorScheme(context).secondaryContainer), + tabs: [Tab(text: UiUtils.getTranslatedLabel(context, 'signInTab')), Tab(text: UiUtils.getTranslatedLabel(context, 'signupBtn'))])), + )); + } + + bool validateAndSave() { + final form = _formkey.currentState; + form!.save(); + + if (!isPolicyAvailable) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'addTCFirst'), context); + return false; + } + + return form.validate(); + } + + Widget setPassword({required FocusNode currFocus, FocusNode? nextFocus, required TextEditingController passC, required String pass, required double topPad, required bool isLogin}) { + return Padding( + padding: EdgeInsets.only(top: topPad), + child: TextFormField( + focusNode: currFocus, + textInputAction: isLogin ? TextInputAction.done : TextInputAction.next, + controller: passC, + obscureText: isObscure, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: UiUtils.getColorScheme(context).primaryContainer, + ), + validator: (val) => Validators.passValidation(val!, context), + onFieldSubmitted: (v) { + if (!isLogin) { + fieldFocusChange(context, currFocus, nextFocus!); + } + }, + onChanged: (String value) { + pass = value; + setState(() {}); + }, + decoration: InputDecoration( + hintText: UiUtils.getTranslatedLabel(context, 'passLbl'), + hintStyle: Theme.of(context).textTheme.titleMedium?.copyWith( + color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.5), + ), + suffixIcon: Padding( + padding: const EdgeInsetsDirectional.only(end: 12.0), + child: IconButton( + icon: isObscure + ? Icon(Icons.visibility_rounded, size: 20, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6)) + : Icon(Icons.visibility_off_rounded, size: 20, color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6)), + splashColor: Colors.transparent, + onPressed: () { + setState(() => isObscure = !isObscure); + }, + )), + filled: true, + fillColor: Theme.of(context).colorScheme.surface, + contentPadding: const EdgeInsets.symmetric(horizontal: 25, vertical: 17), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: UiUtils.getColorScheme(context).outline.withOpacity(0.7)), + borderRadius: BorderRadius.circular(10.0), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(10.0), + ), + ), + ), + ); + } + + showTabBarView() { + return Expanded( + child: Container( + alignment: Alignment.center, + height: MediaQuery.of(context).size.height * 1.0, + child: TabBarView( + controller: _tabController, + dragStartBehavior: DragStartBehavior.start, + children: [ + //Login + SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 5.0), + child: Column(children: [ + loginTxt(), + SetEmail(currFocus: emailFocus, nextFocus: passFocus, emailC: emailC!, email: email ?? '', topPad: 20), + SetPassword(currFocus: passFocus, passC: passC!, pass: pass ?? '', topPad: 20, isLogin: true), + setForgotPass(context), + SetLoginAndSignUpBtn( + onTap: () async { + FocusScope.of(context).unfocus(); //dismiss keyboard + if (validateAndSave()) { + if (await InternetConnectivity.isNetworkAvailable()) { + context.read().socialSignUpUser(email: emailC!.text.trim(), password: passC!.text, authProvider: AuthProviders.email, context: context); + } else { + showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context); + } + } + }, + text: 'loginTxt', + topPad: 20), + SetDividerOR(), + bottomBtn(), + BlocConsumer(listener: (context, state) { + if (state is PrivacyTermsFetchSuccess) { + isPolicyAvailable = true; + } + if (state is PrivacyTermsFetchFailure) { + isPolicyAvailable = false; + } + }, builder: (context, state) { + return (state is PrivacyTermsFetchSuccess) ? setTermPolicyTxt(context, state) : const SizedBox.shrink(); + }) + ]), + )), + //SignUp + SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 5.0), + child: Column( + children: [ + signUpTxt(), + SetName(currFocus: nameFocus, nextFocus: emailSFocus, nameC: sNameC!, name: sNameC!.text), + SetEmail(currFocus: emailSFocus, nextFocus: passSFocus, emailC: sEmailC!, email: sEmailC!.text, topPad: 20), + setPassword(currFocus: passSFocus, nextFocus: confPassFocus, passC: sPassC!, pass: sPassC!.text, topPad: 20, isLogin: false), + SetConfirmPass(currFocus: confPassFocus, confPassC: sConfPassC!, confPass: sConfPassC!.text, pass: sPassC!.text), + SetLoginAndSignUpBtn( + onTap: () async { + FocusScope.of(context).unfocus(); //dismiss keyboard + final form = _formkey.currentState; + if (form!.validate()) { + form.save(); + if (await InternetConnectivity.isNetworkAvailable()) { + registerWithEmailPassword(sEmailC!.text.trim(), sPassC!.text.trim()); + } else { + showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context); + } + } + }, + text: 'signupBtn', + topPad: 25) + ], + ), + )) + ], + )), + ); + } + + registerWithEmailPassword(String email, String password) async { + try { + final credential = await FirebaseAuth.instance.createUserWithEmailAndPassword(email: email, password: password); + User? user = credential.user; + user!.updateDisplayName(sNameC!.text.trim()).then((value) => debugPrint("updated name is - ${user.displayName}")); + user.reload(); + + user.sendEmailVerification().then((value) => showSnackBar('${UiUtils.getTranslatedLabel(context, 'verifSentMail')} $email', context)); + clearSignUpTextFields(); + _tabController!.animateTo(0); + FocusScope.of(context).requestFocus(emailFocus); + } on FirebaseAuthException catch (e) { + if (e.code == 'weakPassword') { + showSnackBar(UiUtils.getTranslatedLabel(context, 'weakPassword'), context); + } + if (e.code == 'email-already-in-use') { + showSnackBar(UiUtils.getTranslatedLabel(context, 'emailAlreadyInUse'), context); + } + } catch (e) {} + } + + loginTxt() { + return Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsetsDirectional.only(top: 20.0, start: 10.0), + child: CustomTextLabel( + text: 'loginDescr', + textStyle: Theme.of(context).textTheme.headlineSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w800, letterSpacing: 0.5), + textAlign: TextAlign.left, + ), + )); + } + + signUpTxt() { + return Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsetsDirectional.only(top: 35.0, start: 10.0), + child: CustomTextLabel( + text: 'signupDescr', + textStyle: Theme.of(context).textTheme.headlineSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w800, letterSpacing: 0.5), + textAlign: TextAlign.left, + ), + )); + } + + bottomBtn() { + return Padding( + padding: const EdgeInsets.only(top: 20.0), + child: Column( + mainAxisAlignment: (fblogInEnabled) ? MainAxisAlignment.spaceBetween : MainAxisAlignment.spaceAround, + children: [ + BottomCommButton( + btnCaption: 'google', + onTap: () { + if (!isPolicyAvailable) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'addTCFirst'), context); + } else { + context.read().socialSignUpUser(authProvider: AuthProviders.gmail, context: context); + } + }, + img: 'google_button'), + if (fblogInEnabled) + BottomCommButton( + onTap: () { + if (!isPolicyAvailable) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'addTCFirst'), context); + } else { + context.read().socialSignUpUser(authProvider: AuthProviders.fb, context: context); + } + }, + img: 'facebook_button', + btnCaption: 'fb'), + if (Platform.isIOS) + BottomCommButton( + onTap: () { + if (!isPolicyAvailable) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'addTCFirst'), context); + } else { + context.read().socialSignUpUser(authProvider: AuthProviders.apple, context: context); + } + }, + img: 'apple_logo', + btnCaption: 'apple', + btnColor: UiUtils.getColorScheme(context).primaryContainer), + if (context.read().getMobileLoginMode() != "" && context.read().getMobileLoginMode() != "0") + BottomCommButton( + onTap: () { + if (!isPolicyAvailable) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'addTCFirst'), context); + } else { + Navigator.of(context).pushNamed(Routes.requestOtp); + } + }, + img: 'phone_button', + btnCaption: 'mobileLbl', + btnColor: UiUtils.getColorScheme(context).primaryContainer) + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: showContent(), + ); + } +} diff --git a/news-app/lib/ui/screens/authorDetailsScreen.dart b/news-app/lib/ui/screens/authorDetailsScreen.dart new file mode 100644 index 00000000..f445f957 --- /dev/null +++ b/news-app/lib/ui/screens/authorDetailsScreen.dart @@ -0,0 +1,275 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/Author/authorNewsCubit.dart'; +import 'package:news/cubits/NewsByIdCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/customBackBtn.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/errorContainerWidget.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class AuthorDetailsScreen extends StatefulWidget { + final String authorId; + const AuthorDetailsScreen({super.key, required this.authorId}); + + @override + State createState() => _AuthorDetailsScreenState(); + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute(builder: (_) => AuthorDetailsScreen(authorId: arguments['authorId'])); + } +} + +class _AuthorDetailsScreenState extends State { + AuthorLayoutType layout = AuthorLayoutType.list; + double borderRadius = 10; + late NewsModel newsData; + String totalViews = "0", totalLikes = "0"; + + @override + void initState() { + getNewsByAuthor(); + super.initState(); + } + + void getNewsByAuthor() { + context.read().getAuthorNews(authorId: widget.authorId); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const CustomTextLabel(text: 'authorLbl'), + leading: CustomBackButton(), + actions: [ + Padding( + padding: const EdgeInsets.all(3.0), + child: Container( + margin: EdgeInsets.all(10), + decoration: BoxDecoration(border: Border.all(color: UiUtils.getColorScheme(context).outline), borderRadius: BorderRadius.circular(5)), + child: GestureDetector( + child: Icon(layout == AuthorLayoutType.list ? Icons.grid_view : Icons.view_list, size: 25), + onTap: () { + setState(() { + layout = layout == AuthorLayoutType.list ? AuthorLayoutType.grid : AuthorLayoutType.list; + }); + }, + ), + ), + ), + ], + ), + body: BlocBuilder( + builder: (context, state) { + if (state is AuthorNewsFetchSuccess) { + return Column( + children: [ + authorHeader(state: state), + const SizedBox(height: 10), + Expanded(child: layout == AuthorLayoutType.list ? authorNewsListView(state: state) : authorNewsGridView(state: state)), + ], + ); + } else if (state is AuthorNewsFetchFailed) { + return ErrorContainerWidget(errorMsg: state.errorMessage, onRetry: getNewsByAuthor); + } else { + return const SizedBox.shrink(); + } + }, + ), + ); + } + + Widget authorHeader({required AuthorNewsFetchSuccess state}) { + return Container( + margin: const EdgeInsets.all(16), + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: UiUtils.getColorScheme(context).surface, + borderRadius: BorderRadius.circular(borderRadius), + ), + child: Column( + children: [ + Row( + children: [ + // author image + ClipRRect(borderRadius: BorderRadius.circular(6), child: CustomNetworkImage(networkImageUrl: state.authorData.profile ?? "")), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomTextLabel(text: state.authorData.name ?? "", textStyle: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)), + SizedBox(height: 6), + CustomTextLabel(text: state.authorData.authorData!.bio ?? "", textStyle: TextStyle(fontSize: 13)), + ], + ), + ), + ], + ), + Padding( + padding: const EdgeInsets.all(8), + child: Row( + children: [ + CustomTextLabel(text: 'followLbl'), + SizedBox(width: 6), + (state.authorData.authorData != null) + ? Row(spacing: 6.5, children: [ + showSocialMediaLinks(socialMediaLink: state.authorData.authorData!.telegramLink ?? "", socialMediaIconName: "telegram"), + showSocialMediaLinks(socialMediaLink: state.authorData.authorData!.facebookLink ?? "", socialMediaIconName: "facebook"), + showSocialMediaLinks(socialMediaLink: state.authorData.authorData!.whatsappLink ?? "", socialMediaIconName: "whatsapp"), + showSocialMediaLinks(socialMediaLink: state.authorData.authorData!.linkedinLink ?? "", socialMediaIconName: "linkedin"), + ]) + : SizedBox.shrink() + ], + ), + ) + ], + ), + ); + } + + Widget showSocialMediaLinks({required String socialMediaLink, required String socialMediaIconName}) { + return Container( + height: 30, + width: 30, + padding: EdgeInsets.all(3), + decoration: BoxDecoration(shape: BoxShape.rectangle, borderRadius: BorderRadius.circular(7), color: UiUtils.getColorScheme(context).outline.withOpacity(0.2)), + child: GestureDetector( + onTap: () async { + if (await canLaunchUrl(Uri.parse(socialMediaLink))) { + await launchUrl(Uri.parse(socialMediaLink), mode: LaunchMode.externalApplication); + } + }, + child: ClipRRect( + borderRadius: BorderRadius.circular(7), + child: SvgPictureWidget( + assetName: socialMediaIconName, + height: 11, + width: 11, + fit: BoxFit.contain, + assetColor: ColorFilter.mode(UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), BlendMode.srcIn)), + )), + ); + } + + Widget authorNewsListView({required AuthorNewsFetchSuccess state}) { + return ListView.builder( + padding: const EdgeInsets.symmetric(horizontal: 16), itemCount: state.AuthorNewsList.length, itemBuilder: (BuildContext, index) => newsListTile(newsItem: state.AuthorNewsList[index])); + } + + Widget newsListTile({required NewsModel newsItem}) { + return GestureDetector( + onTap: () => redirectToNewsDetailsScreen(newsItem: newsItem), + child: Container( + margin: const EdgeInsets.only(bottom: 10), + decoration: BoxDecoration(color: UiUtils.getColorScheme(context).surface, borderRadius: BorderRadius.circular(12)), + child: Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.only(topLeft: Radius.circular(borderRadius), bottomLeft: Radius.circular(borderRadius)), + child: CustomNetworkImage(networkImageUrl: newsItem.image!, width: 110, height: 110, fit: BoxFit.cover)), + const SizedBox(width: 12), + Expanded( + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomTextLabel(text: newsItem.title ?? "", maxLines: 2, overflow: TextOverflow.ellipsis, textStyle: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), + SizedBox(height: 6), + CustomTextLabel(text: newsItem.desc ?? "", maxLines: 3, overflow: TextOverflow.ellipsis, textStyle: TextStyle(fontSize: 13)), + SizedBox(height: 6), + Divider(), + SizedBox(height: 3), + Row( + children: [ + CustomTextLabel( + text: UiUtils.convertToAgo(context, DateTime.parse(newsItem.publishDate ?? newsItem.date!), 0)!, + textStyle: TextStyle(fontSize: 12, color: UiUtils.getColorScheme(context).outline)), + Spacer(), + showViewsAndLikes(newsId: newsItem.id ?? "0", totalViews: newsItem.totalViews ?? "0", totalLikes: newsItem.totalLikes ?? "0") + ], + ), + ], + ), + ), + ) + ], + ), + ), + ); + } + + Widget showViewsAndLikes({required String newsId, required String totalViews, required String totalLikes}) { + return FutureBuilder( + future: context.read().getNewsById(newsId: newsId, langId: context.read().state.id), + builder: (context, snapshot) { + final updated = snapshot.data![0]; + totalViews = updated.totalViews ?? "0"; + totalLikes = updated.totalLikes ?? "0"; + return Row( + spacing: 15, + children: [ + Row( + spacing: 5, + children: [Icon(Icons.remove_red_eye_rounded, size: 10), CustomTextLabel(text: totalViews, textStyle: TextStyle(fontSize: 12))], + ), + Row( + spacing: 5, + children: [Icon(Icons.thumb_up_alt_outlined, size: 10), CustomTextLabel(text: totalLikes, textStyle: TextStyle(fontSize: 12))], + ), + ], + ); + }); + } + + Widget authorNewsGridView({required AuthorNewsFetchSuccess state}) { + return GridView.builder( + padding: const EdgeInsets.symmetric(horizontal: 16), + itemCount: state.AuthorNewsList.length, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, mainAxisSpacing: 16, crossAxisSpacing: 16, childAspectRatio: 0.65), + itemBuilder: (BuildContext, index) { + return newsGridTile(newsItem: state.AuthorNewsList[index]); + }); + } + + Widget newsGridTile({required NewsModel newsItem}) { + return GestureDetector( + onTap: () => redirectToNewsDetailsScreen(newsItem: newsItem), + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(borderRadius), color: UiUtils.getColorScheme(context).surface, boxShadow: const [BoxShadow(blurRadius: 5, spreadRadius: 1, color: dividerColor)]), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect(borderRadius: BorderRadius.circular(8), child: CustomNetworkImage(networkImageUrl: newsItem.image!, height: 120, width: double.infinity, fit: BoxFit.cover)), + const SizedBox(height: 8), + CustomTextLabel( + text: UiUtils.convertToAgo(context, DateTime.parse(newsItem.publishDate ?? newsItem.date!), 0)!, textStyle: TextStyle(color: UiUtils.getColorScheme(context).outline, fontSize: 11)), + const SizedBox(height: 4), + CustomTextLabel(text: newsItem.title ?? "", maxLines: 2, overflow: TextOverflow.ellipsis, textStyle: TextStyle(fontWeight: FontWeight.w600, fontSize: 14)), + const SizedBox(height: 4), + CustomTextLabel(text: newsItem.desc ?? "", maxLines: 2, overflow: TextOverflow.ellipsis, textStyle: TextStyle(fontSize: 12)), + Divider(), + showViewsAndLikes(newsId: newsItem.id ?? "0", totalViews: newsItem.totalViews ?? "0", totalLikes: newsItem.totalLikes ?? "0") + ], + ), + ), + ); + } + + void redirectToNewsDetailsScreen({required NewsModel newsItem}) { + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": newsItem, "isFromBreak": false, "fromShowMore": false}); + } +} diff --git a/news-app/lib/ui/screens/dashBoard/dashBoardScreen.dart b/news-app/lib/ui/screens/dashBoard/dashBoardScreen.dart new file mode 100644 index 00000000..eb37b5c8 --- /dev/null +++ b/news-app/lib/ui/screens/dashBoard/dashBoardScreen.dart @@ -0,0 +1,213 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/cubits/ConnectivityCubit.dart'; +import 'package:news/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/Bookmark/bookmarkCubit.dart'; +import 'package:news/cubits/NewsByIdCubit.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; +import 'package:news/cubits/breakingNewsCubit.dart'; +import 'package:news/cubits/languageCubit.dart'; +import 'package:news/cubits/slugNewsCubit.dart'; +import 'package:news/cubits/themeCubit.dart'; +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/ui/screens/CategoryScreen.dart'; +import 'package:news/ui/screens/HomePage/HomePage.dart'; +import 'package:news/ui/screens/Profile/ProfileScreen.dart'; +import 'package:news/ui/screens/RSSFeedScreen.dart'; +import 'package:news/ui/screens/Videos/VideoScreen.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/errorContainerWidget.dart'; +import 'package:news/utils/strings.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/app/routes.dart'; + +GlobalKey? homeScreenKey; +bool? isNotificationReceivedInbg, isShared; +String? notificationNewsId; +String? saleNotification; +String? routeSettingsName, newsSlug; + +class DashBoard extends StatefulWidget { + const DashBoard({super.key}); + + @override + DashBoardState createState() => DashBoardState(); + + static Route route(RouteSettings routeSettings) { + return CupertinoPageRoute(builder: (_) => const DashBoard()); + } +} + +class DashBoardState extends State { + List fragments = []; + DateTime? currentBackPressTime; + int _selectedIndex = 0; + List iconList = []; + List itemName = []; + bool shouldPopScope = false; + + @override + void initState() { + homeScreenKey = GlobalKey(); + iconList = [ + Icons.home_rounded, + Icons.video_collection_rounded, + //Add only if Category Mode is enabled From Admin panel. + if (context.read().getCategoryMode() == "1") Icons.grid_view_rounded, + if (context.read().getRSSFeedMode() == "1") Icons.rss_feed_rounded, + Icons.settings_rounded + ]; + itemName = [ + 'homeLbl', + 'videosLbl', + if (context.read().getCategoryMode() == "1") 'categoryLbl', + if (context.read().getRSSFeedMode() == "1") 'rssFeed', + 'profile' + ]; + fragments = [ + HomeScreen(key: homeScreenKey), + const VideoScreen(), + //Add only if Category Mode is enabled From Admin panel. + if (context.read().getCategoryMode() == "1") const CategoryScreen(), + if (context.read().getRSSFeedMode() == "1") RSSFeedScreen(), + const ProfileScreen(), + ]; + if ((isShared != null && isShared == true) && routeSettingsName != null && newsSlug != null) initDynamicLinks(); + checkForPengingNotifications(); + checkMaintenanceMode(); + + super.initState(); + } + + void checkMaintenanceMode() { + if (context.read().getMaintenanceMode() == "1") { + //app is in maintenance mode - no function should be performed + Navigator.of(context).pushReplacementNamed(Routes.maintenance); + } + } + + void checkForPengingNotifications() async { + if (isNotificationReceivedInbg != null && notificationNewsId != null && notificationNewsId != "0" && isNotificationReceivedInbg!) { + context.read().getNewsById(newsId: notificationNewsId!, langId: context.read().state.id).then((value) { + if (value.isNotEmpty) { + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": value[0], "isFromBreak": false, "fromShowMore": false}); + } + }); + } + } + + void initDynamicLinks() async { + await Future.delayed(Duration(seconds: 2)); // Simulate API delay + if (routeSettingsName!.contains('/news/')) { + String langCodeShared = routeSettingsName!.split("/")[1]; + String? langIdPass = UiUtils.rootNavigatorKey.currentContext!.read().state.id; + if (context.read().langList().isNotEmpty) langIdPass = context.read().langList().firstWhere((e) => e.code == langCodeShared).id; + UiUtils.rootNavigatorKey.currentContext?.read().getSlugNews(langId: langIdPass ?? "0", newsSlug: newsSlug).then((value) { + NewsModel? model = (value[DATA] as List).map((e) => NewsModel.fromJson(e)).toList().first; + Navigator.pushNamed(context, Routes.newsDetails, + arguments: {"model": model, "slug": newsSlug, "isFromBreak": routeSettingsName!.contains('/breaking-news/') ? true : false, "fromShowMore": false}); + }); + } else if (routeSettingsName!.contains('/breaking-news/')) { + //for breaking news + UiUtils.rootNavigatorKey.currentContext?.read().getBreakingNews(langId: UiUtils.rootNavigatorKey.currentContext!.read().state.id).then((value) { + BreakingNewsModel? brModel = value[0]; + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"breakModel": brModel, "slug": newsSlug, "isFromBreak": true, "fromShowMore": false}); + }); + } + isShared = false; //reset + } + + onWillPop(bool isTrue) { + DateTime now = DateTime.now(); + if (_selectedIndex != 0) { + setState(() { + _selectedIndex = 0; + shouldPopScope = false; + }); + } else if (currentBackPressTime == null || now.difference(currentBackPressTime!) > const Duration(seconds: 2)) { + currentBackPressTime = now; + showSnackBar(UiUtils.getTranslatedLabel(context, 'exitWR'), context); + setState(() => shouldPopScope = false); + } + + setState(() => shouldPopScope = true); + } + + Widget buildNavBarItem(IconData icon, String itemName, int index) { + return InkWell( + onTap: () { + setState(() => _selectedIndex = index); + }, + child: Container( + height: 60, + width: MediaQuery.of(context).size.width / iconList.length, + decoration: index == _selectedIndex ? BoxDecoration(border: Border(top: BorderSide(width: 3, color: Theme.of(context).primaryColor))) : null, + child: Column( + children: [ + SizedBox(height: 3), + Icon(icon, color: index == _selectedIndex ? Theme.of(context).primaryColor : UiUtils.getColorScheme(context).outline), + SizedBox(height: 2.5), + CustomTextLabel( + text: itemName, softWrap: true, textStyle: TextStyle(color: (index == _selectedIndex) ? Theme.of(context).primaryColor : UiUtils.getColorScheme(context).outline, fontSize: 12)) + ], + ), + ), + ); + } + + bottomBar() { + List navBarItemList = []; + for (var i = 0; i < iconList.length; i++) { + navBarItemList.add(buildNavBarItem(iconList[i], itemName[i], i)); + } + + return Container( + padding: (Platform.isIOS) ? EdgeInsets.only(bottom: 15) : EdgeInsets.zero, + decoration: BoxDecoration( + color: UiUtils.getColorScheme(context).secondary, + borderRadius: const BorderRadius.only(topLeft: Radius.circular(10.0), topRight: Radius.circular(10.0)), + boxShadow: [BoxShadow(blurRadius: 6, offset: const Offset(5.0, 5.0), color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.4), spreadRadius: 0)], + ), + child: ClipRRect(borderRadius: const BorderRadius.only(topLeft: Radius.circular(10.0), topRight: Radius.circular(10.0)), child: Row(children: navBarItemList))); + } + + @override + Widget build(BuildContext context) { + UiUtils.setUIOverlayStyle(appTheme: context.read().state.appTheme); //set UiOverlayStyle according to selected theme + return PopScope( + canPop: shouldPopScope, + onPopInvoked: onWillPop, + child: BlocConsumer( + listener: (context, state) { + if (state is Authenticated) { + Future.delayed(Duration.zero, () { + context.read().getBookmark(langId: context.read().state.id); + context.read().getLike(langId: context.read().state.id); + }); + } + }, + builder: (context, state) { + return Scaffold( + bottomNavigationBar: bottomBar(), + body: BlocBuilder( + builder: (context, connectivityStatus) { + if (connectivityStatus is ConnectivityDisconnected) { + return ErrorContainerWidget(errorMsg: UiUtils.getTranslatedLabel(context, 'internetmsg'), onRetry: () {}); + } else { + return IndexedStack(index: _selectedIndex, children: fragments); + } + }, + ), + ); + }, + ), + ); + } +} diff --git a/news-app/lib/ui/screens/filter/FilterBottomSheet.dart b/news-app/lib/ui/screens/filter/FilterBottomSheet.dart new file mode 100644 index 00000000..1553ca9a --- /dev/null +++ b/news-app/lib/ui/screens/filter/FilterBottomSheet.dart @@ -0,0 +1,413 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/categoryCubit.dart'; +import 'package:news/cubits/tagCubit.dart'; +import 'package:news/data/models/CategoryModel.dart'; +import 'package:news/data/models/TagModel.dart'; +import 'package:news/ui/screens/filter/widgets/custom_date_selector.dart'; +import 'package:news/ui/screens/filter/widgets/duration_filter_widget.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/utils/uiUtils.dart'; + +class NewsFilterData { + final List selectedCategories; + final List selectedTags; + final DateTime? selectedDate; + final DurationFilter? durationFilter; + + NewsFilterData({required this.selectedCategories, required this.selectedTags, this.selectedDate, this.durationFilter}); + + @override + String toString() { + return 'NewsFilterData(selectedCategories: $selectedCategories, selectedTags: $selectedTags, selectedDate: $selectedDate, durationFilter: $durationFilter)'; + } +} + +class FilterBottomSheet extends StatefulWidget { + final NewsFilterData initialFilters; + final bool isCategoryModeON; + + const FilterBottomSheet({ + Key? key, + required this.isCategoryModeON, + required this.initialFilters, + }) : super(key: key); + + @override + State createState() => _FilterBottomSheetState(); +} + +class _FilterBottomSheetState extends State { + late final ValueNotifier selectedFilterTabIndex = ValueNotifier(0); + late List selectedCategories; + late List selectedTags; + DateTime? selectedDate; + + List filterTabs = []; + + late DurationFilter? durationFilter = widget.initialFilters.durationFilter; + @override + void initState() { + super.initState(); + filterTabs = [if (widget.isCategoryModeON) "catLbl", "tagLbl", "date"]; + + selectedCategories = List.from(widget.initialFilters.selectedCategories); + selectedTags = List.from(widget.initialFilters.selectedTags); + selectedDate = widget.initialFilters.selectedDate; + } + + @override + void dispose() { + selectedFilterTabIndex.dispose(); + super.dispose(); + } + + Widget _buildFilterTabItem({required String title, required int index}) { + return ValueListenableBuilder( + valueListenable: selectedFilterTabIndex, + builder: (context, value, child) { + final isSelected = value == index; + return InkWell( + onTap: () { + selectedFilterTabIndex.value = index; + }, + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 15), + decoration: BoxDecoration( + color: isSelected ? UiUtils.getColorScheme(context).secondaryContainer : Colors.transparent, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: isSelected ? UiUtils.getColorScheme(context).secondary : UiUtils.getColorScheme(context).primaryContainer, + ), + ), + child: Text( + title, + textAlign: TextAlign.center, + style: TextStyle( + color: isSelected ? UiUtils.getColorScheme(context).surface : UiUtils.getColorScheme(context).onSurface, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + ), + ); + }, + ); + } + + Widget _buildCategoryList() { + return BlocBuilder( + builder: (context, state) { + if (state is CategoryFetchInProgress) { + return const Center(child: CircularProgressIndicator()); + } + if (state is CategoryFetchFailure) { + return Center(child: Text(state.errorMessage)); + } + if (state is CategoryFetchSuccess) { + return NotificationListener( + onNotification: (notification) { + if (notification is ScrollUpdateNotification) { + if (notification.metrics.pixels == notification.metrics.maxScrollExtent) { + if (context.read().hasMoreCategory()) { + context.read().getMoreCategory(langId: context.read().state.id); + } + } + } + return true; + }, + child: ListView.builder( + shrinkWrap: true, + physics: const BouncingScrollPhysics(), + itemCount: state.category.length, + itemBuilder: (context, index) { + final category = state.category[index]; + final isSelected = selectedCategories.contains(category); + + return CheckboxListTile( + title: Text( + category.categoryName ?? "", + style: TextStyle( + color: UiUtils.getColorScheme(context).onSurface, + fontWeight: FontWeight.w500, + ), + ), + value: isSelected, + activeColor: UiUtils.getColorScheme(context).primary, + checkColor: UiUtils.getColorScheme(context).onPrimary, + onChanged: (bool? value) { + setState(() { + if (value == true) { + if (!selectedCategories.contains(category)) { + selectedCategories.add(category); + } + } else { + selectedCategories.remove(category); + } + }); + }, + controlAffinity: ListTileControlAffinity.trailing, + ); + }, + ), + ); + } + return Container(); + }, + ); + } + + Widget _buildTagList() { + return BlocBuilder( + builder: (context, state) { + if (state is TagFetchInProgress) { + return const Center(child: CircularProgressIndicator()); + } + if (state is TagFetchFailure) { + return Center(child: Text(state.errorMessage)); + } + if (state is TagFetchSuccess) { + return NotificationListener( + onNotification: (notification) { + if (notification is ScrollUpdateNotification) { + if (notification.metrics.pixels == notification.metrics.maxScrollExtent) { + if (context.read().hasMoreTags()) { + context.read().getMoreTags(langId: context.read().state.id); + } + } + } + return true; + }, + child: ListView.builder( + shrinkWrap: true, + physics: const BouncingScrollPhysics(), + itemCount: state.tag.length, + itemBuilder: (context, index) { + final tag = state.tag[index]; + final isSelected = selectedTags.contains(tag); + + return CheckboxListTile( + title: Text( + tag.tagName ?? "", + style: TextStyle( + color: UiUtils.getColorScheme(context).onSurface, + fontWeight: FontWeight.w500, + ), + ), + value: isSelected, + activeColor: UiUtils.getColorScheme(context).primary, + checkColor: UiUtils.getColorScheme(context).onPrimary, + onChanged: (bool? value) { + setState(() { + if (value == true) { + if (!selectedTags.contains(tag)) { + selectedTags.add(tag); + } + } else { + selectedTags.remove(tag); + } + }); + }, + controlAffinity: ListTileControlAffinity.trailing, + ); + }, + ), + ); + } + return Container(); + }, + ); + } + + Widget _buildDatePicker() { + return Padding( + padding: const EdgeInsets.all(16.0), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 250, + child: CustomCalendarView( + selectedDate: selectedDate, + onDateSelected: (day) { + selectedDate = day; + setState(() {}); + }, + )), + DurationFilterWidget( + selectedFilter: durationFilter, + filters: [ + LastDays(daysCount: 1), + LastDays(daysCount: 7), + LastDays(daysCount: 30), + LastDays(daysCount: 60), + LastDays(daysCount: 90), + ...List.generate( + 7, + (int index) { + return Year(year: DateTime.now().year - index); + }, + ), + ], + onSelected: (DurationFilter? filter) { + durationFilter = filter; + setState(() {}); + }, + ) + ], + ), + ), + ); + } + + Widget _buildFilterContent() { + return ValueListenableBuilder( + valueListenable: selectedFilterTabIndex, + builder: (context, selectedIndex, _) { + final views = widget.isCategoryModeON ? [_buildCategoryList(), _buildTagList(), _buildDatePicker()] : [_buildTagList(), _buildDatePicker()]; + return views[selectedIndex]; + }, + ); + } + + @override + Widget build(BuildContext context) { + return Container( + height: MediaQuery.of(context).size.height * 0.7, + decoration: BoxDecoration( + color: UiUtils.getColorScheme(context).surface, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + ), + child: Column( + children: [ + Container( + width: 40, + height: 5, + margin: const EdgeInsets.symmetric(vertical: 10), + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(10), + ), + ), + Text( + UiUtils.getTranslatedLabel(context, "filter"), + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: UiUtils.getColorScheme(context).onSurface, + ), + ), + Divider(), + const SizedBox(height: 15), + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 3, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: filterTabs + .asMap() + .entries + .map((entry) => Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: _buildFilterTabItem( + title: UiUtils.getTranslatedLabel(context, entry.value), + index: entry.key, + ), + )) + .toList(), + ), + ), + ), + Container( + width: 1, + color: Colors.grey[300], + height: MediaQuery.of(context).size.height * 0.5, + ), + Expanded( + flex: 7, + child: _buildFilterContent(), + ), + ], + ), + ), + const Divider(height: 1), + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: () { + setState(() { + selectedCategories = []; + selectedTags = []; + selectedDate = null; + durationFilter = null; + }); + }, + style: OutlinedButton.styleFrom( + side: BorderSide(color: primaryColor), + padding: const EdgeInsets.symmetric(vertical: 12), + ), + child: Text( + UiUtils.getTranslatedLabel(context, 'clear'), + style: TextStyle(color: primaryColor, fontWeight: FontWeight.bold), + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: ElevatedButton( + onPressed: () { + final result = NewsFilterData(selectedCategories: selectedCategories, selectedTags: selectedTags, selectedDate: selectedDate, durationFilter: durationFilter); + Navigator.pop(context, result); + }, + style: ElevatedButton.styleFrom( + backgroundColor: primaryColor, + padding: const EdgeInsets.symmetric(vertical: 12), + ), + child: Text( + UiUtils.getTranslatedLabel(context, 'apply'), + style: TextStyle( + color: UiUtils.getColorScheme(context).onPrimary, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} + +// Function to show the filter bottom sheet +Future showFilterBottomSheet({ + required BuildContext context, + required bool isCategoryModeON, + required NewsFilterData initialFilters, +}) async { + return await showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) => FilterBottomSheet( + isCategoryModeON: isCategoryModeON, + initialFilters: initialFilters, + )); +} diff --git a/news-app/lib/ui/screens/filter/widgets/custom_date_selector.dart b/news-app/lib/ui/screens/filter/widgets/custom_date_selector.dart new file mode 100644 index 00000000..9fd0b764 --- /dev/null +++ b/news-app/lib/ui/screens/filter/widgets/custom_date_selector.dart @@ -0,0 +1,169 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/utils/uiUtils.dart'; + +class CustomDateSelector extends StatefulWidget { + final DateTime? selectedDate; + final void Function(DateTime? date) onDateChanged; + const CustomDateSelector({ + Key? key, + required this.onDateChanged, + this.selectedDate, + }) : super(key: key); + @override + _CustomDateSelectorState createState() => _CustomDateSelectorState(); +} + +class _CustomDateSelectorState extends State { + late DateTime? selectedDate = widget.selectedDate; + + @override + void didUpdateWidget(covariant CustomDateSelector oldWidget) { + if (oldWidget.selectedDate != widget.selectedDate) { + selectedDate = widget.selectedDate; + setState(() {}); + } + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + return Container( + child: Theme( + data: Theme.of(context).copyWith( + colorScheme: + ColorScheme.of(context).copyWith(primary: primaryColor), + datePickerTheme: UiUtils.buildDatePickerTheme(context)), + child: CalendarDatePicker( + initialDate: DateTime.now(), + firstDate: DateTime(1999), + currentDate: selectedDate, + lastDate: DateTime.now(), + onDateChanged: (DateTime date) { + setState(() { + selectedDate = date; + }); + widget.onDateChanged(date); + }, + ), + ), + ); + } +} + +class CustomCalendarView extends StatefulWidget { + final Function(DateTime date) onDateSelected; + final DateTime? selectedDate; + const CustomCalendarView( + {super.key, required this.onDateSelected, this.selectedDate}); + @override + _CustomCalendarViewState createState() => _CustomCalendarViewState(); +} + +class _CustomCalendarViewState extends State { + DateTime selectedDate = DateTime.now(); + + @override + Widget build(BuildContext context) { + final firstDayOfMonth = DateTime(selectedDate.year, selectedDate.month, 1); + final startWeekday = firstDayOfMonth.weekday; // Mon = 1, Sun = 7 + final daysInMonth = + DateUtils.getDaysInMonth(selectedDate.year, selectedDate.month); + + // Previous month filler days + final prevMonth = DateTime(selectedDate.year, selectedDate.month - 1); + final daysInPrevMonth = + DateUtils.getDaysInMonth(prevMonth.year, prevMonth.month); + final prefixDays = List.generate( + startWeekday - 1, + (i) => DateTime(prevMonth.year, prevMonth.month, + daysInPrevMonth - (startWeekday - 2 - i))); + + // Current month days + final currentMonthDays = List.generate(daysInMonth, + (i) => DateTime(selectedDate.year, selectedDate.month, i + 1)); + + // Fillers from next month + final totalSlots = prefixDays.length + currentMonthDays.length; + final suffixDays = List.generate(42 - totalSlots, + (i) => DateTime(selectedDate.year, selectedDate.month + 1, i + 1)); + + final allDays = [...prefixDays, ...currentMonthDays, ...suffixDays]; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Text( + DateFormat.yMMMM().format(selectedDate), + style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] + .map((d) => Expanded( + child: Center( + child: Text(d, + style: TextStyle(fontWeight: FontWeight.w500)), + ), + )) + .toList(), + ), + const SizedBox(height: 8), + Expanded( + child: GridView.builder( + padding: const EdgeInsets.all(8), + physics: NeverScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 7, + crossAxisSpacing: 6, + mainAxisSpacing: 6, + ), + itemCount: allDays.length, + itemBuilder: (_, i) { + final day = allDays[i]; + final isCurrentMonth = day.month == selectedDate.month; + final isToday = DateUtils.isSameDay(day, DateTime.now()); + + return GestureDetector( + onTap: () { + final bool isFutureDate = day.isAfter(DateTime.now()); + + if (isCurrentMonth && !isFutureDate) { + widget.onDateSelected(day); + } + }, + child: Container( + decoration: BoxDecoration( + color: + (widget.selectedDate?.day == day.day && isCurrentMonth) + ? UiUtils.getColorScheme(context).primary + : (isToday + ? UiUtils.getColorScheme(context) + .primary + .withValues(alpha: 0.1) + : null), + borderRadius: BorderRadius.circular(8), + ), + child: Center( + child: Text( + '${day.day}', + style: TextStyle( + color: isCurrentMonth ? null : Colors.grey, + fontWeight: + isToday ? FontWeight.bold : FontWeight.normal, + ), + ), + ), + ), + ); + }, + ), + ), + ], + ); + } +} diff --git a/news-app/lib/ui/screens/filter/widgets/duration_filter_widget.dart b/news-app/lib/ui/screens/filter/widgets/duration_filter_widget.dart new file mode 100644 index 00000000..16e1917e --- /dev/null +++ b/news-app/lib/ui/screens/filter/widgets/duration_filter_widget.dart @@ -0,0 +1,128 @@ +import 'package:flutter/material.dart'; +import 'package:news/utils/strings.dart'; +import 'package:news/utils/uiUtils.dart'; + +abstract class DurationFilter { + Map toMap(); + String toText(BuildContext context); +} + +class LastDays extends DurationFilter { + final int daysCount; + LastDays({required this.daysCount}); + @override + toMap() { + return { + LAST_N_DAYS: daysCount.toString(), + }; + } + + @override + String toText(BuildContext context) { + if (daysCount == 1) { + return UiUtils.getTranslatedLabel(context, 'today'); + } + + return UiUtils.getTranslatedLabel(context, 'last') + ' $daysCount ' + UiUtils.getTranslatedLabel(context, 'days'); + } + + @override + bool operator ==(covariant DurationFilter other) { + if (identical(this, other)) return true; + if (other is LastDays) { + return other.daysCount == daysCount; + } + return false; + } + + @override + int get hashCode => daysCount.hashCode; +} + +class Year extends DurationFilter { + final int year; + Year({required this.year}); + @override + Map toMap() { + return { + YEAR: year.toString(), + }; + } + + @override + String toText(BuildContext context) { + return year.toString(); + } + + @override + bool operator ==(covariant DurationFilter other) { + if (identical(this, other)) return true; + if (other is Year) { + return other.year == year; + } + return false; + } + + @override + int get hashCode => year.hashCode; +} + +class DurationFilterWidget extends StatefulWidget { + final DurationFilter? selectedFilter; + final List filters; + final void Function(DurationFilter? filter)? onSelected; + const DurationFilterWidget({super.key, required this.filters, this.onSelected, this.selectedFilter}); + + @override + State createState() => _DurationFilterWidgetState(); +} + +class _DurationFilterWidgetState extends State { + late DurationFilter? selectedFilter = widget.selectedFilter; + + @override + void didUpdateWidget(DurationFilterWidget oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.selectedFilter != oldWidget.selectedFilter) { + selectedFilter = widget.selectedFilter; + } + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: widget.filters.map( + (DurationFilter e) { + return _buildDurationFilter(context, e); + }, + ).toList(), + ); + } + + Widget _buildDurationFilter(BuildContext context, DurationFilter filter) { + return GestureDetector( + onTap: () { + if (selectedFilter == filter) { + selectedFilter = null; + } else { + selectedFilter = filter; + } + widget.onSelected?.call(selectedFilter); + + setState(() {}); + }, + child: Container( + width: 100, + decoration: BoxDecoration( + color: selectedFilter == filter ? UiUtils.getColorScheme(context).secondaryContainer.withValues(alpha: 0.2) : UiUtils.getColorScheme(context).surface, + borderRadius: BorderRadius.circular(4), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(filter.toText(context)), + ), + ), + ); + } +} diff --git a/news-app/lib/ui/screens/introSlider.dart b/news-app/lib/ui/screens/introSlider.dart new file mode 100644 index 00000000..241dd86a --- /dev/null +++ b/news-app/lib/ui/screens/introSlider.dart @@ -0,0 +1,196 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/customTextBtn.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/settingCubit.dart'; + +class Slide { + final String? imageUrl; + final String? title; + final String? description; + + Slide({@required this.imageUrl, @required this.title, @required this.description}); +} + +class IntroSliderScreen extends StatefulWidget { + const IntroSliderScreen({super.key}); + + @override + GettingStartedScreenState createState() => GettingStartedScreenState(); +} + +class GettingStartedScreenState extends State with TickerProviderStateMixin { + PageController pageController = PageController(); + + int currentIndex = 0; + + late List slideList = [ + Slide(imageUrl: 'onboarding1', title: UiUtils.getTranslatedLabel(context, 'welTitle1'), description: UiUtils.getTranslatedLabel(context, 'welDes1')), + Slide(imageUrl: 'onboarding2', title: UiUtils.getTranslatedLabel(context, 'welTitle2'), description: UiUtils.getTranslatedLabel(context, 'welDes2')), + Slide(imageUrl: 'onboarding3', title: UiUtils.getTranslatedLabel(context, 'welTitle3'), description: UiUtils.getTranslatedLabel(context, 'welDes3')), + ]; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + bottomNavigationBar: nextButton(), + appBar: AppBar(automaticallyImplyLeading: false, centerTitle: false, actions: [setSkipButton()], title: SvgPictureWidget(assetName: "intro_icon", fit: BoxFit.cover)), + body: _buildIntroSlider()); + } + + gotoNext() { + context.read().changeShowIntroSlider(false); + Navigator.of(context).pushReplacementNamed(Routes.login); + } + + void onPageChanged(int index) { + setState(() { + currentIndex = index; //update current index for Next button + }); + } + + Widget _buildIntroSlider() { + return PageView.builder( + onPageChanged: onPageChanged, + controller: pageController, + itemCount: slideList.length, + itemBuilder: (context, index) { + final size = MediaQuery.of(context).size; + final imagePath = slideList[index].imageUrl; + + return SizedBox( + width: size.width * 0.99, + height: size.height * 0.75, + child: Transform( + alignment: Alignment.center, + transform: Matrix4.identity() + ..setEntry(3, 2, 0.001) + ..rotateX(0.1), + child: Stack( + children: [ + Positioned( + left: size.width * 0.3, + right: size.width * 0.3, + top: size.height * 0.71, + child: Container( + height: 7, + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: UiUtils.getColorScheme(context).primary.withOpacity(0.3), + blurRadius: 20, + spreadRadius: 5, + offset: Offset.zero, + ), + ], + shape: BoxShape.rectangle, + ), + ), + ), + Card( + color: secondaryColor, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(40.0)), + elevation: 0, + margin: const EdgeInsets.fromLTRB(24, 30, 24, 20), + child: Column( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(20), + child: imagePath != null ? SvgPictureWidget(assetName: imagePath, fit: BoxFit.contain) : SizedBox.shrink(), + ), + ), + titleText(index), + subtitleText(index), + progressIndicator(index), + const SizedBox(height: 25), + ], + ), + ), + ], + ), + ), + ); + }, + ); + } + + Widget setSkipButton() { + return (currentIndex != slideList.length - 1) + ? CustomTextButton( + onTap: () { + gotoNext(); + }, + color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), + text: UiUtils.getTranslatedLabel(context, 'skip')) + : const SizedBox.shrink(); + } + + Widget titleText(int index) { + return Container( + padding: const EdgeInsets.only(left: 20, right: 10), + margin: const EdgeInsets.only(bottom: 20.0, left: 10, right: 10), + alignment: Alignment.center, + child: CustomTextLabel(text: slideList[index].title!, textStyle: Theme.of(context).textTheme.headlineSmall?.copyWith(color: darkSecondaryColor, fontWeight: FontWeight.bold, letterSpacing: 0.5)), + ); + } + + Widget subtitleText(int index) { + return Container( + padding: const EdgeInsets.only(left: 10), + margin: const EdgeInsets.only(bottom: 55.0, left: 10, right: 10), + child: CustomTextLabel( + text: slideList[index].description!, + textAlign: TextAlign.left, + textStyle: Theme.of(context).textTheme.titleMedium?.copyWith(color: darkSecondaryColor.withOpacity(0.5), fontWeight: FontWeight.normal, letterSpacing: 0.5), + maxLines: 3, + overflow: TextOverflow.ellipsis)); + } + + Widget progressIndicator(int index) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate( + slideList.length, + (index) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 5), + child: InkWell( + onTap: () => pageController.animateToPage(index, duration: const Duration(seconds: 1), curve: Curves.fastLinearToSlowEaseIn), + child: (currentIndex == index) + ? ClipRRect(borderRadius: BorderRadius.circular(10.0), child: Container(height: 10, width: 40.0, color: darkSecondaryColor)) + : CircleAvatar(radius: 5, backgroundColor: darkSecondaryColor.withOpacity(0.6)))))); + } + + Widget nextButton() { + return MaterialButton( + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + padding: EdgeInsets.symmetric(horizontal: MediaQuery.of(context).size.width * 0.28, vertical: 20), + onPressed: () { + if (currentIndex == slideList.length - 1 && currentIndex != 0) { + gotoNext(); + } else { + currentIndex += 1; + pageController.animateToPage(currentIndex, duration: const Duration(seconds: 1), curve: Curves.fastLinearToSlowEaseIn); + } + }, + child: Container( + height: 50, + width: 162, + alignment: Alignment.center, + decoration: BoxDecoration(color: Theme.of(context).primaryColor, borderRadius: BorderRadius.circular(14)), + child: CustomTextLabel( + text: (currentIndex == (slideList.length - 1)) ? 'loginBtn' : 'nxt', + textStyle: Theme.of(context).textTheme.bodyLarge!.copyWith(color: secondaryColor, fontWeight: FontWeight.bold), + textAlign: TextAlign.center))); + } +} diff --git a/news-app/lib/ui/screens/languageList.dart b/news-app/lib/ui/screens/languageList.dart new file mode 100644 index 00000000..ba073036 --- /dev/null +++ b/news-app/lib/ui/screens/languageList.dart @@ -0,0 +1,255 @@ +import 'dart:convert'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/Auth/registerTokenCubit.dart'; +import 'package:news/cubits/generalNewsCubit.dart'; +import 'package:news/cubits/rssFeedCubit.dart'; +import 'package:news/cubits/settingCubit.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:intl/intl.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; +import 'package:news/cubits/breakingNewsCubit.dart'; +import 'package:news/data/repositories/Settings/settingsLocalDataRepository.dart'; +import 'package:news/ui/screens/dashBoard/dashBoardScreen.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/customAppBar.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/errorContainerWidget.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import 'package:news/utils/ErrorMessageKeys.dart'; +import 'package:news/utils/internetConnectivity.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/cubits/languageCubit.dart'; +import 'package:news/cubits/Bookmark/bookmarkCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/categoryCubit.dart'; +import 'package:news/cubits/featureSectionCubit.dart'; +import 'package:news/cubits/languageJsonCubit.dart'; +import 'package:news/cubits/liveStreamCubit.dart'; +import 'package:news/cubits/otherPagesCubit.dart'; +import 'package:news/cubits/videosCubit.dart'; +import 'package:news/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart'; + +class LanguageList extends StatefulWidget { + final String? from; + const LanguageList({super.key, this.from}); + + @override + LanguageListState createState() => LanguageListState(); + + static Route route(RouteSettings routeSettings) { + final arguments = routeSettings.arguments as Map; + return CupertinoPageRoute(builder: (_) => LanguageList(from: arguments['from'])); + } +} + +class LanguageListState extends State { + String? selLanCode, selLanId; + late String latitude, longitude; + int? selLanRTL; + bool isNetworkAvail = true; + + @override + void initState() { + isNetworkAvailable(); + getLanguageData(); + setLatitudeLongitude(); + super.initState(); + } + + Future getLanguageData() async { + Future.delayed(Duration.zero, () { + context.read().getLanguage(); + }); + } + + void setLatitudeLongitude() { + latitude = SettingsLocalDataRepository().getLocationCityValues().first; + longitude = SettingsLocalDataRepository().getLocationCityValues().last; + } + + Widget getLangList() { + return BlocBuilder(builder: (context, stateLocale) { + return BlocBuilder(builder: (context, state) { + if (state is LanguageFetchSuccess) { + return ListView.separated( + padding: const EdgeInsets.only(bottom: 20, top: 10), + physics: const AlwaysScrollableScrollPhysics(), + itemBuilder: ((context, index) { + return Padding( + padding: const EdgeInsets.fromLTRB(20.0, 5.0, 20.0, 5.0), + child: Container( + height: MediaQuery.of(context).size.height * 0.08, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15.0), + color: (selLanCode ?? stateLocale.languageCode) == state.language[index].code! ? UiUtils.getColorScheme(context).primaryContainer : null), + child: InkWell( + onTap: () { + setState(() { + Intl.defaultLocale = state.language[index].code; + selLanCode = state.language[index].code!; + selLanId = state.language[index].id!; + selLanRTL = state.language[index].isRTL!; + }); + }, + child: Container( + margin: const EdgeInsets.only(left: 15, right: 15), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + CustomNetworkImage(networkImageUrl: state.language[index].image!, isVideo: false, height: 40, fit: BoxFit.fill, width: 40), + SizedBox(width: MediaQuery.of(context).size.width * 0.05), + CustomTextLabel( + text: state.language[index].languageDisplayName ?? state.language[index].language!, + textStyle: Theme.of(this.context).textTheme.titleLarge?.copyWith( + color: ((selLanCode ?? (stateLocale).languageCode) == state.language[index].code!) + ? UiUtils.getColorScheme(context).secondary + : UiUtils.getColorScheme(context).primaryContainer)), + ], + ), + ), + ), + )); + }), + separatorBuilder: (context, index) { + return const SizedBox(height: 1.0); + }, + itemCount: state.language.length); + } + if (state is LanguageFetchFailure) { + return Padding( + padding: const EdgeInsets.only(left: 30.0, right: 30.0), + child: ErrorContainerWidget( + errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage, onRetry: getLanguageData), + ); + } + return const Padding(padding: EdgeInsets.only(bottom: 10.0, left: 30.0, right: 30.0), child: SizedBox.shrink()); + }); + }); + } + + saveBtn() { + return BlocConsumer( + bloc: context.read(), + listener: (context, state) { + if (state is LanguageJsonFetchSuccess) { + final langId = selLanId ?? context.read().state.id; + + UiUtils.setDynamicStringValue( + context.read().state.languageCode, + jsonEncode(state.languageJson), + ).then((_) { + // Update languageId + homeScreenKey?.currentState?.languageId = langId; + + // Fetch general data + context.read().getOtherPage(langId: langId); + context.read().getSection(langId: langId, latitude: latitude, longitude: longitude); + context.read().getLiveStream(langId: langId); + context.read().getGeneralNews(langId: langId, latitude: latitude, longitude: longitude); + context.read().getVideo(langId: langId, latitude: latitude, longitude: longitude); + context.read().getCategory(langId: langId); + + // Conditional based on config + final config = context.read(); + if (config.getWeatherMode() == "1") { + homeScreenKey?.currentState?.getWeatherData(); + } + if (config.getRSSFeedMode() == "1") { + context.read().getRSSFeed(langId: langId); + } + if (config.getBreakingNewsMode() == "1") { + context.read().getBreakingNews(langId: langId); + } + + // If user is logged in + final auth = context.read(); + if (auth.getUserId() != "0") { + context.read().getLike(langId: langId); + context.read().getBookmark(langId: langId); + updateUserLanguageWithFCMid(); + } + }); + + if (widget.from != null && widget.from == "firstLogin" && context.read().getCatList().isNotEmpty) { + //check if it is firstLogin - then goto Home or else pop + Navigator.of(context).pushNamedAndRemoveUntil(Routes.managePref, (route) => false, arguments: {"from": 2}); + } else if (widget.from == "firstLogin") { + Navigator.of(context).pushReplacementNamed(Routes.home, arguments: false); + } else { + Navigator.pop(context); + } + } + }, + builder: (context, state) { + if (state is LanguageJsonFetchSuccess) { + return InkWell( + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + child: Container( + height: 45.0, + margin: const EdgeInsetsDirectional.all(20), + width: MediaQuery.of(context).size.width * 0.9, + alignment: Alignment.center, + decoration: BoxDecoration(color: Theme.of(context).primaryColor, borderRadius: BorderRadius.circular(15.0)), + child: CustomTextLabel(text: 'saveLbl', textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: backgroundColor, fontWeight: FontWeight.bold))), + onTap: () { + setState(() { + if (selLanCode != null && context.read().state.languageCode != selLanCode) { + context.read().changeLanguage(selLanCode!, selLanId!, selLanRTL!); + context.read().getLanguageJson(lanCode: selLanCode!); + } else { + if (widget.from != null && widget.from == "firstLogin" && context.read().getCatList().isNotEmpty) { + Navigator.of(context).pushNamedAndRemoveUntil(Routes.managePref, (route) => false, arguments: {"from": 2}); + } else if (widget.from == "firstLogin") { + Navigator.of(context).pushReplacementNamed(Routes.home, arguments: false); + } else { + Navigator.pop(context); + } + } + }); + }); + } + return const SizedBox.shrink(); + }); + } + + void updateUserLanguageWithFCMid() async { + String currentFCMId = context.read().getSettings().token.trim(); + + if (currentFCMId.isEmpty) { + final newToken = await FirebaseMessaging.instance.getToken(); + if (newToken != null) { + currentFCMId = newToken; + context.read().registerToken(fcmId: currentFCMId, context: context); + } + } else { + context.read().registerToken(fcmId: currentFCMId, context: context); + } + + context.read().changeFcmToken(currentFCMId); + } + + isNetworkAvailable() async { + if (await InternetConnectivity.isNetworkAvailable()) { + setState(() { + isNetworkAvail = true; + }); + } else { + setState(() { + isNetworkAvail = false; + }); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: CustomAppBar(height: 45, isBackBtn: true, label: 'chooseLanLbl', horizontalPad: 15, isConvertText: true), + bottomNavigationBar: (isNetworkAvail) ? saveBtn() : const SizedBox.shrink(), + body: getLangList()); + } +} diff --git a/news-app/lib/ui/screens/maintenanceScreen.dart b/news-app/lib/ui/screens/maintenanceScreen.dart new file mode 100644 index 00000000..85986002 --- /dev/null +++ b/news-app/lib/ui/screens/maintenanceScreen.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; + +class MaintenanceScreen extends StatefulWidget { + const MaintenanceScreen({super.key}); + + @override + MaintenanceScreenState createState() => MaintenanceScreenState(); +} + +class MaintenanceScreenState extends State { + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Padding( + padding: const EdgeInsetsDirectional.only(start: 15.0, end: 15.0, top: 10.0, bottom: 10.0), + child: BlocBuilder( + builder: (context, state) { + if (state is AppConfigurationFetchSuccess && state.appConfiguration.maintenanceMode == "1") { + return Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + SvgPictureWidget(assetName: "maintenance"), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: CustomTextLabel( + textStyle: TextStyle(color: Theme.of(context).colorScheme.primaryContainer, fontSize: 18, fontWeight: FontWeight.w600), + textAlign: TextAlign.center, + text: 'maintenanceMessageLbl')) + ]); + } else if (state is AppConfigurationFetchSuccess && state.appConfiguration.maintenanceMode == "0") { + Navigator.of(context).pop(); + } + //default/Processing state + return Padding(padding: const EdgeInsets.only(bottom: 10.0, left: 10.0, right: 10.0), child: CircularProgressIndicator()); + }, + ), + )); + } +} diff --git a/news-app/lib/ui/screens/splashScreen.dart b/news-app/lib/ui/screens/splashScreen.dart new file mode 100644 index 00000000..7aee602d --- /dev/null +++ b/news-app/lib/ui/screens/splashScreen.dart @@ -0,0 +1,134 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hive/hive.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/languageCubit.dart'; +import 'package:news/cubits/languageJsonCubit.dart'; +import 'package:news/cubits/settingCubit.dart'; +import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart'; +import 'package:news/ui/styles/appTheme.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/errorContainerWidget.dart'; +import 'package:news/utils/ErrorMessageKeys.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; +import 'package:news/utils/hiveBoxKeys.dart'; + +class Splash extends StatefulWidget { + const Splash({super.key}); + + @override + SplashState createState() => SplashState(); +} + +class SplashState extends State with TickerProviderStateMixin { + @override + void initState() { + super.initState(); + fetchAppConfigurations(); + } + + fetchAppConfigurations() { + context.read().fetchAppConfiguration(); + } + + fetchLanguages({required AppConfigurationFetchSuccess state}) async { + String currentLanguage = Hive.box(settingsBoxKey).get(currentLanguageCodeKey) ?? ""; + if (currentLanguage == "" && state.appConfiguration.defaultLangDataModel != null) { + context + .read() + .changeLanguage(state.appConfiguration.defaultLangDataModel!.code!, state.appConfiguration.defaultLangDataModel!.id!, state.appConfiguration.defaultLangDataModel!.isRTL!); + context.read().fetchCurrentLanguageAndLabels(state.appConfiguration.defaultLangDataModel!.code!); + } else { + context.read().fetchCurrentLanguageAndLabels(currentLanguage); + } + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (Platform.isAndroid) UiUtils.setUIOverlayStyle(appTheme: AppTheme.Dark); //set UiOverlayStyle to dark - due to fixed splashScreen backgroundColor // according to selected theme + return Scaffold(backgroundColor: Theme.of(context).secondaryHeaderColor, body: buildScale()); + } + + Future navigationPage() async { + Future.delayed(const Duration(seconds: 4), () async { + final currentSettings = context.read().state.settingsModel; + if (context.read().getMaintenanceMode() == "1") { + //app is in maintenance mode - no function should be performed + Navigator.of(context).pushReplacementNamed(Routes.maintenance); + } else if (currentSettings!.showIntroSlider) { + Navigator.of(context).pushReplacementNamed(Routes.introSlider); + } else { + Navigator.of(context).pushReplacementNamed(Routes.home, arguments: false); + } + }); + } + + Widget buildScale() { + return BlocConsumer( + bloc: context.read(), + listener: (context, state) { + if (state is AppConfigurationFetchSuccess) { + fetchLanguages(state: state); + } + }, + builder: (context, state) { + return BlocConsumer( + bloc: context.read(), + listener: (context, state) { + if (state is LanguageJsonFetchSuccess) { + navigationPage(); + context.read().getLanguage(); //Load languages for dynamic link + } + }, + builder: (context, langState) { + if (state is AppConfigurationFetchFailure) { + return ErrorContainerWidget( + errorMsg: (state.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : state.errorMessage, + onRetry: () { + fetchAppConfigurations(); + }, + ); + } else if (langState is LanguageJsonFetchFailure) { + return ErrorContainerWidget( + errorMsg: (langState.errorMessage.contains(ErrorMessageKeys.noInternet)) ? UiUtils.getTranslatedLabel(context, 'internetmsg') : langState.errorMessage, + onRetry: () { + fetchLanguages(state: state as AppConfigurationFetchSuccess); + }, + ); + } else { + return Container( + width: double.maxFinite, + decoration: BoxDecoration(color: primaryColor), + child: Column(mainAxisSize: MainAxisSize.min, children: [const SizedBox(height: 220), splashLogoIcon(), newsTextIcon(), subTitle(), const Spacer(), bottomText()]), + ); + } + }); + }); + } + + Widget splashLogoIcon() { + return Center(child: SvgPictureWidget(assetName: "splash_icon", height: 110.0, fit: BoxFit.fill)); + } + + Widget newsTextIcon() { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 20.0), + child: Center(child: SvgPictureWidget(assetName: "caribe_blanco", height: 58.0, width: 300, fit: BoxFit.fill)), + ); + } + + Widget subTitle() => + CustomTextLabel(text: 'fastTrendNewsLbl', textAlign: TextAlign.center, textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(color: backgroundColor, fontWeight: FontWeight.bold)); + + Widget bottomText() => Container(margin: const EdgeInsetsDirectional.only(bottom: 20), child: SvgPictureWidget(assetName: "wrteam_logo", height: 40.0, fit: BoxFit.fill)); +} diff --git a/news-app/lib/ui/styles/appTheme.dart b/news-app/lib/ui/styles/appTheme.dart new file mode 100644 index 00000000..68ad13b7 --- /dev/null +++ b/news-app/lib/ui/styles/appTheme.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'colors.dart'; + +enum AppTheme { Light, Dark } + +final appThemeData = { + AppTheme.Light: ThemeData( + useMaterial3: false, + fontFamily: 'Roboto', + brightness: Brightness.light, + primaryColor: primaryColor, + canvasColor: backgroundColor, + textTheme: const TextTheme().apply(bodyColor: darkSecondaryColor, displayColor: darkSecondaryColor), + appBarTheme: AppBarTheme( + elevation: 0.0, + backgroundColor: backgroundColor, + systemOverlayStyle: SystemUiOverlayStyle(statusBarBrightness: Brightness.light, statusBarIconBrightness: Brightness.dark, statusBarColor: backgroundColor.withOpacity(0.8))), + iconTheme: const IconThemeData(color: darkSecondaryColor), + colorScheme: ColorScheme.fromSeed( + seedColor: primaryColor, + brightness: Brightness.light, + surface: secondaryColor, + secondary: secondaryColor, + onPrimary: darkBackgroundColor, + secondaryContainer: darkSecondaryColor, + outline: borderColor, + primaryContainer: darkSecondaryColor), + dialogBackgroundColor: backgroundColor //for datePicker + ), + AppTheme.Dark: ThemeData( + useMaterial3: false, + fontFamily: 'Roboto', + brightness: Brightness.dark, + primaryColor: primaryColor, + canvasColor: darkSecondaryColor, + appBarTheme: AppBarTheme( + elevation: 0.0, + backgroundColor: darkBackgroundColor, + systemOverlayStyle: SystemUiOverlayStyle( + statusBarBrightness: Brightness.dark, + statusBarIconBrightness: Brightness.light, + statusBarColor: darkSecondaryColor.withOpacity(0.8), + )), + textTheme: const TextTheme().apply(bodyColor: secondaryColor, displayColor: secondaryColor), + iconTheme: const IconThemeData(color: secondaryColor), + colorScheme: ColorScheme.fromSeed( + seedColor: primaryColor, + onPrimary: secondaryColor, + surface: darkBackgroundColor, + brightness: Brightness.dark, + secondary: darkSecondaryColor, + secondaryContainer: primaryColor, + outline: backgroundColor, + primaryContainer: secondaryColor //for datePicker + ), + dialogBackgroundColor: darkBackgroundColor), +}; diff --git a/news-app/lib/ui/styles/colors.dart b/news-app/lib/ui/styles/colors.dart new file mode 100644 index 00000000..1e4610b3 --- /dev/null +++ b/news-app/lib/ui/styles/colors.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; + +Color primaryColor = const Color(0xff008DA8); +const Color secondaryColor = Color(0xffffffff); +const Color backgroundColor = Color(0xffF1F6F9); +const Color borderColor = Color(0xff6B6B6B); +const Color warningColor = Colors.amberAccent; +const Color iconColor = Color(0xffBE151E); + +const Color darkSecondaryColor = Color(0xff1B2D51); +const Color darkBackgroundColor = Color(0xff1F345E); +const Color darkIconColor = Color(0xffFF787F); + +const Color dividerColor = Color(0x1F000000); + +const Color authorRequestColor = darkSecondaryColor; +const Color authorReviewColor = Color(0xff017A80); +const Color authorApprovedColor = Color(0xff060F72); diff --git a/news-app/lib/ui/widgets/NewsItem.dart b/news-app/lib/ui/widgets/NewsItem.dart new file mode 100644 index 00000000..468275c8 --- /dev/null +++ b/news-app/lib/ui/widgets/NewsItem.dart @@ -0,0 +1,256 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/newsCard.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/utils/internetConnectivity.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/cubits/Bookmark/UpdateBookmarkCubit.dart'; +import 'package:news/cubits/Bookmark/bookmarkCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; +import 'package:news/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart'; +import 'package:news/cubits/LikeAndDislikeNews/updateLikeAndDislikeCubit.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/Bookmark/bookmarkRepository.dart'; +import 'package:news/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsRepository.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; + +class NewsItem extends StatefulWidget { + final NewsModel model; + final int index; + final List newslist; + final bool fromShowMore; + + const NewsItem({super.key, required this.model, required this.index, required this.newslist, required this.fromShowMore}); + + @override + NewsItemState createState() => NewsItemState(); +} + +class NewsItemState extends State { + late BannerAd bannerAd; + + @override + void initState() { + super.initState(); + if (context.read().getInAppAdsMode() == "1" && + (context.read().getAdsType() != "unity" || context.read().getIOSAdsType() != "unity")) bannerAd = UiUtils.createBannerAd(context: context); + } + + Widget setButton({required Widget childWidget}) { + return Container(padding: const EdgeInsets.all(3), decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: UiUtils.getColorScheme(context).secondary), child: childWidget); + } + + Widget setNewsImage() { + return ClipRRect( + borderRadius: BorderRadius.circular(10), + child: ShaderMask( + shaderCallback: (rect) => LinearGradient(begin: Alignment.center, end: Alignment.bottomCenter, colors: [Colors.transparent, darkSecondaryColor.withOpacity(0.9)]).createShader(rect), + blendMode: BlendMode.darken, + child: Container( + color: primaryColor.withAlpha(5), + width: double.maxFinite, + height: MediaQuery.of(context).size.height / 3.3, + child: CustomNetworkImage(networkImageUrl: widget.model.image!, width: double.infinity, height: MediaQuery.of(context).size.height / 4.2, fit: BoxFit.cover, isVideo: false)))); + } + + Widget setTagsList({required List tagList, required List tagId}) { + return Positioned.directional( + textDirection: Directionality.of(context), + bottom: 80.0, + start: 7.0, + child: widget.model.tagName! != "" + ? SizedBox( + height: 16.0, + child: ListView.builder( + physics: const AlwaysScrollableScrollPhysics(), + scrollDirection: Axis.horizontal, + shrinkWrap: true, + itemCount: tagList.length, + itemBuilder: (context, index) { + return Padding( + padding: EdgeInsetsDirectional.only(start: index == 0 ? 0 : 5.5), + child: InkWell( + child: Container( + height: 20.0, + width: 65, + alignment: Alignment.center, + padding: const EdgeInsetsDirectional.only(start: 3.0, end: 3.0, top: 1.0, bottom: 1.0), + decoration: BoxDecoration( + borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(10.0), topRight: Radius.circular(10.0)), + color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.85)), + child: CustomTextLabel( + text: tagList[index], + textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: UiUtils.getColorScheme(context).secondary, fontSize: 8.5), + overflow: TextOverflow.ellipsis, + softWrap: true)), + onTap: () async { + Navigator.of(context).pushNamed(Routes.tagScreen, arguments: {"tagId": tagId[index], "tagName": tagList[index]}); + }, + ), + ); + })) + : const SizedBox.shrink()); + } + + Widget setTitleAndDate() { + return Positioned.directional( + textDirection: Directionality.of(context), + bottom: 20, + start: 10, + end: 10, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + alignment: Alignment.bottomLeft, + padding: const EdgeInsetsDirectional.only(top: 4.0, start: 5.0, end: 5.0), + child: CustomTextLabel( + text: widget.model.title!, textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: secondaryColor), maxLines: 2, softWrap: true, overflow: TextOverflow.ellipsis)), + Padding( + padding: const EdgeInsetsDirectional.only(top: 4.0, start: 5.0, end: 5.0), + child: CustomTextLabel( + text: UiUtils.convertToAgo(context, DateTime.parse(widget.model.publishDate ?? widget.model.date!), 0)!, + textStyle: Theme.of(context).textTheme.bodySmall!.copyWith(color: secondaryColor.withOpacity(0.6)))), + ], + ), + ); + } + + Widget shareButton() { + return Padding( + padding: const EdgeInsetsDirectional.only(start: 13.0), + child: InkWell( + child: setButton(childWidget: const Icon(Icons.share_rounded, size: 20)), + onTap: () async { + if (await InternetConnectivity.isNetworkAvailable()) { + UiUtils.shareNews(context: context, slug: widget.model.slug!, title: widget.model.title!, isVideo: false, videoId: "", isBreakingNews: false, isNews: true); + } else { + showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context); + } + }, + ), + ); + } + + Widget bookmarkButton() { + return BlocProvider( + create: (context) => UpdateBookmarkStatusCubit(BookmarkRepository()), + child: BlocBuilder( + bloc: context.read(), + builder: (context, bookmarkState) { + bool isBookmark = context.read().isNewsBookmark(widget.model.id!); + return BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is UpdateBookmarkStatusSuccess) { + (state.wasBookmarkNewsProcess) ? context.read().addBookmarkNews(state.news) : context.read().removeBookmarkNews(state.news); + } + }), + builder: (context, state) { + return InkWell( + onTap: () { + if (context.read().getUserId() != "0") { + if (state is UpdateBookmarkStatusInProgress) return; + context.read().setBookmarkNews(news: widget.model, status: (isBookmark) ? "0" : "1"); + } else { + UiUtils.loginRequired(context); + } + }, + child: setButton( + childWidget: (state is UpdateBookmarkStatusInProgress) + ? SizedBox(height: 20, width: 20, child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)) + : ((isBookmark) ? const Icon(Icons.bookmark_added_rounded, size: 20) : const Icon(Icons.bookmark_add_outlined, size: 20)))); + }); + }), + ); + } + + Widget likeButton() { + bool isLike = context.read().isNewsLikeAndDisLike(widget.model.id!); + + return BlocProvider( + create: (context) => UpdateLikeAndDisLikeStatusCubit(LikeAndDisLikeRepository()), + child: BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is LikeAndDisLikeFetchSuccess) { + isLike = context.read().isNewsLikeAndDisLike(widget.model.id!); + } + }), + builder: (context, likeAndDislikeState) { + return BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is UpdateLikeAndDisLikeStatusSuccess) { + context.read().getLike(langId: context.read().state.id); + widget.model.totalLikes = (!isLike) + ? (int.parse(widget.model.totalLikes.toString()) + 1).toString() + : (widget.model.totalLikes!.isNotEmpty) + ? (int.parse(widget.model.totalLikes.toString()) - 1).toString() + : "0"; + } + }), + builder: (context, state) { + return InkWell( + splashColor: Colors.transparent, + onTap: () { + if (context.read().getUserId() != "0") { + if (state is UpdateLikeAndDisLikeStatusInProgress) { + return; + } + context.read().setLikeAndDisLikeNews(news: widget.model, status: (isLike) ? "0" : "1"); + } else { + UiUtils.loginRequired(context); + } + }, + child: setButton( + childWidget: (state is UpdateLikeAndDisLikeStatusInProgress) + ? SizedBox(height: 20, width: 20, child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)) + : ((isLike) ? const Icon(Icons.thumb_up_alt, size: 20) : const Icon(Icons.thumb_up_off_alt, size: 20)))); + }); + })); + } + + Widget setShareBookmarkLikeOptions() { + return Positioned.directional( + end: 5, + bottom: 5, + textDirection: Directionality.of(context), + child: Row( + children: [shareButton(), SizedBox(width: MediaQuery.of(context).size.width / 98.0), bookmarkButton(), SizedBox(width: MediaQuery.of(context).size.width / 98.0), likeButton()], + ), + ); + } + + Widget newsData() { + return Builder(builder: (context) { + return Padding( + padding: EdgeInsetsDirectional.only(top: widget.index == 0 ? 0 : 15.0, start: 15, end: 15), + child: Column(children: [ + if (context.read().getInAppAdsMode() == "1" && + (context.read().getAdsType() != "unity" || context.read().getIOSAdsType() != "unity")) + nativeAdsShow(context: context, index: widget.index), + NewsCard( + newsDetail: widget.model, + showTags: true, + onTap: () { + List newsList = []; + newsList.addAll(widget.newslist); + newsList.removeAt(widget.index); + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"model": widget.model, "newsList": newsList, "isFromBreak": false, "fromShowMore": widget.fromShowMore}); + }) + ])); + }); + } + + @override + Widget build(BuildContext context) { + return newsData(); + } +} diff --git a/news-app/lib/ui/widgets/SnackBarWidget.dart b/news-app/lib/ui/widgets/SnackBarWidget.dart new file mode 100644 index 00000000..23a99dbb --- /dev/null +++ b/news-app/lib/ui/widgets/SnackBarWidget.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/uiUtils.dart'; + +showSnackBar(String msg, BuildContext context, {int? durationInMiliSeconds}) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: CustomTextLabel(text: msg, textAlign: TextAlign.center, textStyle: TextStyle(color: Theme.of(context).colorScheme.surface)), + showCloseIcon: false, + duration: Duration(milliseconds: durationInMiliSeconds ?? 1500), //bydefault 4000 ms + backgroundColor: UiUtils.getColorScheme(context).primaryContainer, + elevation: 1.0, + ), + ); +} diff --git a/news-app/lib/ui/widgets/adSpaces.dart b/news-app/lib/ui/widgets/adSpaces.dart new file mode 100644 index 00000000..5fadf959 --- /dev/null +++ b/news-app/lib/ui/widgets/adSpaces.dart @@ -0,0 +1,43 @@ +//sponsored Ads + +import 'package:flutter/material.dart'; +import 'package:news/data/models/adSpaceModel.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class AdSpaces extends StatelessWidget { + AdSpaceModel adsModel; + AdSpaces({super.key, required this.adsModel}); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only(top: 10), + child: InkWell( + splashColor: Colors.transparent, + onTap: () async { + if (await canLaunchUrl(Uri.parse(adsModel.adUrl!))) { + //To open link in other apps or outside of Current App + //Add -> , mode: LaunchMode.externalApplication + await launchUrl(Uri.parse(adsModel.adUrl!)); + } + }, + child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ + Container( + alignment: AlignmentDirectional.centerEnd, + padding: const EdgeInsetsDirectional.only(end: 5), + child: CustomTextLabel( + text: 'sponsoredLbl', + textStyle: Theme.of(context).textTheme.bodySmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.6), fontWeight: FontWeight.w800), + ), + ), + Padding( + padding: const EdgeInsets.only(top: 2), + child: CustomNetworkImage(networkImageUrl: adsModel.adImage!, isVideo: false, width: MediaQuery.of(context).size.width, fit: BoxFit.values.first), + ), + ])), + ); + } +} diff --git a/news-app/lib/ui/widgets/breakingNewsItem.dart b/news-app/lib/ui/widgets/breakingNewsItem.dart new file mode 100644 index 00000000..fbf22458 --- /dev/null +++ b/news-app/lib/ui/widgets/breakingNewsItem.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; +import 'package:news/data/models/BreakingNewsModel.dart'; + +class BreakNewsItem extends StatefulWidget { + final BreakingNewsModel model; + final int index; + final List breakNewsList; + + const BreakNewsItem({super.key, required this.model, required this.index, required this.breakNewsList}); + + @override + BreakNewsItemState createState() => BreakNewsItemState(); +} + +class BreakNewsItemState extends State { + late BannerAd _bannerAd; + @override + void initState() { + if (context.read().getInAppAdsMode() == "1") createBannerAd(); + super.initState(); + } + + Widget newsData() { + return Builder(builder: (context) { + return Padding( + padding: EdgeInsetsDirectional.only(top: widget.index == 0 ? 0 : 15.0), + child: Column(children: [ + if (context.read().getInAppAdsMode() == "1") nativeAdsShow(), + InkWell( + child: Column( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(10.0), + child: CustomNetworkImage(networkImageUrl: widget.model.image!, width: double.infinity, height: MediaQuery.of(context).size.height / 4.2, fit: BoxFit.cover, isVideo: false)), + Container( + alignment: Alignment.bottomLeft, + padding: const EdgeInsetsDirectional.only(top: 4.0, start: 5.0, end: 5.0), + child: CustomTextLabel( + text: widget.model.title!, + textStyle: Theme.of(context).textTheme.titleSmall?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.9)), + maxLines: 2, + softWrap: true, + overflow: TextOverflow.ellipsis)) + ], + ), + onTap: () { + List newsList = []; + newsList.addAll(widget.breakNewsList); + newsList.removeAt(widget.index); + Navigator.of(context).pushNamed(Routes.newsDetails, arguments: {"breakModel": widget.model, "breakNewsList": newsList, "isFromBreak": true, "fromShowMore": false}); + }, + ), + ])); + }); + } + + BannerAd createBannerAd() { + if (context.read().bannerId() != "") { + _bannerAd = BannerAd( + adUnitId: context.read().bannerId()!, + request: const AdRequest(), + size: AdSize.mediumRectangle, + listener: BannerAdListener( + onAdLoaded: (_) {}, + onAdFailedToLoad: (ad, err) { + ad.dispose(); + }, + onAdOpened: (Ad ad) => debugPrint('Native ad opened.'), + // Called when an ad opens an overlay that covers the screen. + onAdClosed: (Ad ad) => debugPrint('Native ad closed.'), + // Called when an ad removes an overlay that covers the screen. + onAdImpression: (Ad ad) => debugPrint('Native ad impression.')), + ); + } + return _bannerAd; + } + + Widget bannerAdsShow() { + return AdWidget(key: UniqueKey(), ad: createBannerAd()..load()); + } + + Widget nativeAdsShow() { + if (context.read().getInAppAdsMode() == "1" && + context.read().checkAdsType() != null && + context.read().getAdsType() != "unity" && + widget.index != 0 && + widget.index % nativeAdsIndex == 0) { + return Padding( + padding: const EdgeInsets.only(bottom: 15.0), + child: Container( + padding: const EdgeInsets.all(7.0), + height: 300, + width: double.infinity, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.5), + borderRadius: BorderRadius.circular(10.0), + ), + child: context.read().checkAdsType() == "google" && (context.read().bannerId() != "") ? bannerAdsShow() : SizedBox.shrink())); + } else { + return const SizedBox.shrink(); + } + } + + @override + Widget build(BuildContext context) { + return newsData(); + } +} diff --git a/news-app/lib/ui/widgets/breakingVideoItem.dart b/news-app/lib/ui/widgets/breakingVideoItem.dart new file mode 100644 index 00000000..602f9060 --- /dev/null +++ b/news-app/lib/ui/widgets/breakingVideoItem.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:youtube_player_flutter/youtube_player_flutter.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/ui/widgets/networkImage.dart'; + +class BreakVideoItem extends StatefulWidget { + final BreakingNewsModel model; + + const BreakVideoItem({super.key, required this.model}); + + @override + BreakVideoItemState createState() => BreakVideoItemState(); +} + +class BreakVideoItemState extends State { + Widget videoData(BreakingNewsModel video) { + return Padding( + padding: const EdgeInsets.only(top: 15.0), + child: Column( + children: [ + ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(10.0)), + child: GestureDetector( + onTap: () { + Navigator.of(context).pushNamed(Routes.newsVideo, arguments: {"from": 3, "breakModel": video}); + }, + child: Stack( + alignment: Alignment.center, + children: [ + (video.contentType == 'video_youtube') + ? CustomNetworkImage( + networkImageUrl: 'https://img.youtube.com/vi/${YoutubePlayer.convertUrlToId(video.contentValue!)!}/0.jpg', + fit: BoxFit.cover, + width: double.maxFinite, + height: 220, + isVideo: true) + : CustomNetworkImage(networkImageUrl: video.image!, fit: BoxFit.cover, width: double.maxFinite, height: 220, isVideo: true), + const CircleAvatar(radius: 30, backgroundColor: Colors.black45, child: Icon(Icons.play_arrow, size: 40, color: Colors.white)) + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 5, right: 5), + child: + Align(alignment: Alignment.centerLeft, child: CustomTextLabel(text: video.title!, textStyle: Theme.of(context).textTheme.titleSmall, maxLines: 2, overflow: TextOverflow.ellipsis))), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return videoData(widget.model); + } +} diff --git a/news-app/lib/ui/widgets/customAppBar.dart b/news-app/lib/ui/widgets/customAppBar.dart new file mode 100644 index 00000000..60b04815 --- /dev/null +++ b/news-app/lib/ui/widgets/customAppBar.dart @@ -0,0 +1,45 @@ +import 'dart:core'; +import 'package:flutter/material.dart'; +import 'package:news/ui/widgets/customBackBtn.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/uiUtils.dart'; + +class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { + final double height; + final double? horizontalPad; + final bool isBackBtn, isConvertText; + final String label; + final List? actionWidget; + + const CustomAppBar({Key? key, required this.height, required this.isBackBtn, required this.label, required this.isConvertText, this.horizontalPad, this.actionWidget}) : super(key: key); + + @override + Widget build(BuildContext context) { + return PreferredSize( + preferredSize: Size(double.infinity, height), + child: UiUtils.applyBoxShadow( + context: context, + child: AppBar( + leading: (isBackBtn) ? CustomBackButton(horizontalPadding: horizontalPad ?? 15) : null, + actions: actionWidget, + titleSpacing: 0.0, + automaticallyImplyLeading: false, + centerTitle: false, + elevation: 0, + backgroundColor: Colors.transparent, + title: Padding( + padding: EdgeInsetsDirectional.only(start: isBackBtn ? 0 : 20), + child: !isConvertText + ? Text(label, style: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600, letterSpacing: 0.5)) + : CustomTextLabel( + text: label, + textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontWeight: FontWeight.w600, letterSpacing: 0.5), + ), + ), + ), + )); + } + + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); +} diff --git a/news-app/lib/ui/widgets/customBackBtn.dart b/news-app/lib/ui/widgets/customBackBtn.dart new file mode 100644 index 00000000..59cff78e --- /dev/null +++ b/news-app/lib/ui/widgets/customBackBtn.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:news/utils/uiUtils.dart'; + +class CustomBackButton extends StatelessWidget { + final Function? onTap; + final double? horizontalPadding; + + const CustomBackButton({super.key, this.onTap, this.horizontalPadding}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: horizontalPadding ?? 0), + child: InkWell( + onTap: () => onTap ?? Navigator.of(context).pop(), + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + child: Icon(Icons.arrow_back, color: UiUtils.getColorScheme(context).primaryContainer))); + } +} diff --git a/news-app/lib/ui/widgets/customTextBtn.dart b/news-app/lib/ui/widgets/customTextBtn.dart new file mode 100644 index 00000000..76208ecc --- /dev/null +++ b/news-app/lib/ui/widgets/customTextBtn.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/styles/colors.dart'; + +class CustomTextButton extends StatelessWidget { + final Function onTap; + final Color? color; + final String? text; + final ButtonStyle? buttonStyle; + final Widget? textWidget; + + const CustomTextButton({super.key, required this.onTap, this.color, this.text, this.buttonStyle, this.textWidget}); + + @override + Widget build(BuildContext context) { + return TextButton( + style: buttonStyle ?? ButtonStyle(overlayColor: WidgetStateProperty.all(Colors.transparent), foregroundColor: WidgetStateProperty.all(darkSecondaryColor)), + onPressed: () { + onTap(); + }, + child: textWidget ?? CustomTextLabel(text: text!, textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(color: color, fontWeight: FontWeight.normal)), + ); + } +} diff --git a/news-app/lib/ui/widgets/customTextLabel.dart b/news-app/lib/ui/widgets/customTextLabel.dart new file mode 100644 index 00000000..11ab9fcd --- /dev/null +++ b/news-app/lib/ui/widgets/customTextLabel.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/languageJsonCubit.dart'; + +class CustomTextLabel extends StatelessWidget { + final String text; + final TextStyle? textStyle; + final TextAlign? textAlign; + final int? maxLines; + final TextOverflow? overflow; + final bool? softWrap; + + const CustomTextLabel({super.key, required this.text, this.textStyle, this.textAlign, this.maxLines, this.overflow, this.softWrap}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Text(context.read().getTranslatedLabels(text), maxLines: maxLines, overflow: overflow, softWrap: softWrap, style: textStyle, textAlign: textAlign); + }, + ); + } +} diff --git a/news-app/lib/ui/widgets/errorContainerWidget.dart b/news-app/lib/ui/widgets/errorContainerWidget.dart new file mode 100644 index 00000000..3d10e833 --- /dev/null +++ b/news-app/lib/ui/widgets/errorContainerWidget.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:lottie/lottie.dart'; +import 'package:news/ui/widgets/customTextBtn.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/uiUtils.dart'; + +class ErrorContainerWidget extends StatelessWidget { + final String errorMsg; + final Function onRetry; + const ErrorContainerWidget({super.key, required this.errorMsg, required this.onRetry}); + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.center, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + (errorMsg.contains(UiUtils.getTranslatedLabel(context, 'internetmsg'))) + ? Container( + width: MediaQuery.of(context).size.width * (0.7), + margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top + MediaQuery.of(context).size.height * (0.05)), + height: MediaQuery.of(context).size.height * (0.4), + child: Lottie.asset("assets/animations/noInternet.json")) + : SizedBox(height: MediaQuery.of(context).size.height * (0.2)), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: CustomTextLabel( + text: errorMsg, + textAlign: TextAlign.center, + maxLines: 4, + overflow: TextOverflow.ellipsis, + textStyle: TextStyle(color: Theme.of(context).colorScheme.primaryContainer, fontSize: 16, fontWeight: FontWeight.w300))), + SizedBox(height: MediaQuery.of(context).size.height * (0.035)), + CustomTextButton( + onTap: () { + onRetry.call(); + }, + buttonStyle: ButtonStyle( + backgroundColor: WidgetStateProperty.all(UiUtils.getColorScheme(context).primaryContainer), + shape: WidgetStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)))), + textWidget: CustomTextLabel( + text: 'RetryLbl', + textStyle: Theme.of(context).textTheme.titleLarge?.copyWith(color: UiUtils.getColorScheme(context).surface, fontWeight: FontWeight.w600, fontSize: 21, letterSpacing: 0.6), + )), + SizedBox(height: MediaQuery.of(context).size.height * (0.15)), + ], + )); + } +} diff --git a/news-app/lib/ui/widgets/loadingScreen.dart b/news-app/lib/ui/widgets/loadingScreen.dart new file mode 100644 index 00000000..100834a3 --- /dev/null +++ b/news-app/lib/ui/widgets/loadingScreen.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/breakingNewsCubit.dart'; +import 'package:news/cubits/languageCubit.dart'; +import 'package:news/cubits/slugNewsCubit.dart'; +import 'package:news/data/models/BreakingNewsModel.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/utils/strings.dart'; +import 'package:news/utils/uiUtils.dart'; + +//TODO: if change in API calls or variables, change the same in Dashboard screen > initDynamicLinks() +class LoadingScreen extends StatefulWidget { + String routeSettingsName; + String newsSlug; + + LoadingScreen({required this.routeSettingsName, required this.newsSlug, Key? key}) : super(key: key); + + @override + _LoadingScreenState createState() => _LoadingScreenState(); +} + +class _LoadingScreenState extends State { + ValueNotifier isLoading = ValueNotifier(true); + + @override + void initState() { + super.initState(); + + context.read().getLanguage().then( + (value) { + fetchData(); + }, + ); + isLoading.addListener(() { + if (!isLoading.value) { + Navigator.pop(context); + } + }); + } + + Future fetchData() async { + await Future.delayed(Duration(seconds: 2)); // Simulate API delay + if (widget.routeSettingsName.contains('/news/')) { + String langCodeShared = widget.routeSettingsName.split("/")[1]; + String? langIdPass = UiUtils.rootNavigatorKey.currentContext!.read().state.id; + if (context.read().langList().isNotEmpty) langIdPass = context.read().langList().firstWhere((e) => e.code == langCodeShared).id; + UiUtils.rootNavigatorKey.currentContext?.read().getSlugNews(langId: langIdPass ?? "0", newsSlug: widget.newsSlug).then((value) { + if (value[DATA] != null) { + NewsModel? model = (value[DATA] as List).map((e) => NewsModel.fromJson(e)).toList().first; + Navigator.popAndPushNamed(context, Routes.newsDetails, + arguments: {"model": model, "slug": widget.newsSlug, "isFromBreak": widget.routeSettingsName.contains('/breaking-news/') ? true : false, "fromShowMore": false}); + } else { + setState(() { + isLoading.value = false; + }); + } + }); + } else if (widget.routeSettingsName.contains('/breaking-news/')) { + UiUtils.rootNavigatorKey.currentContext?.read().getBreakingNews(langId: UiUtils.rootNavigatorKey.currentContext!.read().state.id).then((value) { + BreakingNewsModel? brModel = value[0]; + Navigator.of(context).pushReplacementNamed(Routes.newsDetails, arguments: {"breakModel": brModel, "slug": widget.newsSlug, "isFromBreak": true, "fromShowMore": false}); + }); + } + } + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: isLoading, + builder: (context, value, child) { + return Scaffold(body: Center(child: CircularProgressIndicator())); + }); + } +} diff --git a/news-app/lib/ui/widgets/networkImage.dart b/news-app/lib/ui/widgets/networkImage.dart new file mode 100644 index 00000000..aafde7c7 --- /dev/null +++ b/news-app/lib/ui/widgets/networkImage.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:news/utils/uiUtils.dart'; + +class CustomNetworkImage extends StatelessWidget { + final String networkImageUrl; + final double? width, height; + final BoxFit? fit; + final bool? isVideo; + final Widget? errorBuilder; + + const CustomNetworkImage({super.key, required this.networkImageUrl, this.width, this.height, this.fit, this.isVideo, this.errorBuilder}); + + @override + Widget build(BuildContext context) { + return FadeInImage.assetNetwork( + fadeInDuration: const Duration(milliseconds: 150), + fadeOutCurve: Curves.bounceOut, + image: networkImageUrl, + width: width ?? 100, + height: height ?? 100, + fit: fit ?? BoxFit.cover, + imageErrorBuilder: (context, error, stackTrace) { + return Image.asset(UiUtils.getPlaceholderPngPath(), width: width ?? 100, height: height ?? 100); + }, + placeholderErrorBuilder: (context, error, stackTrace) { + return Image.asset(UiUtils.getPlaceholderPngPath(), width: width ?? 100, height: height ?? 100); + }, + placeholderFit: fit ?? BoxFit.cover, + placeholder: UiUtils.getPlaceholderPngPath()); + } +} diff --git a/news-app/lib/ui/widgets/newsCard.dart b/news-app/lib/ui/widgets/newsCard.dart new file mode 100644 index 00000000..d955ebbd --- /dev/null +++ b/news-app/lib/ui/widgets/newsCard.dart @@ -0,0 +1,179 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/Bookmark/UpdateBookmarkCubit.dart'; +import 'package:news/cubits/Bookmark/bookmarkCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import 'package:news/utils/uiUtils.dart'; + +class NewsCard extends StatefulWidget { + NewsModel newsDetail; + bool showViews; + bool showTags; + Function()? onTap; + NewsCard({super.key, required this.newsDetail, this.showViews = false, this.showTags = false, required this.onTap}); + + @override + State createState() => NewsCardState(); +} + +class NewsCardState extends State { + List tagList = []; + List tagId = []; + late NewsModel newsData; + + @override + void initState() { + super.initState(); + + newsData = widget.newsDetail; + + if (newsData.tagName! != "") { + final tagName = newsData.tagName!; + tagList = tagName.split(','); + } + + if (newsData.tagId != null && newsData.tagId! != "") { + tagId = newsData.tagId!.split(","); + } + } + + @override + Widget build(BuildContext context) { + return Container( + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration(color: UiUtils.getColorScheme(context).surface, borderRadius: BorderRadius.circular(8)), + child: GestureDetector( + onTap: widget.onTap, + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8, + children: [setImage(), setContent()], + ), + (widget.showViews) ? setBookmarkIconButton() : SizedBox.shrink() + ], + ), + ), + ); + } + + Widget setContent() { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8, + children: [ + //title + CustomTextLabel(text: widget.newsDetail.title!, textStyle: TextStyle(fontWeight: FontWeight.bold)), + Row( + spacing: 10, + children: [(widget.newsDetail.date != null && widget.newsDetail.date!.isNotEmpty) ? setDate() : SizedBox.shrink(), (widget.showViews) ? setViews() : SizedBox.shrink()], + ), + ], + ), + ); + } + + Widget setBookmarkIconButton() { + return BlocListener( + listener: (context, state) { + if (state is UpdateBookmarkStatusSuccess) { + context.read().getBookmark(langId: context.read().state.id); + } + }, + child: Positioned.directional( + textDirection: Directionality.of(context), + end: 10, + top: 10, + child: GestureDetector( + onTap: () { + context.read().setBookmarkNews(news: widget.newsDetail, status: "0"); + }, + child: Container( + height: 30, + width: 30, + decoration: BoxDecoration(borderRadius: const BorderRadius.all(Radius.circular(15)), color: UiUtils.getColorScheme(context).surface), + child: Icon(Icons.bookmark_outlined, color: UiUtils.getColorScheme(context).onPrimary, size: 20)))), + ); + } + + Widget setViews() { + return Row(spacing: 8, children: [ + Icon(Icons.remove_red_eye_rounded, size: 20), + CustomTextLabel(text: "${widget.newsDetail.totalViews} ${UiUtils.getTranslatedLabel(context, 'viewsLbl')}", textStyle: TextStyle(fontSize: 12)) + ]); + } + + Widget setDate() { + return Row( + spacing: 8, + children: [Icon(Icons.calendar_month_rounded, size: 20), CustomTextLabel(text: UiUtils.formatDate(widget.newsDetail.date ?? ''), textStyle: TextStyle(fontSize: 12))], + ); + } + + Widget setTags() { + return Positioned.directional( + textDirection: Directionality.of(context), + bottom: 15.0, + start: 7.0, + child: widget.newsDetail.tagName! != "" + ? SizedBox( + height: 30.0, + child: ListView.builder( + physics: const AlwaysScrollableScrollPhysics(), + scrollDirection: Axis.horizontal, + shrinkWrap: true, + itemCount: tagList.length, + itemBuilder: (context, index) { + return Padding( + padding: EdgeInsetsDirectional.only(start: index == 0 ? 0 : 5.5), + child: InkWell( + child: Container( + height: 25.0, + width: 65, + alignment: Alignment.center, + padding: const EdgeInsetsDirectional.only(start: 3.0, end: 3.0, top: 1.0, bottom: 1.0), + decoration: BoxDecoration(borderRadius: const BorderRadius.all(Radius.circular(15)), color: UiUtils.getColorScheme(context).secondary.withOpacity(0.85)), + child: CustomTextLabel( + text: tagList[index], + textStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(color: UiUtils.getColorScheme(context).primaryContainer, fontSize: 12), + overflow: TextOverflow.ellipsis, + softWrap: true)), + onTap: () async { + Navigator.of(context).pushNamed(Routes.tagScreen, arguments: {"tagId": tagId[index], "tagName": tagList[index]}); + }, + ), + ); + })) + : const SizedBox.shrink()); + } + + Widget setImage() { + return Stack( + children: [ + Container( + height: 192, + color: borderColor, + child: ShaderMask( + shaderCallback: (rect) => + LinearGradient(begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [darkSecondaryColor.withOpacity(0.6), darkSecondaryColor.withOpacity(0.6)]).createShader(rect), + blendMode: BlendMode.darken, + child: Container( + color: primaryColor.withAlpha(5), + width: double.maxFinite, + height: MediaQuery.of(context).size.height / 3.3, + child: CustomNetworkImage(width: double.maxFinite, networkImageUrl: widget.newsDetail.image ?? ''), + ))), + //tags + if (widget.showTags) setTags() + ], + ); + } +} diff --git a/news-app/lib/ui/widgets/shimmerNewsList.dart b/news-app/lib/ui/widgets/shimmerNewsList.dart new file mode 100644 index 00000000..4ebe5dae --- /dev/null +++ b/news-app/lib/ui/widgets/shimmerNewsList.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; + +class ShimmerNewsList extends StatelessWidget { + bool isNews = true; + + ShimmerNewsList({Key? key, required this.isNews}) : super(key: key); + + @override + Widget build(BuildContext context) { + //videos,Subcategory,Bookmarks & TagsPage + return Shimmer.fromColors( + baseColor: Colors.grey.withOpacity(0.6), + highlightColor: Colors.grey, + child: SingleChildScrollView( + padding: const EdgeInsetsDirectional.only(top: 0.0), + child: ListView.builder( + shrinkWrap: true, + physics: const AlwaysScrollableScrollPhysics(), + itemBuilder: (_, i) => Padding( + padding: EdgeInsetsDirectional.only(top: i == 0 ? 0 : 15.0), + child: Padding( + padding: EdgeInsetsDirectional.only(top: MediaQuery.of(context).size.height / 35.0, start: 15.0, end: 15.0), + child: Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(10.0), + child: Container( + height: MediaQuery.of(context).size.height / 4.2, + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: Colors.grey.withOpacity(0.6)), + )), + (!isNews) + ? const SizedBox.shrink() + : Positioned.directional( + textDirection: Directionality.of(context), + bottom: 10.0, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: MediaQuery.of(context).size.width / 1.15, + height: 10.0, + margin: const EdgeInsetsDirectional.only(top: 4.0, start: 5.0, end: 5.0), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: Colors.grey.withOpacity(0.6)), + ), + Container( + width: MediaQuery.of(context).size.width / 2.0, + height: 10.0, + margin: const EdgeInsetsDirectional.only(top: 4.0, start: 5.0, end: 5.0), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: Colors.grey.withOpacity(0.6)), + ), + ], + ), + ), + ], + ), + )), + itemCount: 6, + ), + )); + } +} + +Widget iconButtons({required BuildContext context}) { + return Container( + width: MediaQuery.of(context).size.width / 20.0, + height: 20.0, + padding: const EdgeInsetsDirectional.only(top: 4.0, start: 5.0, end: 5.0), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(15.0), color: Colors.grey.withOpacity(0.6)), + ); +} diff --git a/news-app/lib/ui/widgets/videoItem.dart b/news-app/lib/ui/widgets/videoItem.dart new file mode 100644 index 00000000..ee6e810c --- /dev/null +++ b/news-app/lib/ui/widgets/videoItem.dart @@ -0,0 +1,215 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:news/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart'; +import 'package:news/cubits/LikeAndDislikeNews/updateLikeAndDislikeCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/data/repositories/LikeAndDisLikeNews/LikeAndDisLikeNewsRepository.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/strings.dart'; +import 'package:youtube_player_flutter/youtube_player_flutter.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/cubits/Bookmark/UpdateBookmarkCubit.dart'; +import 'package:news/cubits/Bookmark/bookmarkCubit.dart'; +import 'package:news/data/models/NewsModel.dart'; +import 'package:news/data/repositories/Bookmark/bookmarkRepository.dart'; +import 'package:news/utils/internetConnectivity.dart'; +import 'package:news/utils/uiUtils.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; +import 'package:news/ui/widgets/networkImage.dart'; +import 'package:html/parser.dart'; + +class VideoItem extends StatefulWidget { + final NewsModel model; + + VideoItem({super.key, required this.model}); + + @override + VideoItemState createState() => VideoItemState(); +} + +class VideoItemState extends State { + String formattedDescription = ""; + + @override + void initState() { + super.initState(); + } + + void dispose() async { + super.dispose(); + } + + void checkAndSetDescription({required String descr}) { + formattedDescription = ""; + + // Parse HTML and extract plain text + formattedDescription = parse(descr).body?.text ?? ''; + } + + Widget videoData(NewsModel video) { + checkAndSetDescription(descr: video.shortDesc ?? video.desc ?? ''); + return Padding( + padding: EdgeInsets.zero, + child: Column( + children: [ + ClipRRect( + child: Stack( + alignment: Alignment.center, + children: [ + ShaderMask( + shaderCallback: (Rect bounds) { + return LinearGradient(begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.transparent, darkSecondaryColor]).createShader(bounds); + }, + blendMode: BlendMode.darken, + child: CustomNetworkImage( + networkImageUrl: (video.contentType == 'video_youtube' && video.contentValue!.isNotEmpty) + ? 'https://img.youtube.com/vi/${YoutubePlayer.convertUrlToId(video.contentValue!)!}/0.jpg' + : video.image!, + fit: BoxFit.cover, + width: double.maxFinite, + height: (Platform.isIOS) ? MediaQuery.of(context).size.height / 1.29 : MediaQuery.of(context).size.height / 1.25, + isVideo: true)), + CircleAvatar(radius: 30, backgroundColor: Colors.black45, child: Icon(Icons.play_arrow, size: 40, color: Colors.white)), + Positioned.directional( + textDirection: Directionality.of(context), + bottom: 25.0, + start: 0, + height: MediaQuery.of(context).size.height / 8.4, + width: MediaQuery.of(context).size.width / 1.3, + child: Padding( + padding: const EdgeInsets.only(left: 10.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Align( + alignment: Alignment.centerLeft, + child: CustomTextLabel( + text: video.title!, + textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: secondaryColor, fontWeight: FontWeight.bold), + maxLines: 2, + overflow: TextOverflow.ellipsis)), + Align( + alignment: Alignment.centerLeft, + child: CustomTextLabel( + text: formattedDescription.trim(), textStyle: Theme.of(context).textTheme.titleSmall!.copyWith(color: secondaryColor), maxLines: 3, overflow: TextOverflow.ellipsis), + ) + ], + ), + )), + Positioned.directional( + textDirection: Directionality.of(context), + bottom: 10.0, + end: 10.0, + height: MediaQuery.of(context).size.height / 6, + width: MediaQuery.of(context).size.width / 8, + child: Container( + padding: const EdgeInsets.only(bottom: 5.0), + child: Column( + children: [ + if (video.sourceType == NEWS) //user can like or bookmark videos of source type news, and not Breaking News + Column(children: [ + likeButton(), + const SizedBox(height: 15), + BlocProvider( + create: (context) => UpdateBookmarkStatusCubit(BookmarkRepository()), + child: BlocBuilder( + bloc: context.read(), + builder: (context, bookmarkState) { + bool isBookmark = context.read().isNewsBookmark(video.id!); + return BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is UpdateBookmarkStatusSuccess) { + (state.wasBookmarkNewsProcess) ? context.read().addBookmarkNews(state.news) : context.read().removeBookmarkNews(state.news); + setState(() {}); + } + }), + builder: (context, state) { + return InkWell( + onTap: () { + if (context.read().getUserId() != "0") { + if (state is UpdateBookmarkStatusInProgress) return; + context.read().setBookmarkNews(news: video, status: (isBookmark) ? "0" : "1"); + } else { + UiUtils.loginRequired(context); + } + }, + child: state is UpdateBookmarkStatusInProgress + ? SizedBox(height: 15, width: 15, child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)) + : Icon(isBookmark ? Icons.bookmark_added_rounded : Icons.bookmark_add_outlined, color: secondaryColor)); + }); + }), + ), + const SizedBox(height: 15), + ]), + InkWell( + onTap: () async { + (await InternetConnectivity.isNetworkAvailable()) + ? UiUtils.shareNews(context: context, slug: video.slug ?? "", title: video.title!, isVideo: true, videoId: video.id!, isBreakingNews: false, isNews: false) + : showSnackBar(UiUtils.getTranslatedLabel(context, 'internetmsg'), context); + }, + splashColor: Colors.transparent, + child: const Icon(Icons.share_rounded, color: secondaryColor)) + ], + ), + ), + ), + ], + ), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return (widget.model.contentValue!.isNotEmpty) ? videoData(widget.model) : const SizedBox.shrink(); + } + + Widget likeButton() { + bool isLike = context.read().isNewsLikeAndDisLike(widget.model.newsId!); + + return BlocProvider( + create: (context) => UpdateLikeAndDisLikeStatusCubit(LikeAndDisLikeRepository()), + child: BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is LikeAndDisLikeFetchSuccess) { + isLike = context.read().isNewsLikeAndDisLike(widget.model.newsId!); + } + }), + builder: (context, likeAndDislikeState) { + return BlocConsumer( + bloc: context.read(), + listener: ((context, state) { + if (state is UpdateLikeAndDisLikeStatusSuccess) { + context.read().getLike(langId: context.read().state.id); + } + }), + builder: (context, state) { + return InkWell( + splashColor: Colors.transparent, + onTap: () { + if (context.read().getUserId() != "0") { + if (state is UpdateLikeAndDisLikeStatusInProgress) { + return; + } + context.read().setLikeAndDisLikeNews( + news: widget.model, + status: (isLike) ? "0" : "1", + ); + } else { + UiUtils.loginRequired(context); + } + }, + child: Container( + child: (state is UpdateLikeAndDisLikeStatusInProgress) + ? SizedBox(height: 15, width: 15, child: UiUtils.showCircularProgress(true, Theme.of(context).primaryColor)) + : ((isLike) ? const Icon(Icons.thumb_up_alt, size: 25, color: secondaryColor) : const Icon(Icons.thumb_up_off_alt, size: 25, color: secondaryColor)))); + }); + })); + } +} diff --git a/news-app/lib/ui/widgets/videoPlayContainer.dart b/news-app/lib/ui/widgets/videoPlayContainer.dart new file mode 100644 index 00000000..f554e72f --- /dev/null +++ b/news-app/lib/ui/widgets/videoPlayContainer.dart @@ -0,0 +1,86 @@ +import 'package:flick_video_player/flick_video_player.dart'; +import 'package:flutter/material.dart'; +import 'package:video_player/video_player.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import 'package:youtube_player_flutter/youtube_player_flutter.dart'; + +class VideoPlayContainer extends StatefulWidget { + final String contentType, contentValue; + final bool autoPlay; + + Duration lastPosition; + + VideoPlayContainer({super.key, required this.contentType, required this.contentValue, this.autoPlay = true, this.lastPosition = Duration.zero}); + @override + VideoPlayContainerState createState() => VideoPlayContainerState(); +} + +class VideoPlayContainerState extends State { + bool playVideo = false; + + FlickManager? flickManager; + YoutubePlayerController? _yc; + + late VideoPlayerController _controller; + late final WebViewController webViewController; + + @override + void initState() { + super.initState(); + initialisePlayer(); + } + + @override + void dispose() async { + try { + if (widget.contentType == "video_upload") { + // pause only if controller still valid + if (_controller.value.isInitialized && _controller.value.isPlaying) { + _controller.pause(); + } + + // exit fullscreen safely + flickManager?.flickControlManager?.exitFullscreen(); + + // dispose only the flick manager + flickManager?.dispose(); + flickManager = null; + } else if (widget.contentValue == "video_youtube") { + _yc?.dispose(); + } + } catch (e) {} + + super.dispose(); // always last + } + + initialisePlayer() { + if (widget.contentValue != "") { + if (widget.contentType == "video_upload") { + _controller = VideoPlayerController.networkUrl(Uri.parse(widget.contentValue)); + flickManager = FlickManager( + videoPlayerController: _controller, + autoPlay: widget.autoPlay, + onVideoEnd: () { + widget.lastPosition = Duration.zero; + }); + } else if (widget.contentType == "video_youtube" || widget.contentType == "url_youtube") { + _yc = YoutubePlayerController(initialVideoId: YoutubePlayer.convertUrlToId(widget.contentValue) ?? "", flags: YoutubePlayerFlags(autoPlay: widget.autoPlay)); + } else if (widget.contentType == "video_other") { + webViewController = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..loadRequest(Uri.parse(widget.contentValue)); + } + } + } + + @override + Widget build(BuildContext context) { + return (widget.contentType == "video_upload") + ? FlickVideoPlayer(flickManager: flickManager!) + : (widget.contentType == "video_youtube" || widget.contentType == "url_youtube") + ? YoutubePlayer(controller: _yc!, showVideoProgressIndicator: true, progressIndicatorColor: Theme.of(context).primaryColor) + : widget.contentType == "video_other" + ? Center(child: WebViewWidget(controller: webViewController)) + : const SizedBox.shrink(); + } +} diff --git a/news-app/lib/ui/widgets/videoShimmer.dart b/news-app/lib/ui/widgets/videoShimmer.dart new file mode 100644 index 00000000..11296a79 --- /dev/null +++ b/news-app/lib/ui/widgets/videoShimmer.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; + +videoShimmer(BuildContext context) { + return Shimmer.fromColors( + baseColor: Colors.grey.withOpacity(0.6), + highlightColor: Colors.grey, + child: SingleChildScrollView( + padding: const EdgeInsetsDirectional.only(top: 15.0), + child: ListView.builder( + shrinkWrap: true, + physics: const AlwaysScrollableScrollPhysics(), + itemBuilder: (_, i) => Padding( + padding: EdgeInsetsDirectional.only(top: i == 0 ? 0 : 15.0), + child: Stack(children: [Container(decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), color: Colors.grey.withOpacity(0.6)), height: 215.0)])), + itemCount: 6), + )); +} diff --git a/news-app/lib/utils/ErrorMessageKeys.dart b/news-app/lib/utils/ErrorMessageKeys.dart new file mode 100644 index 00000000..cbe4da5a --- /dev/null +++ b/news-app/lib/utils/ErrorMessageKeys.dart @@ -0,0 +1,7 @@ +class ErrorMessageKeys { + static const String defaultErrorMessage = "Something went wrong. Please try again later!"; + static const String noInternet = "No internet"; + static const String serverDownMessage = "Our service is temporarily unavailable due to maintenance or high traffic. Please check back in a little while."; + static const String noDataMessage = "No Data Found"; + static const String requestAgainMessage = "The Request cannot be fulfilled for now.\nPlease Try again later"; +} diff --git a/news-app/lib/utils/api.dart b/news-app/lib/utils/api.dart new file mode 100644 index 00000000..044587d3 --- /dev/null +++ b/news-app/lib/utils/api.dart @@ -0,0 +1,165 @@ +import 'dart:io'; +import 'package:dio/dio.dart'; +import 'package:news/data/repositories/Auth/authLocalDataSource.dart'; +import 'package:news/utils/ErrorMessageKeys.dart'; +import 'package:news/utils/internetConnectivity.dart'; +import 'package:news/utils/constant.dart'; + +class ApiMessageAndCodeException implements Exception { + final String errorMessage; + + ApiMessageAndCodeException({required this.errorMessage}); + + Map toError() => {"message": errorMessage}; + + @override + String toString() => errorMessage; +} + +class ApiException implements Exception { + String errorMessage; + + ApiException(this.errorMessage); + + @override + String toString() { + return errorMessage; + } +} + +class Api { + static String getToken() { + String token = AuthLocalDataSource().getJWTtoken(); + return (token.trim().isNotEmpty) ? token : ""; + } + + static Map get headers => {"Authorization": 'Bearer ${getToken()}'}; + + //all apis list + static String getUserSignUpApi = 'user_signup'; + static String getNewsApi = 'get_news'; + static String getSettingApi = 'get_settings'; + static String getCatApi = 'get_category'; + static String setBookmarkApi = 'set_bookmark'; + static String getBookmarkApi = 'get_bookmark'; + static String setCommentApi = 'set_comment'; + static String getCommentByNewsApi = 'get_comment_by_news'; + static String getBreakingNewsApi = 'get_breaking_news'; + static String setUpdateProfileApi = 'update_profile'; + static String setRegisterToken = 'register_token'; + static String setUserCatApi = 'set_user_category'; + static String getUserByIdApi = 'get_user_by_id'; + static String setCommentDeleteApi = 'delete_comment'; + static String setLikesDislikesApi = 'set_like_dislike'; + static String setFlagApi = 'set_flag'; + static String getLiveStreamingApi = 'get_live_streaming'; + static String getSubCategoryApi = 'get_subcategory_by_category'; + static String setLikeDislikeComApi = 'set_comment_like_dislike'; + static String deleteUserNotiApi = 'delete_user_notification'; + static String getQueApi = 'get_question'; + static String getQueResultApi = 'get_question_result'; + static String setQueResultApi = 'set_question_result'; + static String userDeleteApi = 'delete_user'; + static String getTagsApi = 'get_tag'; + static String setNewsApi = 'set_news'; + static String setDeleteNewsApi = 'delete_news'; + static String setDeleteImageApi = 'delete_news_images'; + static String getVideosApi = 'get_videos'; + static String getLanguagesApi = 'get_languages_list'; + static String getLangJsonDataApi = 'get_language_json_data'; + static String getPagesApi = 'get_pages'; + static String getPolicyPagesApi = 'get_policy_pages'; + static String getFeatureSectionApi = 'get_featured_sections'; + static String getLikeNewsApi = 'get_like'; + static String setNewsViewApi = 'set_news_view'; + static String setBreakingNewsViewApi = 'set_breaking_news_view'; + static String getAdsNewsDetailsApi = 'get_ad_space_news_details'; + static String getLocationCityApi = 'get_location'; + static String slugCheckApi = 'check_slug_availability'; + static String rssFeedApi = 'get_rss_feed'; + static String becomeAnAuthorApi = 'become_author'; + static String getAuthorNewsApi = 'get_authors_news'; + static String getDraftNewsApi = 'get_user_drafted_news'; + static String geminiMetaInfoApi = 'https://generativelanguage.googleapis.com/v1beta/models/'; + + static FormData? toFormData(dynamic data) { + if (data == null) return null; + + if (data is Map) { + return FormData.fromMap(data, ListFormat.multiCompatible); + } + + if (data is List) { + final formData = FormData(); + for (var value in data) { + formData.fields.add(MapEntry("list[]", value.toString())); + } + return formData; + } + + // Raw value (int/string/etc.) → cannot convert → return null + return null; + } + + static Future> sendApiRequest({required dynamic body, required String url, bool isGet = false}) async { + try { + if (!await InternetConnectivity.isNetworkAvailable()) { + throw const SocketException(ErrorMessageKeys.noInternet); + } + + final Dio dio = Dio(); + final apiUrl = "$databaseUrl$url"; + + // Auto-detect GET for endpoints starting with "get_" + final shouldUseGet = isGet || url.startsWith('get_'); + + // Convert body to query parameters for GET requests + Map? queryParams; + if (shouldUseGet && body != null && body is Map) { + queryParams = body; + } + + // Convert only if possible (for POST) + final convertedBody = toFormData(body); + + final response = (shouldUseGet) + ? await dio.get( + apiUrl, + queryParameters: queryParams ?? {}, + options: Options(headers: headers), + ) + : await dio.post( + apiUrl, + data: convertedBody ?? body, + options: Options(headers: headers), + ); + + if (response.data['error'] == 'true') { + throw ApiException(response.data['message']); + } + + return Map.from(response.data); + } on DioException catch (e) { + print('DEBUG API ERROR: ${e.response?.statusCode} - ${e.message} - URL: $url'); + print('DEBUG API RESPONSE: ${e.response?.data}'); + if (e.response?.statusCode == 503) { + throw ApiException(ErrorMessageKeys.serverDownMessage); + } else if (e.response?.statusCode == 404) { + throw ApiException(ErrorMessageKeys.requestAgainMessage); + } + + throw ApiException( + e.error is SocketException ? ErrorMessageKeys.noInternet : ErrorMessageKeys.defaultErrorMessage, + ); + } on SocketException catch (e) { + print('DEBUG SOCKET ERROR: ${e.message}'); + throw SocketException(e.message); + } on ApiException catch (e) { + print('DEBUG API EXCEPTION: ${e.errorMessage}'); + throw ApiException(e.errorMessage); + } catch (e) { + print('DEBUG CATCH ALL ERROR: $e'); + throw ApiException(ErrorMessageKeys.defaultErrorMessage); + } + } +} diff --git a/news-app/lib/utils/appLanguages.dart b/news-app/lib/utils/appLanguages.dart new file mode 100644 index 00000000..30f6d9ec --- /dev/null +++ b/news-app/lib/utils/appLanguages.dart @@ -0,0 +1,244 @@ +const Map appLanguageLabelKeys = { + "somethingMSg": "Something went wrong. Please try again after some time", + "bookmarkLbl": "Bookmarks", + "loginLbl": "Login", + "welTitle1": "Always Up-to-Date", + "welTitle2": "Bookmark & Share", + "welTitle3": "New Categories", + "welDes1": "Receive notifications for the most recent news updates and many more.", + "welDes2": "Save and easily share news with your friends using our intuitive news app feature.", + "welDes3": "Enjoy expertly tailored news, crafted exclusively for your interests.", + "nameLbl": "Name", + "emailLbl": "Email", + "passLbl": "Password", + "confpassLbl": "Confirm Password", + "priPolicy": "Privacy Policy", + "andLbl": " and ", + "termLbl": "Terms of Service", + "forgotPassLbl": "Forgot Password ?", + "internetmsg": "Internet Connection not available", + "loginMsg": "Login Successfully", + "loginNowLbl": "Login Now", + "logoutLbl": "Logout", + "cancelBtn": "Cancel", + "noNews": "News Not Available", + "exitWR": "Double tap back button to exit", + "shareLbl": "Share", + "deactiveMsg": "You are deactivated by admin", + "bookmarkNotAvail": "Bookmarks Not Available", + "notiNotAvail": "Notifications Not Available", + "notificationLbl": "Notifications", + "logoutTxt": "Are you sure you want to Logout?", + "yesLbl": "Yes", + "noLbl": "No", + "frgtPassHead": "Enter the email address associated with your account", + "forgotPassSub": "We will email you a link to reset your password", + "submitBtn": "Submit", + "verifyEmailMsg": "Please first verify your email address!!!", + "passReset": "Password reset link has been sent to your mail", + "profileUpdateMsg": "Profile Data Updated Successfully", + "bookmarkLogin": "Please Login to Access Your Bookmarks !!", + "preferenceSave": "Your preference saved!!", + "managePreferences": "Manage Preferences", + "loginReqMsg": "Login Required...", + "firstFillData": "Please First Fill Data...!", + "deleteTxt": "Delete", + "reportTxt": "Report", + "nameRequired": "Name is Required", + "nameLength": "Name should be atleast 2 character long", + "emailRequired": "email address is Required", + "emailValid": "Please enter a valid email Address!", + "pwdRequired": "Password is Required", + "confPassRequired": "Confirm Password is Required", + "confPassNotMatch": "Confirm Password not match", + "photoLibLbl": "Photo Library", + "cameraLbl": "Camera", + "verifSentMail": "Verification email sent to ", + "cancelLogin": "Login cancelled by the user.", + "loginTxt": "Log In", + "loginBtn": "Login", + "signupBtn": "Sign Up", + "otpVerifyLbl": "OTP Verification", + "enterMblLbl": "Enter Your Mobile Number", + "receiveDigitLbl": "You'll Receive 6 digit code for phone number verification", + "reqOtpLbl": "Request OTP", + "otpSentLbl": "OTP has been sent to ", + "resendCodeLbl": "Resend Code in", + "mobileLbl": "Mobile", + "darkModeLbl": "Dark Mode", + "changeLang": "Change Language", + "rateUs": "Rate Us", + "shareApp": "Share App", + "weatherLbl": "Weather Forecast", + "categoryLbl": "Categories", + "allLbl": "All", + "comLbl": "Comment ", + "saveLbl": "Save", + "txtSizeLbl": "Text Size", + "speakLoudLbl": "Speak Loud", + "likeLbl": "likes", + "comsLbl": "Comments", + "shareThoghtLbl": "Share Your Thoughts.", + "repliesLbl": "Replies", + "publicReply": "Add a public reply...", + "personalLbl": "Personal", + "newsLbl": "News", + "plzLbl": "Please", + "fastTrendNewsLbl": "Porque Mereces Verdaderas Respuestas! ", + "enterOtpTxt": "Please Enter OTP", + "otpError": "Error validating OTP, try again", + "otpMsg": "OTP verified successfully", + "resendLbl": "Resend OTP", + "otpTimeoutLbl": "Otp Retrieval Timeout!!!", + "mblRequired": "Mobile number is Required", + "mblValid": "Please enter a valid mobile number!", + "codeSent": "Code Sent Successfully!!!", + "relatedNews": "You might also like", + "optSel": "Please Select One Option!!!", + "madeBy": "Made by", + "skip": "Skip", + "nxt": "Next", + "signInTab": "Sign In", + "agreeTermPolicyLbl": "By Logging In, you agree to our", + "addTCFirst": "Please Ask Admin to Add Privacy Policy & Terms and Conditions first !!", + "orLbl": "or Log In with", + "signupDescr": "Create\nan Account", + "firstAccLbl": "First to access", + "allFunLbl": "all Functions", + "chooseLanLbl": "Select Language", + "videosLbl": "Videos", + "search": "Search", + "searchHomeNews": "Search News, Categories, etc.", + "viewMore": "View More", + "viewFullCoverage": "View full Coverage", + "updateName": "Update your Name", + "loginDescr": "Let's Sign \nYou In", + "logoutAcc": "Logout Account", + "deleteAcc": "Delete Account", + "deleteAlertTitle": "Re-Login", + "deleteRelogin": "To Delete your Account, You need to Login again.\nAfter that you will be able to Delete your Account.", + "deleteConfirm": "Are you sure?\nDo You Really Want to Delete Your Account?", + "pwdLength": "Password should be more than 6 character long", + "userNotFound": "No user found for given email.", + "wrongPassword": "Wrong password provided for that user.", + "weakPassword": "The password provided is too weak.", + "emailAlreadyInUse": "The account already exists for that email.", + "invalidPhoneNumber": "The provided phone number is not valid.", + "invalidVerificationCode": "The sms verification code used to create the phone auth credential is invalid.", + "ago": "ago", + "years": "years", + "months": "months", + "minutes": "minutes", + "seconds": "seconds", + "hours": "hours", + "days": "days", + "justNow": "just now", + "about": "about", + "liveVideosLbl": "Live Videos", + "stdPostLbl": "Standard Post", + "videoYoutubeLbl": "Video (Youtube)", + "videoOtherUrlLbl": "Video (Other Url)", + "videoUploadLbl": "Video (Upload)", + "createNewsLbl": "Create News", + "step1Of2Lbl": "Step 1 of 2", + "catLbl": "Category", + "plzSelCatLbl": "Please select category", + "subcatLbl": "SubCategory", + "contentTypeLbl": "Content Type", + "uploadVideoLbl": "Upload Video", + "youtubeUrlLbl": "Youtube Url", + "otherUrlLbl": "Other Url", + "selContentTypeLbl": "Select Content Type", + "titleLbl": "Title", + "tagLbl": "Tag", + "showTilledDate": "Show Till Date", + "uploadMainImageLbl": "Upload Main Image", + "uploadOtherImageLbl": "Upload Other Image", + "plzUploadVideoLbl": "Please upload video!!!", + "plzAddMainImageLbl": "Please add main image!!!", + "selTagLbl": "Select Tag", + "selSubCatLbl": "Select Sub Category", + "selCatLbl": "Select Category", + "editNewsLbl": "Edit News", + "doYouReallyNewsLbl": "Do You Really Want to Delete this News?", + "delNewsLbl": "Delete News", + "newsTitleReqLbl": "News title is required!!!", + "plzAddValidTitleLbl": "Please add valid news title!!!", + "urlReqLbl": "Url is required!!!", + "plzValidUrlLbl": "Please add valid url!!!", + "manageNewsLbl": "Manage News", + "step2of2Lbl": "Step 2 of 2", + "descLbl": "Description", + "RetryLbl": "Retry", + "previewLbl": "Preview", + "sponsoredLbl": "Sponsored", + "searchForLbl": "Search Result for", + "readLessLbl": "Read less", + "readMoreLbl": "Read more", + "myProfile": "My Profile", + "editProfile": "Edit Profile", + "noComments": "Be the First One to Comment !!!", + "minute": "minute", + "read": "read", + "selLocationLbl": "Select Location", + "metaKeywordLbl": "Meta Keyword", + "metaTitleLbl": "Meta Title", + "metaDescriptionLbl": "Meta Description", + "slugLbl": "Slug", + "metaTitleWarningLbl": "Meta Title length should not exceed 60 characters.", + "metaDescriptionWarningLbl": "Meta Description length should between 50 to 160 characters.", + "metaKeywordWarningLbl": "Meta Keywords are not more than 10 keyword phrases & should be comma separated.", + "slugWarningLbl": "Slug only accept lowercase letters, numbers, and hyphens. No spaces or special characters allowed.", + "metaTitleRequired": "Meta title is Required", + "metaDescriptionRequired": "Meta Description is Required", + "metaKeywordRequired": "Meta Keyword is Required", + "slugRequired": "Slug is Required", + "slugValid": "Please enter valid Slug!", + "slugUsedAlready": "This slug is already in use. Please add any other slug.", + "maintenanceMessageLbl": "We're Under Maintenance\nPlease try again later.", + "notificationLogin": "Please Login to view Your Notifications !!", + "publishDate": "Publish Date", + "dateConfirmation": "Please change Show till date , as it can't be set before Publish date", + "expiredKey": "expired", + "deactivatedKey": "deactivated", + "clearFilter": "Clear filter", + "FilterBy": "Filter by", + "rssFeed": "RSS Feed", + "profile": "Profile", + "homeLbl": "Home", + "replyLbl": "Reply", + "disabledCommentsMsg": "Comments are disabled for this News by admin", + "last": "Last", + "today": "Today", + "clear": "Clear", + "apply": "Apply", + "date": "Date", + "continueWith": "Continue with", + "google": "Google", + "apple": "Apple", + "fb": "Facebook", + "didntGetCode": "Didn't get Code?", + "viewsLbl": "Views", + "recentVidLbl": "Recent News Videos", + "forceUpdateTitleLbl": "Update Required", + "newVersionAvailableTitleLbl": "New Version Available !!!", + "newVersionAvailableDescLbl": "Would you like to update now ?", + "forceUpdateDescLbl": "A new version of this app is available with important improvements and features. To continue using the app, please update to the latest version.", + "exitLbl": "Exit", + "newsCreatedSuccessfully": "Your news has been created successfully! It will be visible to others after admin approval.", + "summarizedDescription": "Summarized Description", + "clickSummarizeDescription": "Click 'Summarize Description' to generate a summary of your content", + "enterDescriptionFirst": "Add description First to generate Summarized Description", + "authorReviewPendingLbl": "Pending Review", + "becomeAuthorLbl": "Become an Author", + "authorLbl": "Author", + "saveAsDraftLbl": "Save As Draft", + "manageNewsAllLbl": "All News", + "manageNewsDraftLbl": "Draft News", + "publishBtnLbl": "Publish", + "addYourBioHintLbl": "Add your bio", + "addLinkHereHintLbl": "Add {type} Link here", + "noteForAuthorLbl": "You can add social media links & Bio only if you are the author.", + "socialMediaLinksLbl": "Social media links", + "followLbl": "Follow :" +}; diff --git a/news-app/lib/utils/constant.dart b/news-app/lib/utils/constant.dart new file mode 100644 index 00000000..c907c67b --- /dev/null +++ b/news-app/lib/utils/constant.dart @@ -0,0 +1,38 @@ +//Please add your admin panel url here and make sure you do not add '/' at the end of the url + +const String baseUrl = "https://news.ajsmartsolutions.space"; + +const String databaseUrl = "$baseUrl/api/"; //Do not change + +const String shareNavigationWebUrl = "https://newspaper.ajsmartsolutions.space"; //with http:// OR https:// + +const int limitOfSectionsData = 5; + +const int limitOfAPIData = 10; +const int limitOfStyle1 = 3; +const int limitOfAllOtherStyle = 20; + +//Facebook Login enable/disable +const bool fblogInEnabled = false; + +//set value for survey show after news data +const int surveyShow = 4; + +//set value for native ads show after news data +const int nativeAdsIndex = 3; + +//set value for interstitial ads show after news data +const int interstitialAdsIndex = 2; + +//set value for reward ads show after news data +const int rewardAdsIndex = 4; + +const String appName = 'elCaribe'; + +enum VideoViewType { normal, page } + +enum ContentType { youtube, uploaded, live } + +enum AuthorStatus { approved, pending, rejected } + +enum AuthorLayoutType { list, grid } diff --git a/news-app/lib/utils/hiveBoxKeys.dart b/news-app/lib/utils/hiveBoxKeys.dart new file mode 100644 index 00000000..9baeacb9 --- /dev/null +++ b/news-app/lib/utils/hiveBoxKeys.dart @@ -0,0 +1,39 @@ +//Authbox keys +const String authBoxKey = "authBox"; +const String jwtTokenKey = "jwtToken"; +const String isLogInKey = "isLogIn"; +const String userIdKey = "userId"; +const String userNameKey = "userName"; +const String userMobKey = "userMob"; +const String userEmailKey = "userEmail"; +const String userProfileKey = "userProfile"; +const String userRoleKey = "userRole"; +const String userStatusKey = "userStatus"; +const String userTypeKey = "Type"; +const String authorWhatsappLinkKey = "whatsAppLink"; +const String authorTelegramLinkKey = "telegramLink"; +const String authorfacebookLinkKey = "facebookLink"; +const String authorLinkedInLinkKey = "linkedInLink"; +const String authorBioKey = "authorBio"; +const String authorStatusKey = "authorStatus"; + +//Settings box keys +const String settingsBoxKey = "settings"; +const String currentLanguageCodeKey = "currentLanguageCode"; +const String currentLanguageIDKey = "currentLanguageId"; +const String currentLanguageRTLKey = "currentLanguageRTL"; +const String introSliderKey = "introSlider"; +const String currentThemeKey = "currentTheme"; +const String notificationKey = "notification"; +const String tokenKey = "token"; +const String historyListKey = "historyList"; +const String notificationEnabledKey = "notificationEnabled"; + +//Location box keys +const String locationCityBoxKey = "locationCity"; +const String latitudeKey = "Latitude"; +const String longitudeKey = "Longitude"; + +///VideoPreference +const String videoPreferenceKey = "videoPreference"; +const String videoStyle = 'videoStyle'; diff --git a/news-app/lib/utils/internetConnectivity.dart b/news-app/lib/utils/internetConnectivity.dart new file mode 100644 index 00000000..7b54aba7 --- /dev/null +++ b/news-app/lib/utils/internetConnectivity.dart @@ -0,0 +1,13 @@ +import 'package:connectivity_plus/connectivity_plus.dart'; + +class InternetConnectivity { + static Future isNetworkAvailable() async { + final List connectivityResult = await Connectivity().checkConnectivity(); + if (connectivityResult.contains(ConnectivityResult.mobile)) { + return true; + } else if (connectivityResult.contains(ConnectivityResult.wifi)) { + return true; + } + return false; + } +} diff --git a/news-app/lib/utils/labelKeys.dart b/news-app/lib/utils/labelKeys.dart new file mode 100644 index 00000000..02eb406b --- /dev/null +++ b/news-app/lib/utils/labelKeys.dart @@ -0,0 +1,2 @@ +const String lightThemeKey = "lightTheme"; +const String darkThemeKey = "darkTheme"; diff --git a/news-app/lib/utils/strings.dart b/news-app/lib/utils/strings.dart new file mode 100644 index 00000000..961e5b39 --- /dev/null +++ b/news-app/lib/utils/strings.dart @@ -0,0 +1,165 @@ +const String ID = "id"; +const String DATA = "data"; +const String NAME = "name"; +const String EMAIL = "email"; +const String TYPE = "type"; +const String URL = "url"; +const String STATUS = "status"; +const String FCM_ID = "fcm_id"; +const String PROFILE = "profile"; +const String ROLE = "role"; +const String MOBILE = "mobile"; +const String FIREBASE_ID = "firebase_id"; +const String CATEGORY_ID = "category_id"; +const String CONTENT_TYPE = "content_type"; +const String SHOW_TILL = "show_till"; +const String CONTENT_VALUE = "content_value"; +const String DATE = "date"; +const String TITLE = "title"; +const String DESCRIPTION = "description"; +const String IMAGE = "image"; +const String CATEGORY_NAME = "category_name"; +const String NEWS_ID = "news_id"; +const String BR_NEWS_ID = "breaking_news_id"; +const String USER_ID = "user_id"; +const String USER = "user"; +const String OFFSET = "offset"; +const String LIMIT = "limit"; +const String USER_NEWS = "get_user_news"; +const String MESSAGE = "message"; +const String COUNTER = "counter"; +const String DATE_SENT = "date_sent"; +const String OTHER_IMAGE = "other_image"; +const String IMAGES = "images"; +const String IMAGE_DATA = "image_data"; +const String COMMENT_ID = "comment_id"; +const String TOTAL_LIKE = "total_like"; +const String LIKE = "like"; +const String BOOKMARK = "bookmark"; +const String SUBCAT_NAME = "subcategory_name"; +const String SUBCAT_ID = "subcategory_id"; +const String DISLIKE = "dislike"; +const String TOTAL_DISLIKE = "total_dislike"; +const String PARENT_ID = "parent_id"; +const String REPLY = "reply"; +const String SEARCH = "search"; +const String TAGNAME = "tag_name"; +const String CATEGORY = "category"; +const String SUBCATEGORY = "sub_category"; +const String SUBCATEGORIES = "sub_categories"; +const String TAG_ID = "tag_id"; +const String TAG = "tag_name"; +const String QUESTION = "question"; +const String QUESTION_ID = "question_id"; +const String OPTION_ID = "option_id"; +const String OPTION = "survey_options"; +const String OPTIONS = "options"; +const String PERCENTAGE = "percentage"; +const String CATEGORY_MODE = "category_mode"; +const String BREAK_NEWS_MODE = "breaking_news_mode"; +const String COMM_MODE = "comments_mode"; +const String LIVE_STREAM_MODE = "live_streaming_mode"; +const String SUBCAT_MODE = "subcategory_mode"; +const String GO_REWARDED_ID = "google_rewarded_video_id"; +const String GO_INTER_ID = "google_interstitial_id"; +const String GO_BANNER_ID = "google_banner_id"; +const String GO_NATIVE_ID = "google_native_unit_id"; +const String IOS_GO_REWARDED_ID = "ios_google_rewarded_video_id"; +const String IOS_GO_INTER_ID = "ios_google_interstitial_id"; +const String IOS_GO_BANNER_ID = "ios_google_banner_id"; +const String IOS_GO_NATIVE_ID = "ios_google_native_unit_id"; +const String U_REWARDED_ID = "unity_rewarded_video_id"; +const String U_INTER_ID = "unity_interstitial_id"; +const String U_BANNER_ID = "unity_banner_id"; +const String U_AND_GAME_ID = "android_game_id"; +const String IOS_U_REWARDED_ID = "ios_unity_rewarded_video_id"; +const String IOS_U_INTER_ID = "ios_unity_interstitial_id"; +const String IOS_U_BANNER_ID = "ios_unity_banner_id"; +const String IOS_U_GAME_ID = "ios_game_id"; +const String ADS_MODE = "in_app_ads_mode"; +const String IOS_ADS_MODE = "ios_in_app_ads_mode"; +const String ADS_TYPE = "ads_type"; +const String IOS_ADS_TYPE = "ios_ads_type"; +const String LOCATION_WISE_NEWS_MODE = "location_news_mode"; +const String WEATHER_MODE = "weather_mode"; +const String MAINTENANCE_MODE = "maintenance_mode"; +const String LOCATION = "location"; +const String LOCATION_ID = "location_id"; +const String LOCATION_NAME = "location_name"; +const String LATITUDE = "latitude"; +const String LONGITUDE = "longitude"; +const String TOKEN = "token"; +const String LANGUAGE = "language"; +const String CODE = "code"; +const String DEFAULT_LANG = "default_language"; +const String PAGE_CONTENT = "page_content"; +const String ISRTL = "isRTL"; +const String LANGUAGE_ID = "language_id"; +const String SECTION_ID = "section_id"; +const String PAGE_ICON = "page_icon"; +const String NEWS = "news"; +const String BREAKING_NEWS = "breaking_news"; +const String VIDEOS = "videos"; +const String SHORT_DESC = "short_description"; +const String NEWS_TYPE = "news_type"; +const String VIDEOS_TYPE = "videos_type"; +const String FILTER_TYPE = "filter_type"; +const String CAT_IDS = "category_ids"; +const String SUBCAT_IDS = "subcategory_ids"; +const String NEWS_IDS = "news_ids"; +const String STYLE_APP = "style_app"; +const String NEWS_TOTAL = "news_total"; +const String TOTAL = "total"; +const String BREAK_NEWS_TOTAL = "breaking_news_total"; +const String VIDEOS_TOTAL = "videos_total"; +const String IS_LOGIN = "is_login"; +const String TOTAL_VIEWS = "total_views"; +const String DISPLAY_NAME_LANG = "display_name"; +const String AD_SPACES = "ad_spaces"; +const String AD_SPACE = "ad_space"; +const String AD_FEATURED_SECTION_ID = "ad_featured_section_id"; +const String AD_IMAGE = "ad_image"; +const String AD_URL = "ad_url"; +const String ACTION_TYPE = "action_type"; +const String CONTENT_DATA = "content_data"; +const String META_TITLE = "meta_title"; +const String META_DESC = "meta_description"; +const String META_KEYWORD = "meta_keyword"; +const String SLUG = "slug"; +const String ERROR = "error"; +const String PUBLISHED_DATE = "published_date"; +const String IS_EXPIRED = "is_expired"; +const String FEED_NAME = "feed_name"; +const String FEED_URL = "feed_url"; +const String RSS_FEED_MODE = "rss_feed_mode"; +const String MOBILE_LOGIN_MODE = "mobile_login_mode"; +const String COUNTRY_CODE = "country_code"; +const String SHARE_APP_TEXT = "shareapp_text"; +const String APPSTORE_ID = "app_store_id"; +const String ANDROID_APP_LINK = "android_app_link"; +const String IOS_APP_LINK = "ios_app_link"; +const String WEB_SETTING = "web_setting"; +const String MERGE_TAG = "merge_tag"; +const String IS_COMMENT_ENABLED = "is_comment"; +const String LAST_N_DAYS = "last_n_days"; +const String YEAR = "year"; +const String VIDEO_TYPE_PREFERENCE = "video_type_preference"; +const String SOURCE_TYPE = "source_type"; +const String SECTION_OFFSET = "section_offset"; +const String SECTION_LIMIT = "section_limit"; +const String UPDATED_DATE = "updated_at"; +const String FORCE_UPDT_APP_MODE = 'force_update_app_mode'; +const String ANDROID_APP_VERSION = 'android_app_version'; +const String IOS_APP_VERSION = 'ios_app_version'; +const String SUMM_DESCRIPTION = 'summarized_description'; +const String GEMINI_API_KEY = 'google_gemini_api_key'; +const String IS_DRAFT_KEY = 'is_draft'; +const String IS_AUTHOR = 'is_author'; +const String AUTHOR = 'author'; +const String AUTHOR_BIO = 'bio'; +const String AUTHOR_STATUS = "author_status"; +const String AUTHOR_WHATSAPP_LINK = 'whatsapp_link'; +const String AUTHOR_TELEGRAM_LINK = 'telegram_link'; +const String AUTHOR_FACEBOOK_LINK = 'facebook_link'; +const String AUTHOR_LINKEDIN_LINK = 'linkedin_link'; +const String AUTHOR_ID = "author_id"; diff --git a/news-app/lib/utils/uiUtils.dart b/news-app/lib/utils/uiUtils.dart new file mode 100644 index 00000000..884026c6 --- /dev/null +++ b/news-app/lib/utils/uiUtils.dart @@ -0,0 +1,507 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:dotted_border/dotted_border.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; +import 'package:hive/hive.dart'; +import 'package:intl/intl.dart'; +import 'package:intl/date_symbol_data_local.dart'; +import 'package:news/app/routes.dart'; +import 'package:news/cubits/Auth/authCubit.dart'; +import 'package:news/cubits/Bookmark/bookmarkCubit.dart'; +import 'package:news/cubits/LikeAndDislikeNews/LikeAndDislikeCubit.dart'; +import 'package:news/cubits/appLocalizationCubit.dart'; +import 'package:news/cubits/appSystemSettingCubit.dart'; +import 'package:news/cubits/languageJsonCubit.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/InterstitialAds/googleInterstitialAds.dart'; +import 'package:news/ui/screens/NewsDetail/Widgets/InterstitialAds/unityInterstitialAds.dart'; +import 'package:news/ui/screens/auth/Widgets/svgPictureWidget.dart'; +import 'package:news/ui/styles/colors.dart'; +import 'package:news/ui/widgets/SnackBarWidget.dart'; +import 'package:news/ui/widgets/customTextLabel.dart'; +import 'package:news/utils/constant.dart'; +import 'package:news/utils/labelKeys.dart'; +import 'package:news/ui/styles/appTheme.dart'; +import 'package:news/utils/hiveBoxKeys.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class UiUtils { + static GlobalKey rootNavigatorKey = GlobalKey(); + + static Future setDynamicStringValue(String key, String value) async { + Hive.box(settingsBoxKey).put(key, value); + } + + static Future setDynamicListValue(String key, String value) async { + List? valueList = getDynamicListValue(key); + if (!valueList.contains(value)) { + if (valueList.length > 4) valueList.removeAt(0); + valueList.add(value); + + Hive.box(settingsBoxKey).put(key, valueList); + } + } + + static List getDynamicListValue(String key) { + return Hive.box(settingsBoxKey).get(key) ?? []; + } + + static String getSvgImagePath(String imageName) { + return "assets/images/svgImage/$imageName.svg"; + } + + static String getPlaceholderPngPath() { + return "assets/images/placeholder.png"; + } + + static ColorScheme getColorScheme(BuildContext context) { + return Theme.of(context).colorScheme; + } + +// get app theme + static String getThemeLabelFromAppTheme(AppTheme appTheme) { + if (appTheme == AppTheme.Dark) { + return darkThemeKey; + } + return lightThemeKey; + } + + static AppTheme getAppThemeFromLabel(String label) { + return (label == darkThemeKey) ? AppTheme.Dark : AppTheme.Light; + } + + static String getTranslatedLabel(BuildContext context, String labelKey) { + return context.read().getTranslatedLabels(labelKey); + } + + static Future loginRequired(BuildContext context) { + showSnackBar(UiUtils.getTranslatedLabel(context, 'loginReqMsg'), context); + + Future.delayed(const Duration(milliseconds: 1000), () { + return Navigator.of(context).pushNamed(Routes.login, arguments: {"isFromApp": true}); //pass isFromApp to get back to specified screen + }); + return Future(() => null); + } + + static Widget showCircularProgress(bool isProgress, Color color) { + if (isProgress) { + return Center(child: CircularProgressIndicator(valueColor: AlwaysStoppedAnimation(color))); + } + return const SizedBox.shrink(); + } + + showUploadImageBottomsheet({required BuildContext context, required VoidCallback? onCamera, required VoidCallback? onGallery}) { + return showModalBottomSheet( + context: context, + shape: const RoundedRectangleBorder(side: BorderSide(color: Colors.transparent), borderRadius: BorderRadius.only(topLeft: Radius.circular(10), topRight: Radius.circular(10))), + builder: (BuildContext buildContext) { + return SafeArea( + child: Wrap( + children: [ + ListTile( + leading: SvgPictureWidget(assetName: 'gallaryIcon', assetColor: ColorFilter.mode(UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), BlendMode.srcIn)), + title: CustomTextLabel(text: 'photoLibLbl', textStyle: TextStyle(color: UiUtils.getColorScheme(context).primaryContainer, fontSize: 16, fontWeight: FontWeight.w400)), + onTap: onGallery), + ListTile( + leading: SvgPictureWidget(assetName: 'cameraIcon', assetColor: ColorFilter.mode(UiUtils.getColorScheme(context).primaryContainer.withOpacity(0.7), BlendMode.srcIn)), + title: CustomTextLabel(text: 'cameraLbl', textStyle: TextStyle(color: UiUtils.getColorScheme(context).primaryContainer, fontSize: 16, fontWeight: FontWeight.w400)), + onTap: onCamera), + ], + ), + ); + }); + } + + static String? convertToAgo(BuildContext context, DateTime input, int from) { + //from - 0 : NewsItem,details, bookmarks & sectionStyle5 , 1 : Comments, 2: Notifications, 3: SectionStyle6 + Duration diff = DateTime.now().difference(input); + if (Intl.defaultLocale != null || Intl.defaultLocale!.isEmpty) Intl.defaultLocale = 'en'; + initializeDateFormatting(); //locale according to location + final langCode = Hive.box(settingsBoxKey).get(currentLanguageCodeKey); + bool isNegative = diff.isNegative; + if (diff.inDays >= 365 && from == 1) { + double years = calculateYearsDifference(input, DateTime.now()); + return "${years.toStringAsFixed(0)} ${getTranslatedLabel(context, 'years')} ${getTranslatedLabel(context, 'ago')}"; + } else { + if (diff.inDays >= 1 || (isNegative && diff.inDays < 1)) { + if (from == 0) { + var newFormat = DateFormat("MMM dd, yyyy", langCode); + final newsDate1 = newFormat.format(input); + return newsDate1; + } else if (from == 1) { + int months = calculateMonthsDifference(input, DateTime.now()); + if ((months < 12 && diff.inDays >= 30) && !isNegative) { + return "${months.toStringAsFixed(0)} ${getTranslatedLabel(context, 'months')} ${getTranslatedLabel(context, 'ago')}"; + } else if ((diff.inHours >= 1 && diff.inHours < 24)) { + return "${getTranslatedLabel(context, 'about')} ${diff.inHours} ${getTranslatedLabel(context, 'hours')} ${input.minute} ${getTranslatedLabel(context, 'minutes')} ${getTranslatedLabel(context, 'ago')}"; + } else if ((isNegative && diff.inMinutes < 1)) { + return "${getTranslatedLabel(context, 'about')} ${input.minute} ${getTranslatedLabel(context, 'minutes')} ${getTranslatedLabel(context, 'ago')}"; + } else { + return "${diff.inDays} ${getTranslatedLabel(context, 'days')} ${getTranslatedLabel(context, 'ago')}"; + } + } else if (from == 2) { + var newFormat = DateFormat("dd MMMM yyyy", langCode); + final newsDate1 = newFormat.format(input); + return newsDate1; + } else if (from == 3) { + var newFormat = DateFormat("MMMM dd, yyyy", langCode); + final newNewsDate = newFormat.format(input); + return newNewsDate; + } + } else if (diff.inHours >= 1 || (isNegative && diff.inMinutes < 1)) { + if (input.minute == 00) { + return "${diff.inHours} ${getTranslatedLabel(context, 'hours')} ${getTranslatedLabel(context, 'ago')}"; + } else { + if (from == 2) { + return "${getTranslatedLabel(context, 'about')} ${diff.inHours} ${getTranslatedLabel(context, 'hours')} ${input.minute} ${getTranslatedLabel(context, 'minutes')} ${getTranslatedLabel(context, 'ago')}"; + } else { + return "${diff.inHours} ${getTranslatedLabel(context, 'hours')} ${input.minute} ${getTranslatedLabel(context, 'minutes')} ${getTranslatedLabel(context, 'ago')}"; + } + } + } else if (diff.inMinutes >= 1 || (isNegative && diff.inMinutes < 1)) { + return "${diff.inMinutes} ${getTranslatedLabel(context, 'minutes')} ${getTranslatedLabel(context, 'ago')}"; + } else if (diff.inSeconds >= 1) { + return "${diff.inSeconds} ${getTranslatedLabel(context, 'seconds')} ${getTranslatedLabel(context, 'ago')}"; + } else { + return getTranslatedLabel(context, 'justNow'); + } + } + return null; + } + + static int calculateMonthsDifference(DateTime startDate, DateTime endDate) { + int yearsDifference = endDate.year - startDate.year; + int monthsDifference = endDate.month - startDate.month; + + return yearsDifference * 12 + monthsDifference; + } + + static double calculateYearsDifference(DateTime startDate, DateTime endDate) { + int monthsDifference = calculateMonthsDifference(startDate, endDate); + return monthsDifference / 12; + } + + /// Pluggable date picker theme builder for consistent styling across the app + /// Usage: Theme(data: Theme.of(context).copyWith(datePickerTheme: UiUtils.buildDatePickerTheme(context))) + static DatePickerThemeData buildDatePickerTheme(BuildContext context) { + final colorScheme = getColorScheme(context); + + return DatePickerThemeData( + // Base styling + dayStyle: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + + // Header configuration + headerBackgroundColor: colorScheme.primary, + headerForegroundColor: colorScheme.onPrimary, + + // Today/Current date styling - ensures proper visibility + todayForegroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return colorScheme.onPrimary; // White text when selected + } + return colorScheme.primary; // Primary color when not selected - fixes white color issue + }), + todayBackgroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return colorScheme.primary; // Primary background when selected + } + return Colors.transparent; // No background when not selected + }), + + // Regular day styling + dayForegroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return colorScheme.onPrimary; // White text on selected days + } + if (states.contains(WidgetState.disabled)) { + return colorScheme.onSurface.withOpacity(0.38); // Disabled state + } + return colorScheme.onSurface; // Normal text color + }), + dayBackgroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return colorScheme.primary; // Primary background for selected + } + return Colors.transparent; // Transparent for unselected + }), + + // Year picker styling + yearForegroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return colorScheme.onPrimary; + } + return colorScheme.onSurface; + }), + yearBackgroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return colorScheme.primary; + } + return Colors.transparent; + }), + + // Weekday header styling + weekdayStyle: TextStyle(color: colorScheme.onSurface.withOpacity(0.6), fontWeight: FontWeight.w500, fontSize: 12), + ); + } + + /// Pluggable themed date picker wrapper that applies consistent styling + /// Fixes the currentDate white color issue across the entire app + /// Usage: UiUtils.showThemedDatePicker(context: context, ...) + static Future showThemedDatePicker({ + required BuildContext context, + required DateTime initialDate, + required DateTime firstDate, + required DateTime lastDate, + DateTime? currentDate, + String? helpText, + String? cancelText, + String? confirmText, + Locale? locale, + bool useRootNavigator = true, + }) async { + return await showDialog( + context: context, + useRootNavigator: useRootNavigator, + builder: (BuildContext context) { + return Theme( + data: Theme.of(context).copyWith( + datePickerTheme: buildDatePickerTheme(context), + ), + child: DatePickerDialog( + initialDate: initialDate, + firstDate: firstDate, + lastDate: lastDate, + currentDate: currentDate, + helpText: helpText, + cancelText: cancelText, + confirmText: confirmText, + ), + ); + }, + ); + } + + static setUIOverlayStyle({required AppTheme appTheme}) { + appTheme == AppTheme.Light + ? SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(statusBarColor: backgroundColor.withOpacity(0.8), statusBarBrightness: Brightness.light, statusBarIconBrightness: Brightness.dark)) + : SystemChrome.setSystemUIOverlayStyle( + SystemUiOverlayStyle(statusBarColor: darkSecondaryColor.withOpacity(0.8), statusBarBrightness: Brightness.dark, statusBarIconBrightness: Brightness.light)); + } + + static userLogOut({required BuildContext contxt}) { + for (int i = 0; i < AuthProviders.values.length; i++) { + if (AuthProviders.values[i].name == contxt.read().getType()) { + contxt.read().resetState(); + contxt.read().resetState(); + contxt.read().signOut(AuthProviders.values[i]).then((value) { + Navigator.of(contxt).pushNamedAndRemoveUntil(Routes.login, (route) => false); + }); + } + } + } + +//widget for User Profile Picture in Comments + static Widget setFixedSizeboxForProfilePicture({required Widget childWidget}) { + return SizedBox(height: 35, width: 35, child: childWidget); + } + + static Future isValidLocale(String locale) async { + try { + await initializeDateFormatting(locale, null); + DateFormat('EEEE', locale).format(DateTime.now()); // test formatting + return true; + } catch (e) { + return false; + } + } + + static Future setValidDefaultLocale(String? locale) async { + if (locale == null || locale.isEmpty || !(await isValidLocale(locale))) { + Intl.defaultLocale = 'en'; + } else { + Intl.defaultLocale = locale; + } + Hive.box(settingsBoxKey).put(currentLanguageCodeKey, Intl.defaultLocale); + } + + static Future checkIfValidLocale({required String langCode}) async { + await setValidDefaultLocale(langCode); //pass langCode here + } + + //Add & Edit News Screen + //roundedRectangle dashed border widget + static Widget dottedRRectBorder({required Widget childWidget}) { + return DottedBorder(options: RoundedRectDottedBorderOptions(radius: const Radius.circular(10), dashPattern: const [6, 3]), child: ClipRRect(child: Center(child: childWidget))); + } + + static Widget dropdownArrow({required BuildContext context}) { + return Align(alignment: Alignment.centerRight, child: Icon(Icons.keyboard_arrow_down_outlined, color: getColorScheme(context).primaryContainer)); + } + + static Widget setRowWithContainer({required BuildContext context, required Widget firstChild, required bool isContentTypeUpload}) { + return Container( + width: double.maxFinite, + alignment: Alignment.centerLeft, + padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 15), + decoration: BoxDecoration(color: getColorScheme(context).surface, borderRadius: BorderRadius.circular(10.0)), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + firstChild, + (isContentTypeUpload) + ? Padding( + padding: const EdgeInsetsDirectional.only(start: 20.0), + child: Align(alignment: Alignment.centerRight, child: Icon(Icons.file_upload_outlined, color: getColorScheme(context).primaryContainer))) + : dropdownArrow(context: context) + ], + ), + ); + } + + static Widget setBottomsheetContainer({required String listItem, required String compareTo, required BuildContext context, required String entryId}) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5.0), color: (compareTo != "" && compareTo == entryId) ? Theme.of(context).primaryColor : getColorScheme(context).primaryContainer.withOpacity(0.1)), + padding: const EdgeInsets.all(10.0), + alignment: Alignment.center, + child: CustomTextLabel( + text: listItem, + textStyle: Theme.of(context).textTheme.titleMedium!.copyWith(color: (compareTo == entryId) ? getColorScheme(context).secondary : getColorScheme(context).primaryContainer))); + } + + static Widget setTopPaddingParent({required Widget childWidget}) { + return Padding(padding: const EdgeInsets.only(top: 10.0), child: childWidget); + } + +//Home Screen - featured sections + static Widget setPlayButton({required BuildContext context, double heightVal = 40}) { + return Container( + alignment: Alignment.center, + height: heightVal, + width: heightVal, + decoration: BoxDecoration(shape: BoxShape.circle, color: Theme.of(context).primaryColor), + child: const Icon(Icons.play_arrow_sharp, size: 25, color: secondaryColor)); + } + + //Native Ads + static BannerAd createBannerAd({required BuildContext context}) { + return BannerAd( + adUnitId: context.read().bannerId()!, + request: const AdRequest(), + size: AdSize.mediumRectangle, + listener: BannerAdListener( + onAdLoaded: (_) => debugPrint("native ad is Loaded !!!"), + onAdFailedToLoad: (ad, err) { + ad.dispose(); + }, + onAdOpened: (Ad ad) => debugPrint('Native ad opened.'), + // Called when an ad opens an overlay that covers the screen. + onAdClosed: (Ad ad) => debugPrint('Native ad closed.'), + // Called when an ad removes an overlay that covers the screen. + onAdImpression: (Ad ad) => debugPrint('Native ad impression.'))); + } + + static Widget bannerAdsShow({required BuildContext context}) { + return AdWidget(key: UniqueKey(), ad: createBannerAd(context: context)..load()); + } + + //Interstitial Ads + static showInterstitialAds({required BuildContext context}) { + if (context.read().getInAppAdsMode() == "1") { + if (context.read().checkAdsType() == "google") { + showGoogleInterstitialAd(context); + } else { + showUnityInterstitialAds(context.read().interstitialId()!); + } + } + } + + static void shareNews( + {required BuildContext context, required String title, required String slug, required bool isBreakingNews, required bool isNews, required bool isVideo, required String videoId}) { + String contentType = ""; + if (isVideo) contentType = "video-news"; + if (isNews) contentType = "news"; + if (isBreakingNews) contentType = "breaking-news"; + + String shareLink = "$shareNavigationWebUrl/${context.read().state.languageCode}/$contentType/$slug?language_id=${context.read().state.id}&share=true"; + + String str = "$title\n\n$appName\n\n${context.read().getShareAppText()}\n\n${context.read().getAndroidAppLink()}\n"; + + bool isIOSAppLive = context.read().getiOSAppLink()?.trim().isNotEmpty ?? false; + if (isIOSAppLive) str += "\n\n${context.read().getiOSAppLink()}"; + str = shareLink + "\n\n" + str; + Share.share(str, subject: appName, sharePositionOrigin: Rect.fromLTWH(0, 0, MediaQuery.of(context).size.width, MediaQuery.of(context).size.height / 2)); + } + + //calculate time in Minutes to Read News Article + static int calculateReadingTime(String text) { + const wordsPerMinute = 200; + final wordCount = text.trim().split(' ').length; + final readTime = (wordCount / wordsPerMinute).ceil(); + return readTime; + } + + static Widget applyBoxShadow({required BuildContext context, Widget? child}) { + return Container( + decoration: BoxDecoration( + color: Theme.of(context).canvasColor, + boxShadow: [ + BoxShadow(color: Colors.black.withOpacity(0.1), offset: Offset(0, 3), blurRadius: 6), + ], + ), + child: child); + } + + static String formatDate(String date) { + DateTime dateTime = DateTime.parse(date); + final DateFormat formatter = DateFormat("MMMM d,yyyy"); + return formatter.format(dateTime); + } + + static gotoStores(BuildContext context) async { + String iosLink = context.read().getiOSAppLink() ?? ""; + String androidLink = context.read().getAndroidAppLink() ?? ""; + + if (await canLaunchUrl(Uri.parse((Platform.isIOS) ? iosLink : androidLink))) { + await launchUrl(Uri.parse((Platform.isIOS) ? iosLink : androidLink), mode: LaunchMode.externalApplication); + } + if (Navigator.of(context).canPop()) Navigator.of(context).pop(false); + } + + static String decryptKey({required geminiKey}) { + // Decode Base64 to bytes + Uint8List bytes = base64.decode(geminiKey); + + // Convert bytes to String (if it's text) + String decodedString = utf8.decode(bytes); + return decodedString; + } +} + +Widget nativeAdsShow({required BuildContext context, required int index}) { + if (context.read().getInAppAdsMode() == "1" && + context.read().checkAdsType() != null && + (context.read().getIOSAdsType() != "unity" || context.read().getAdsType() != "unity") && + index != 0 && + index % nativeAdsIndex == 0) { + return Padding( + padding: const EdgeInsets.only(bottom: 15.0), + child: Container( + padding: const EdgeInsets.all(7.0), + height: 300, + width: double.infinity, + decoration: BoxDecoration(color: Colors.white.withOpacity(0.5), borderRadius: BorderRadius.circular(10.0)), + child: context.read().checkAdsType() == "google" && (context.read().bannerId() != "") + ? UiUtils.bannerAdsShow(context: context) + : SizedBox.shrink())); + } else { + return const SizedBox.shrink(); + } +} diff --git a/news-app/lib/utils/validators.dart b/news-app/lib/utils/validators.dart new file mode 100644 index 00000000..f291df63 --- /dev/null +++ b/news-app/lib/utils/validators.dart @@ -0,0 +1,119 @@ +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:news/utils/uiUtils.dart'; + +class Validators { + //name validation check + static String? nameValidation(String value, BuildContext context) { + if (value.isEmpty) { + return UiUtils.getTranslatedLabel(context, 'nameRequired'); + } + if (value.length <= 1) { + return UiUtils.getTranslatedLabel(context, 'nameLength'); + } + return null; + } + +//email validation check + static String? emailValidation(String value, BuildContext context) { + if (value.isEmpty) { + return UiUtils.getTranslatedLabel(context, 'emailRequired'); + } else if (!RegExp(r"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)" + r"*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+" + r"[a-z0-9](?:[a-z0-9-]*[a-z0-9])?") + .hasMatch(value)) { + return UiUtils.getTranslatedLabel(context, 'emailValid'); + } else { + return null; + } + } + +//password validation check + static String? passValidation(String value, BuildContext context) { + if (value.isEmpty) { + return UiUtils.getTranslatedLabel(context, 'pwdRequired'); + } else if (value.length <= 5) { + return UiUtils.getTranslatedLabel(context, 'pwdLength'); + } else { + return null; + } + } + + static String? mobValidation(String value, BuildContext context) { + if (value.isEmpty) { + return UiUtils.getTranslatedLabel(context, 'mblRequired'); + } + if (value.length < 9 || value.length > 16) { + return UiUtils.getTranslatedLabel(context, 'mblValid'); + } + return null; + } + + static String? titleValidation(String value, BuildContext context) { + if (value.isEmpty) { + return UiUtils.getTranslatedLabel(context, 'newsTitleReqLbl'); + } else if (value.length < 2) { + return UiUtils.getTranslatedLabel(context, 'plzAddValidTitleLbl'); + } + return null; + } + + static String? youtubeUrlValidation(String value, BuildContext context) { + if (value.isEmpty) { + return UiUtils.getTranslatedLabel(context, 'urlReqLbl'); + } else { + bool isValidURL = RegExp(r'^(((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?)').hasMatch(value); + if (!isValidURL) return UiUtils.getTranslatedLabel(context, 'plzValidUrlLbl'); + } + + return null; + } + + static String? urlValidation(String value, BuildContext context) { + bool? test; + if (value.isEmpty) { + return UiUtils.getTranslatedLabel(context, 'urlReqLbl'); + } else { + validUrl(value).then((result) { + test = result; + if (test!) { + return UiUtils.getTranslatedLabel(context, 'plzValidUrlLbl'); + } + }); + } + + return null; + } + + static Future validUrl(String value) async { + await Dio().head(value).then((value) { + return (value.statusCode == 200) ? false : true; + }); + return false; + } + + //name validation check + static String? emptyFieldValidation(String value, String hintText, BuildContext context) { + if (value.isEmpty) { + switch (hintText) { + case 'metaTitleLbl': + return UiUtils.getTranslatedLabel(context, 'metaTitleRequired'); + case 'metaDescriptionLbl': + return UiUtils.getTranslatedLabel(context, 'metaDescriptionRequired'); + case 'metaKeywordLbl': + return UiUtils.getTranslatedLabel(context, 'metaKeywordRequired'); + } + } + return null; + } + + static String? slugValidation(String value, BuildContext context) { + if (value.isEmpty) { + return UiUtils.getTranslatedLabel(context, 'slugRequired'); + } else if (!RegExp("^[a-z0-9]+(?:-[a-z0-9]+)*").hasMatch(value)) { + return UiUtils.getTranslatedLabel(context, 'slugValid'); + } else { + return null; + } + } +} diff --git a/news-app/pubspec.lock b/news-app/pubspec.lock new file mode 100644 index 00000000..319c8447 --- /dev/null +++ b/news-app/pubspec.lock @@ -0,0 +1,1698 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: "8a1f5f3020ef2a74fb93f7ab3ef127a8feea33a7a2276279113660784ee7516a" + url: "https://pub.dev" + source: hosted + version: "1.3.64" + archive: + dependency: transitive + description: + name: archive + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + url: "https://pub.dev" + source: hosted + version: "4.0.7" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + audio_session: + dependency: transitive + description: + name: audio_session + sha256: "8f96a7fecbb718cb093070f868b4cdcb8a9b1053dce342ff8ab2fde10eb9afb7" + url: "https://pub.dev" + source: hosted + version: "0.2.2" + bloc: + dependency: transitive + description: + name: bloc + sha256: a2cebb899f91d36eeeaa55c7b20b5915db5a9df1b8fd4a3c9c825e22e474537d + url: "https://pub.dev" + source: hosted + version: "9.1.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + cached_network_image: + dependency: transitive + description: + name: cached_network_image + sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" + url: "https://pub.dev" + source: hosted + version: "4.1.1" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a + url: "https://pub.dev" + source: hosted + version: "1.4.0" + chewie: + dependency: transitive + description: + name: chewie + sha256: "44bcfc5f0dfd1de290c87c9d86a61308b3282a70b63435d5557cfd60f54a69ca" + url: "https://pub.dev" + source: hosted + version: "1.13.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + connectivity_plus: + dependency: "direct main" + description: + name: connectivity_plus + sha256: "33bae12a398f841c6cda09d1064212957265869104c478e5ad51e2fb26c3973c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + connectivity_plus_platform_interface: + dependency: transitive + description: + name: connectivity_plus_platform_interface + sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + country_code_picker: + dependency: "direct main" + description: + name: country_code_picker + sha256: f0411f4833b6f98e8b7215f4fa3813bcc88e50f13925f70a170dbd36e3e447f5 + url: "https://pub.dev" + source: hosted + version: "3.4.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "701dcfc06da0882883a2657c445103380e53e647060ad8d9dfb710c100996608" + url: "https://pub.dev" + source: hosted + version: "0.3.5+1" + crypto: + dependency: "direct main" + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + cupertino_icons: + dependency: transitive + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + dbus: + dependency: transitive + description: + name: dbus + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + url: "https://pub.dev" + source: hosted + version: "0.7.11" + diacritic: + dependency: transitive + description: + name: diacritic + sha256: "12981945ec38931748836cd76f2b38773118d0baef3c68404bdfde9566147876" + url: "https://pub.dev" + source: hosted + version: "0.1.6" + dio: + dependency: "direct main" + description: + name: dio + sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 + url: "https://pub.dev" + source: hosted + version: "5.9.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + dotted_border: + dependency: "direct main" + description: + name: dotted_border + sha256: "99b091ec6891ba0c5331fdc2b502993c7c108f898995739a73c6845d71dad70c" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + url: "https://pub.dev" + source: hosted + version: "2.0.7" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: ab13ae8ef5580a411c458d6207b6774a6c237d77ac37011b13994879f68a8810 + url: "https://pub.dev" + source: hosted + version: "8.3.7" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" + url: "https://pub.dev" + source: hosted + version: "0.9.4" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" + url: "https://pub.dev" + source: hosted + version: "0.9.5" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" + url: "https://pub.dev" + source: hosted + version: "2.7.0" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" + url: "https://pub.dev" + source: hosted + version: "0.9.3+5" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + sha256: e54fb3ba57de041d832574126a37726eedf0f57400869f1942b0ca8ce4a6e209 + url: "https://pub.dev" + source: hosted + version: "6.1.2" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + sha256: "421f95dc553cb283ed9d4d140e719800c0331d49ed37b962e513c9d1d61b090b" + url: "https://pub.dev" + source: hosted + version: "8.1.4" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + sha256: a064ffee202f7d42d62e2c01775899d4ffcb83c602af07632f206acd46a0964e + url: "https://pub.dev" + source: hosted + version: "6.1.0" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "1f2dfd9f535d81f8b06d7a50ecda6eac1e6922191ed42e09ca2c84bd2288927c" + url: "https://pub.dev" + source: hosted + version: "4.2.1" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: cccb4f572325dc14904c02fcc7db6323ad62ba02536833dddb5c02cac7341c64 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: ff18fabb0ad0ed3595d2f2c85007ecc794aadecdff5b3bb1460b7ee47cded398 + url: "https://pub.dev" + source: hosted + version: "3.3.0" + firebase_messaging: + dependency: "direct main" + description: + name: firebase_messaging + sha256: "22086f857d2340f5d973776cfd542d3fb30cf98e1c643c3aa4a7520bb12745bb" + url: "https://pub.dev" + source: hosted + version: "16.0.4" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + sha256: a59920cbf2eb7c83d34a5f354331210ffec116b216dc72d864d8b8eb983ca398 + url: "https://pub.dev" + source: hosted + version: "4.7.4" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + sha256: "1183e40e6fd2a279a628951cc3b639fcf5ffe7589902632db645011eb70ebefb" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flex_color_picker: + dependency: transitive + description: + name: flex_color_picker + sha256: a0979dd61f21b634717b98eb4ceaed2bfe009fe020ce8597aaf164b9eeb57aaa + url: "https://pub.dev" + source: hosted + version: "3.8.0" + flex_seed_scheme: + dependency: transitive + description: + name: flex_seed_scheme + sha256: a3183753bbcfc3af106224bff3ab3e1844b73f58062136b7499919f49f3667e7 + url: "https://pub.dev" + source: hosted + version: "4.0.1" + flick_video_player: + dependency: "direct main" + description: + name: flick_video_player + sha256: f011cc28c6e4932485ac3a9d09ccacd25d9d430c99090480ccad2f5ebca9366e + url: "https://pub.dev" + source: hosted + version: "0.9.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: cf51747952201a455a1c840f8171d273be009b932c75093020f9af64f2123e38 + url: "https://pub.dev" + source: hosted + version: "9.1.1" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + flutter_inappwebview: + dependency: "direct main" + description: + name: flutter_inappwebview + sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5" + url: "https://pub.dev" + source: hosted + version: "6.1.5" + flutter_inappwebview_android: + dependency: transitive + description: + name: flutter_inappwebview_android + sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba" + url: "https://pub.dev" + source: hosted + version: "1.1.3" + flutter_inappwebview_internal_annotations: + dependency: transitive + description: + name: flutter_inappwebview_internal_annotations + sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + flutter_inappwebview_ios: + dependency: transitive + description: + name: flutter_inappwebview_ios + sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_macos: + dependency: transitive + description: + name: flutter_inappwebview_macos + sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_platform_interface: + dependency: transitive + description: + name: flutter_inappwebview_platform_interface + sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500 + url: "https://pub.dev" + source: hosted + version: "1.3.0+1" + flutter_inappwebview_web: + dependency: transitive + description: + name: flutter_inappwebview_web + sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_windows: + dependency: transitive + description: + name: flutter_inappwebview_windows + sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + flutter_keyboard_visibility: + dependency: transitive + description: + name: flutter_keyboard_visibility + sha256: "98664be7be0e3ffca00de50f7f6a287ab62c763fc8c762e0a21584584a3ff4f8" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_keyboard_visibility_linux: + dependency: transitive + description: + name: flutter_keyboard_visibility_linux + sha256: "6fba7cd9bb033b6ddd8c2beb4c99ad02d728f1e6e6d9b9446667398b2ac39f08" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter_keyboard_visibility_macos: + dependency: transitive + description: + name: flutter_keyboard_visibility_macos + sha256: c5c49b16fff453dfdafdc16f26bdd8fb8d55812a1d50b0ce25fc8d9f2e53d086 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter_keyboard_visibility_platform_interface: + dependency: transitive + description: + name: flutter_keyboard_visibility_platform_interface + sha256: e43a89845873f7be10cb3884345ceb9aebf00a659f479d1c8f4293fcb37022a4 + url: "https://pub.dev" + source: hosted + version: "2.0.0" + flutter_keyboard_visibility_web: + dependency: transitive + description: + name: flutter_keyboard_visibility_web + sha256: d3771a2e752880c79203f8d80658401d0c998e4183edca05a149f5098ce6e3d1 + url: "https://pub.dev" + source: hosted + version: "2.0.0" + flutter_keyboard_visibility_windows: + dependency: transitive + description: + name: flutter_keyboard_visibility_windows + sha256: fc4b0f0b6be9b93ae527f3d527fb56ee2d918cd88bbca438c478af7bcfd0ef73 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: "19ffb0a8bb7407875555e5e98d7343a633bb73707bae6c6a5f37c90014077875" + url: "https://pub.dev" + source: hosted + version: "19.5.0" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: e3c277b2daab8e36ac5a6820536668d07e83851aeeb79c446e525a70710770a5 + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "277d25d960c15674ce78ca97f57d0bae2ee401c844b6ac80fcd972a9c99d09fe" + url: "https://pub.dev" + source: hosted + version: "9.1.0" + flutter_local_notifications_windows: + dependency: transitive + description: + name: flutter_local_notifications_windows + sha256: "8d658f0d367c48bd420e7cf2d26655e2d1130147bca1eea917e576ca76668aaf" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + flutter_login_facebook: + dependency: "direct main" + description: + name: flutter_login_facebook + sha256: a20cbd9bd50b7399609a4f22dbc2fc2ab792c4a2c17d27b98c4dfa960a7505c5 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1 + url: "https://pub.dev" + source: hosted + version: "2.0.33" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_tts: + dependency: "direct main" + description: + name: flutter_tts + sha256: bdf2fc4483e74450dc9fc6fe6a9b6a5663e108d4d0dad3324a22c8e26bf48af4 + url: "https://pub.dev" + source: hosted + version: "4.2.3" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_widget_from_html: + dependency: "direct main" + description: + name: flutter_widget_from_html + sha256: "7f1daefcd3009c43c7e7fb37501e6bb752d79aa7bfad0085fb0444da14e89bd0" + url: "https://pub.dev" + source: hosted + version: "0.17.1" + flutter_widget_from_html_core: + dependency: transitive + description: + name: flutter_widget_from_html_core + sha256: "1120ee6ed3509ceff2d55aa6c6cbc7b6b1291434422de2411b5a59364dd6ff03" + url: "https://pub.dev" + source: hosted + version: "0.17.0" + fwfh_cached_network_image: + dependency: transitive + description: + name: fwfh_cached_network_image + sha256: "484cb5f8047f02cfac0654fca5832bfa91bb715fd7fc651c04eb7454187c4af8" + url: "https://pub.dev" + source: hosted + version: "0.16.1" + fwfh_chewie: + dependency: transitive + description: + name: fwfh_chewie + sha256: ae74fc26798b0e74f3983f7b851e74c63b9eeb2d3015ecd4b829096b2c3f8818 + url: "https://pub.dev" + source: hosted + version: "0.16.1" + fwfh_just_audio: + dependency: transitive + description: + name: fwfh_just_audio + sha256: dfd622a0dfe049ac647423a2a8afa7f057d9b2b93d92710b624e3d370b1ac69a + url: "https://pub.dev" + source: hosted + version: "0.17.0" + fwfh_svg: + dependency: transitive + description: + name: fwfh_svg + sha256: "2e6bb241179eeeb1a7941e05c8c923b05d332d36a9085233e7bf110ea7deb915" + url: "https://pub.dev" + source: hosted + version: "0.16.1" + fwfh_url_launcher: + dependency: transitive + description: + name: fwfh_url_launcher + sha256: c38aa8fb373fda3a89b951fa260b539f623f6edb45eee7874cb8b492471af881 + url: "https://pub.dev" + source: hosted + version: "0.16.1" + fwfh_webview: + dependency: transitive + description: + name: fwfh_webview + sha256: f71b0aa16e15d82f3c017f33560201ff5ae04e91e970cab5d12d3bcf970b870c + url: "https://pub.dev" + source: hosted + version: "0.15.6" + google_identity_services_web: + dependency: transitive + description: + name: google_identity_services_web + sha256: "5d187c46dc59e02646e10fe82665fc3884a9b71bc1c90c2b8b749316d33ee454" + url: "https://pub.dev" + source: hosted + version: "0.3.3+1" + google_mobile_ads: + dependency: "direct main" + description: + name: google_mobile_ads + sha256: a4f59019f2c32769fb6c60ed8aa321e9c21a36297e2c4f23452b3e779a3e7a26 + url: "https://pub.dev" + source: hosted + version: "6.0.0" + google_sign_in: + dependency: "direct main" + description: + name: google_sign_in + sha256: d0a2c3bcb06e607bb11e4daca48bd4b6120f0bbc4015ccebbe757d24ea60ed2a + url: "https://pub.dev" + source: hosted + version: "6.3.0" + google_sign_in_android: + dependency: transitive + description: + name: google_sign_in_android + sha256: d5e23c56a4b84b6427552f1cf3f98f716db3b1d1a647f16b96dbb5b93afa2805 + url: "https://pub.dev" + source: hosted + version: "6.2.1" + google_sign_in_ios: + dependency: transitive + description: + name: google_sign_in_ios + sha256: "102005f498ce18442e7158f6791033bbc15ad2dcc0afa4cf4752e2722a516c96" + url: "https://pub.dev" + source: hosted + version: "5.9.0" + google_sign_in_platform_interface: + dependency: transitive + description: + name: google_sign_in_platform_interface + sha256: "5f6f79cf139c197261adb6ac024577518ae48fdff8e53205c5373b5f6430a8aa" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + google_sign_in_web: + dependency: transitive + description: + name: google_sign_in_web + sha256: "460547beb4962b7623ac0fb8122d6b8268c951cf0b646dd150d60498430e4ded" + url: "https://pub.dev" + source: hosted + version: "0.12.4+4" + hive: + dependency: "direct main" + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + hive_flutter: + dependency: "direct main" + description: + name: hive_flutter + sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc + url: "https://pub.dev" + source: hosted + version: "1.1.0" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" + html_editor_plus: + dependency: "direct main" + description: + name: html_editor_plus + sha256: d6d00275951b2a8b22b0e5128386514077420a673174cffcb6246c51e7bd146f + url: "https://pub.dev" + source: hosted + version: "0.0.5" + http: + dependency: "direct main" + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "5e9bf126c37c117cf8094215373c6d561117a3cfb50ebc5add1a61dc6e224677" + url: "https://pub.dev" + source: hosted + version: "0.8.13+10" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "956c16a42c0c708f914021666ffcd8265dde36e673c9fa68c81f7d085d9774ad" + url: "https://pub.dev" + source: hosted + version: "0.8.13+3" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" + url: "https://pub.dev" + source: hosted + version: "0.2.2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" + url: "https://pub.dev" + source: hosted + version: "0.2.2+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" + url: "https://pub.dev" + source: hosted + version: "2.11.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae + url: "https://pub.dev" + source: hosted + version: "0.2.2" + in_app_review: + dependency: "direct main" + description: + name: in_app_review + sha256: ab26ac54dbd802896af78c670b265eaeab7ecddd6af4d0751e9604b60574817f + url: "https://pub.dev" + source: hosted + version: "2.0.11" + in_app_review_platform_interface: + dependency: transitive + description: + name: in_app_review_platform_interface + sha256: fed2c755f2125caa9ae10495a3c163aa7fab5af3585a9c62ef4a6920c5b45f10 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + infinite_listview: + dependency: transitive + description: + name: infinite_listview + sha256: f6062c1720eb59be553dfa6b89813d3e8dd2f054538445aaa5edaddfa5195ce6 + url: "https://pub.dev" + source: hosted + version: "1.1.0" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + just_audio: + dependency: transitive + description: + name: just_audio + sha256: "9694e4734f515f2a052493d1d7e0d6de219ee0427c7c29492e246ff32a219908" + url: "https://pub.dev" + source: hosted + version: "0.10.5" + just_audio_platform_interface: + dependency: transitive + description: + name: just_audio_platform_interface + sha256: "2532c8d6702528824445921c5ff10548b518b13f808c2e34c2fd54793b999a6a" + url: "https://pub.dev" + source: hosted + version: "4.6.0" + just_audio_web: + dependency: transitive + description: + name: just_audio_web + sha256: "6ba8a2a7e87d57d32f0f7b42856ade3d6a9fbe0f1a11fabae0a4f00bb73f0663" + url: "https://pub.dev" + source: hosted + version: "0.4.16" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + list_ext: + dependency: transitive + description: + name: list_ext + sha256: a38869a6911830f39ace7534972d01e573e17917955b26215e54d5797eb8d5cd + url: "https://pub.dev" + source: hosted + version: "1.0.6" + location: + dependency: "direct main" + description: + name: location + sha256: b080053c181c7d152c43dd576eec6436c40e25f326933051c330da563ddd5333 + url: "https://pub.dev" + source: hosted + version: "8.0.1" + location_platform_interface: + dependency: transitive + description: + name: location_platform_interface + sha256: ca8700bb3f6b1e8b2afbd86bd78b2280d116c613ca7bfa1d4d7b64eba357d749 + url: "https://pub.dev" + source: hosted + version: "6.0.1" + location_web: + dependency: transitive + description: + name: location_web + sha256: b8e3add5efe0d65c5e692b7a135d80a4015c580d3ea646fa71973e97668dd868 + url: "https://pub.dev" + source: hosted + version: "6.0.1" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + lottie: + dependency: "direct main" + description: + name: lottie + sha256: "8ae0be46dbd9e19641791dc12ee480d34e1fd3f84c749adc05f3ad9342b71b95" + url: "https://pub.dev" + source: hosted + version: "3.3.2" + marqueer: + dependency: "direct main" + description: + name: marqueer + sha256: "86b2a2e34b3bbd80be0c6fd4c60ebe6cc737f49fab79f862faa307f1cb18605c" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + modal_bottom_sheet: + dependency: "direct overridden" + description: + name: modal_bottom_sheet + sha256: eac66ef8cb0461bf069a38c5eb0fa728cee525a531a8304bd3f7b2185407c67e + url: "https://pub.dev" + source: hosted + version: "3.0.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + nm: + dependency: transitive + description: + name: nm + sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + numberpicker: + dependency: transitive + description: + name: numberpicker + sha256: "4c129154944b0f6b133e693f8749c3f8bfb67c4d07ef9dcab48b595c22d1f156" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d + url: "https://pub.dev" + source: hosted + version: "9.0.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e + url: "https://pub.dev" + source: hosted + version: "2.2.22" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4" + url: "https://pub.dev" + source: hosted + version: "2.5.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + percent_indicator: + dependency: "direct main" + description: + name: percent_indicator + sha256: "157d29133bbc6ecb11f923d36e7960a96a3f28837549a20b65e5135729f0f9fd" + url: "https://pub.dev" + source: hosted + version: "4.2.5" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + url: "https://pub.dev" + source: hosted + version: "7.0.1" + photo_view: + dependency: "direct main" + description: + name: photo_view + sha256: "1fc3d970a91295fbd1364296575f854c9863f225505c28c46e0a03e48960c75e" + url: "https://pub.dev" + source: hosted + version: "0.15.0" + pin_input_text_field: + dependency: transitive + description: + name: pin_input_text_field + sha256: f45683032283d30b670ec343781660655e3e1953438b281a0bc6e2d358486236 + url: "https://pub.dev" + source: hosted + version: "4.5.2" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pointer_interceptor: + dependency: transitive + description: + name: pointer_interceptor + sha256: "57210410680379aea8b1b7ed6ae0c3ad349bfd56fe845b8ea934a53344b9d523" + url: "https://pub.dev" + source: hosted + version: "0.10.1+2" + pointer_interceptor_ios: + dependency: transitive + description: + name: pointer_interceptor_ios + sha256: "03c5fa5896080963ab4917eeffda8d28c90f22863a496fb5ba13bc10943e40e4" + url: "https://pub.dev" + source: hosted + version: "0.10.1+1" + pointer_interceptor_platform_interface: + dependency: transitive + description: + name: pointer_interceptor_platform_interface + sha256: "0597b0560e14354baeb23f8375cd612e8bd4841bf8306ecb71fcd0bb78552506" + url: "https://pub.dev" + source: hosted + version: "0.10.0+1" + pointer_interceptor_web: + dependency: transitive + description: + name: pointer_interceptor_web + sha256: "460b600e71de6fcea2b3d5f662c92293c049c4319e27f0829310e5a953b3ee2a" + url: "https://pub.dev" + source: hosted + version: "0.10.3" + posix: + dependency: transitive + description: + name: posix + sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" + url: "https://pub.dev" + source: hosted + version: "6.0.3" + provider: + dependency: transitive + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" + quiver: + dependency: transitive + description: + name: quiver + sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: "14c8860d4de93d3a7e53af51bff479598c4e999605290756bbbe45cf65b37840" + url: "https://pub.dev" + source: hosted + version: "12.0.1" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + shimmer: + dependency: "direct main" + description: + name: shimmer + sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + sign_in_with_apple: + dependency: "direct main" + description: + name: sign_in_with_apple + sha256: "8bd875c8e8748272749eb6d25b896f768e7e9d60988446d543fe85a37a2392b8" + url: "https://pub.dev" + source: hosted + version: "7.0.1" + sign_in_with_apple_platform_interface: + dependency: transitive + description: + name: sign_in_with_apple_platform_interface + sha256: "981bca52cf3bb9c3ad7ef44aace2d543e5c468bb713fd8dda4275ff76dfa6659" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + sign_in_with_apple_web: + dependency: transitive + description: + name: sign_in_with_apple_web + sha256: f316400827f52cafcf50d00e1a2e8a0abc534ca1264e856a81c5f06bd5b10fed + url: "https://pub.dev" + source: hosted + version: "3.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + sms_autofill: + dependency: "direct main" + description: + name: sms_autofill + sha256: c65836abe9c1f62ce411bb78d5546a09ece4297558070b1bd871db1db283aaf9 + url: "https://pub.dev" + source: hosted + version: "2.4.1" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88 + url: "https://pub.dev" + source: hosted + version: "2.4.2+2" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + url: "https://pub.dev" + source: hosted + version: "3.4.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + url: "https://pub.dev" + source: hosted + version: "0.7.7" + timezone: + dependency: transitive + description: + name: timezone + sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 + url: "https://pub.dev" + source: hosted + version: "0.10.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + unity_ads_plugin: + dependency: "direct main" + description: + name: unity_ads_plugin + sha256: f35ddd27a3323a462a2ccf20721da99a0b13a4335f11a0cdadbb03db1d17a57b + url: "https://pub.dev" + source: hosted + version: "0.3.28" + universal_html: + dependency: transitive + description: + name: universal_html + sha256: c0bcae5c733c60f26c7dfc88b10b0fd27cbcc45cb7492311cdaa6067e21c9cd4 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2 + url: "https://pub.dev" + source: hosted + version: "2.3.1" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" + url: "https://pub.dev" + source: hosted + version: "6.3.28" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad + url: "https://pub.dev" + source: hosted + version: "6.3.6" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.dev" + source: hosted + version: "3.2.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + uuid: + dependency: transitive + description: + name: uuid + sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 + url: "https://pub.dev" + source: hosted + version: "4.5.2" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 + url: "https://pub.dev" + source: hosted + version: "1.1.19" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" + url: "https://pub.dev" + source: hosted + version: "1.1.13" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc + url: "https://pub.dev" + source: hosted + version: "1.1.19" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + video_player: + dependency: "direct main" + description: + name: video_player + sha256: "096bc28ce10d131be80dfb00c223024eb0fba301315a406728ab43dd99c45bdf" + url: "https://pub.dev" + source: hosted + version: "2.10.1" + video_player_android: + dependency: transitive + description: + name: video_player_android + sha256: d74b66f283afff135d5be0ceccca2ca74dff7df1e9b1eaca6bd4699875d3ae60 + url: "https://pub.dev" + source: hosted + version: "2.8.22" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + sha256: e4d33b79a064498c6eb3a6a492b6a5012573d4943c28d566caf1a6c0840fe78d + url: "https://pub.dev" + source: hosted + version: "2.8.8" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + sha256: "57c5d73173f76d801129d0531c2774052c5a7c11ccb962f1830630decd9f24ec" + url: "https://pub.dev" + source: hosted + version: "6.6.0" + video_player_web: + dependency: transitive + description: + name: video_player_web + sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + visibility_detector: + dependency: transitive + description: + name: visibility_detector + sha256: dd5cc11e13494f432d15939c3aa8ae76844c42b723398643ce9addb88a5ed420 + url: "https://pub.dev" + source: hosted + version: "0.4.0+2" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + url: "https://pub.dev" + source: hosted + version: "15.0.2" + wakelock_plus: + dependency: transitive + description: + name: wakelock_plus + sha256: "9296d40c9adbedaba95d1e704f4e0b434be446e2792948d0e4aa977048104228" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + wakelock_plus_platform_interface: + dependency: transitive + description: + name: wakelock_plus_platform_interface + sha256: "036deb14cd62f558ca3b73006d52ce049fabcdcb2eddfe0bf0fe4e8a943b5cf2" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + webview_flutter: + dependency: transitive + description: + name: webview_flutter + sha256: c3e4fe614b1c814950ad07186007eff2f2e5dd2935eba7b9a9a1af8e5885f1ba + url: "https://pub.dev" + source: hosted + version: "4.13.0" + webview_flutter_android: + dependency: "direct overridden" + description: + name: webview_flutter_android + sha256: eeeb3fcd5f0ff9f8446c9f4bbc18a99b809e40297528a3395597d03aafb9f510 + url: "https://pub.dev" + source: hosted + version: "4.10.11" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: "63d26ee3aca7256a83ccb576a50272edd7cfc80573a4305caa98985feb493ee0" + url: "https://pub.dev" + source: hosted + version: "2.14.0" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: e49f378ed066efb13fc36186bbe0bd2425630d4ea0dbc71a18fdd0e4d8ed8ebc + url: "https://pub.dev" + source: hosted + version: "3.23.5" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: "direct main" + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" + xml2json: + dependency: "direct main" + description: + name: xml2json + sha256: "8a7ae63b76676f083d81287b61f03222952609c0507893bc62e60a4a588a9702" + url: "https://pub.dev" + source: hosted + version: "6.2.7" + youtube_parser: + dependency: "direct main" + description: + name: youtube_parser + sha256: "52ac55275c685ccb51099d5fcf2ab601469ab1667163b47e7f7000ddd226816f" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + youtube_player_flutter: + dependency: "direct main" + description: + name: youtube_player_flutter + sha256: e64eeebaa5f7dc1d55d103cc9abf05f87d8013bae0d3b6a11aad5d33a2f7f5b4 + url: "https://pub.dev" + source: hosted + version: "9.1.3" +sdks: + dart: ">=3.10.0 <4.0.0" + flutter: ">=3.38.0" diff --git a/news-app/pubspec.yaml b/news-app/pubspec.yaml new file mode 100644 index 00000000..38f1b85a --- /dev/null +++ b/news-app/pubspec.yaml @@ -0,0 +1,93 @@ +name: news +# news +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 + +version: 1.0.0+1 + +environment: + sdk: ">=3.4.3 <4.0.0" + +dependencies: + flutter: + sdk: flutter + + flutter_svg: ^2.0.7 + connectivity_plus: ^7.0.0 + country_code_picker: ^3.0.0 + sms_autofill: ^2.0.1 + location: ^8.0.0 + intl: ^0.20.1 + firebase_core: ^4.1.0 + firebase_messaging: ^16.0.1 + firebase_auth: ^6.0.2 + google_sign_in: ^6.2.1 + photo_view: ^0.15.0 + url_launcher: ^6.0.10 + shimmer: ^3.0.0 + share_plus: ^12.0.1 + flutter_tts: ^4.0.2 + image_picker: ^1.0.1 + file_picker: ^8.0.6 + video_player: ^2.9.1 + flick_video_player: ^0.9.0 + youtube_player_flutter: ^9.0.1 + youtube_parser: ^2.1.4 + in_app_review: ^2.0.3 + flutter_local_notifications: ^19.4.1 + path_provider: ^2.1.5 + flutter_login_facebook: ^2.0.0 + sign_in_with_apple: ^7.0.1 + crypto: ^3.0.1 + percent_indicator: ^4.0.0 + google_mobile_ads: ^6.0.0 + flutter_widget_from_html: ^0.17.1 + unity_ads_plugin: ^0.3.15 + flutter_inappwebview: ^6.1.5 + flutter_bloc: ^9.0.0 + dio: ^5.0.1 + hive: ^2.2.3 + hive_flutter: ^1.1.0 + equatable: ^2.0.5 + lottie: ^3.1.0 + dotted_border: ^3.1.0 + marqueer: ^2.0.1 + + xml: ^6.5.0 + http: ^1.2.2 + xml2json: ^6.2.5 + + package_info_plus: ^9.0.0 + html_editor_plus: ^0.0.5 + +dependency_overrides: + modal_bottom_sheet: ^3.0.0-pre + webview_flutter_android: ^4.3.3 + +dev_dependencies: + flutter_test: + sdk: flutter + + +flutter: + uses-material-design: true + assets: + - assets/images/ + - assets/images/svgImage/ + - assets/languages/ + - assets/animations/ + + fonts: + - family: Roboto + fonts: + - asset: assets/font/Roboto-Regular.ttf + weight: 400 + - asset: assets/font/Roboto-Light.ttf + weight: 300 + - asset: assets/font/Roboto-Medium.ttf + weight: 500 + - asset: assets/font/Roboto-Bold.ttf + weight: 700 \ No newline at end of file diff --git a/news-app/test/widget_test.dart b/news-app/test/widget_test.dart new file mode 100644 index 00000000..1777be42 --- /dev/null +++ b/news-app/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:news/app/app.dart'; + + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +}