diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 34e5ed5..08c9397 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,7 @@ -cache: - paths: +cache: + paths: - build/web + stages: - build - push @@ -11,9 +12,10 @@ build: image: cirrusci/flutter:latest script: - flutter clean - - flutter build web --web-renderer canvaskit + - flutter build web --web-renderer html only: - test + - dev push: stage: push @@ -26,8 +28,9 @@ push: - docker push registry.gitlab.com/didvan/didvan-app/api:latest only: - test + - dev -deploy: +deploy-test: stage: deploy image: python:3.10.2 variables: @@ -35,6 +38,18 @@ deploy: script: - pip install fandogh_cli --upgrade - fandogh login --username=$FAN_USR --password=$FAN_PASS - - fandogh service apply -f ./deployment/config.yaml -p SEC_NAME + - fandogh service apply -f ./deployment/test.yaml -p SEC_NAME only: - test + +deploy-dev: + stage: deploy + image: python:3.10.2 + variables: + COLLECT_ERROR: 1 + script: + - pip install fandogh_cli --upgrade + - fandogh login --username=$FAN_USR --password=$FAN_PASS + - fandogh service apply -f ./deployment/dev.yaml -p SEC_NAME + only: + - dev \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index 21ce8ff..293ad3a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -22,7 +22,6 @@ if (flutterVersionName == null) { } apply plugin: 'com.android.application' -apply plugin: 'com.google.gms.google-services' apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" @@ -66,7 +65,5 @@ flutter { } dependencies { - implementation platform('com.google.firebase:firebase-bom:29.0.4') - implementation 'com.google.firebase:firebase-analytics' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } diff --git a/android/app/google-services.json b/android/app/google-services.json deleted file mode 100644 index 5cb370e..0000000 --- a/android/app/google-services.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "project_info": { - "project_number": "935017686266", - "project_id": "didvan-9b7da", - "storage_bucket": "didvan-9b7da.appspot.com" - }, - "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:935017686266:android:f9cbc9aba8e3d65ed2d543", - "android_client_info": { - "package_name": "com.didvan.didvanapp" - } - }, - "oauth_client": [ - { - "client_id": "935017686266-lebnol7rb05oi9h0mripb41c892d2gij.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyBp-UHjWeM0H0UHtX5yguFKG-riMzvvCzw" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [ - { - "client_id": "935017686266-lebnol7rb05oi9h0mripb41c892d2gij.apps.googleusercontent.com", - "client_type": 3 - } - ] - } - } - } - ], - "configuration_version": "1" -} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index aadf776..34d2e7f 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -5,8 +5,10 @@ + android:icon="@mipmap/ic_launcher" + android:usesCleartextTraffic="true"> + + - diff --git a/android/build.gradle b/android/build.gradle index 1607692..09fbd64 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,12 +1,11 @@ buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.6.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.google.gms:google-services:4.3.10' classpath 'com.android.tools.build:gradle:4.1.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index bc6a58a..cc5527d 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/deployment/dev.yaml b/deployment/dev.yaml new file mode 100644 index 0000000..6738cdd --- /dev/null +++ b/deployment/dev.yaml @@ -0,0 +1,14 @@ +kind: ExternalService +name: app-dev +spec: + allow_http: false + disable_default_domains: true + image: registry.gitlab.com/didvan/didvan-app/api:latest + image_pull_policy: Always + image_pull_secret: $SEC_NAME + path: / + replicas: 1 + resources: + memory: 100Mi + domains: + - name: dev.didvan.app \ No newline at end of file diff --git a/deployment/config.yaml b/deployment/test.yaml similarity index 100% rename from deployment/config.yaml rename to deployment/test.yaml diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index 592ceee..ec97fc6 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index 592ceee..c4855bf 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..252d9ec --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '9.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) + end +end diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 0000000..402e2e1 --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,89 @@ +PODS: + - audio_session (0.0.1): + - Flutter + - Flutter (1.0.0) + - flutter_secure_storage (3.3.1): + - Flutter + - flutter_vibrate (0.0.1): + - Flutter + - FMDB (2.7.5): + - FMDB/standard (= 2.7.5) + - FMDB/standard (2.7.5) + - image_cropper (0.0.4): + - Flutter + - TOCropViewController (~> 2.6.1) + - image_picker (0.0.1): + - Flutter + - just_audio (0.0.1): + - Flutter + - path_provider_ios (0.0.1): + - Flutter + - record (0.0.1): + - Flutter + - sqflite (0.0.2): + - Flutter + - FMDB (>= 2.7.5) + - TOCropViewController (2.6.1) + - url_launcher_ios (0.0.1): + - Flutter + +DEPENDENCIES: + - audio_session (from `.symlinks/plugins/audio_session/ios`) + - Flutter (from `Flutter`) + - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) + - flutter_vibrate (from `.symlinks/plugins/flutter_vibrate/ios`) + - image_cropper (from `.symlinks/plugins/image_cropper/ios`) + - image_picker (from `.symlinks/plugins/image_picker/ios`) + - just_audio (from `.symlinks/plugins/just_audio/ios`) + - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) + - record (from `.symlinks/plugins/record/ios`) + - sqflite (from `.symlinks/plugins/sqflite/ios`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + +SPEC REPOS: + trunk: + - FMDB + - TOCropViewController + +EXTERNAL SOURCES: + audio_session: + :path: ".symlinks/plugins/audio_session/ios" + Flutter: + :path: Flutter + flutter_secure_storage: + :path: ".symlinks/plugins/flutter_secure_storage/ios" + flutter_vibrate: + :path: ".symlinks/plugins/flutter_vibrate/ios" + image_cropper: + :path: ".symlinks/plugins/image_cropper/ios" + image_picker: + :path: ".symlinks/plugins/image_picker/ios" + just_audio: + :path: ".symlinks/plugins/just_audio/ios" + path_provider_ios: + :path: ".symlinks/plugins/path_provider_ios/ios" + record: + :path: ".symlinks/plugins/record/ios" + sqflite: + :path: ".symlinks/plugins/sqflite/ios" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + +SPEC CHECKSUMS: + audio_session: 4f3e461722055d21515cf3261b64c973c062f345 + Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a + flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec + flutter_vibrate: 9f4c2ab57008965f78969472367c329dd77eb801 + FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a + image_cropper: 60c2789d1f1a78c873235d4319ca0c34a69f2d98 + image_picker: 9aa50e1d8cdacdbed739e925b7eea16d014367e6 + just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa + path_provider_ios: 7d7ce634493af4477d156294792024ec3485acd5 + record: 7ee2393532f8553bbb09fa19e95478323b7c0a99 + sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 + TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863 + url_launcher_ios: 02f1989d4e14e998335b02b67a7590fa34f971af + +PODFILE CHECKSUM: a75497545d4391e2d394c3668e20cfb1c2bbd4aa + +COCOAPODS: 1.11.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index ab390f9..afb27d5 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 51; objects = { /* Begin PBXBuildFile section */ @@ -13,6 +13,7 @@ 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 */; }; + E870A5F479A60D6704DD5DF2 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 75DBECA488F412614712FB74 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -31,9 +32,11 @@ /* 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 = ""; }; + 1CDA6531AC975E620DBA1134 /* 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 = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 75DBECA488F412614712FB74 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; @@ -42,6 +45,8 @@ 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 = ""; }; + 98ACB01D5FA5A78DB2686183 /* 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 = ""; }; + C97DED20C4A171F16FB949CD /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -49,12 +54,31 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E870A5F479A60D6704DD5DF2 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 650C3E9A238A953E4E8E6AED /* Frameworks */ = { + isa = PBXGroup; + children = ( + 75DBECA488F412614712FB74 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 8170350417391B7E9B77985B /* Pods */ = { + isa = PBXGroup; + children = ( + C97DED20C4A171F16FB949CD /* Pods-Runner.debug.xcconfig */, + 98ACB01D5FA5A78DB2686183 /* Pods-Runner.release.xcconfig */, + 1CDA6531AC975E620DBA1134 /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -72,6 +96,8 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + 8170350417391B7E9B77985B /* Pods */, + 650C3E9A238A953E4E8E6AED /* Frameworks */, ); sourceTree = ""; }; @@ -105,12 +131,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 9F9C99F59A6A1134B656560D /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + AE20B55CEF8506DDEEBE3543 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -127,7 +155,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1300; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -197,6 +225,45 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + 9F9C99F59A6A1134B656560D /* [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; + }; + AE20B55CEF8506DDEEBE3543 /* [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; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -287,12 +354,19 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = W2PAW454F9; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.didvan.didvanapp; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -411,12 +485,19 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = W2PAW454F9; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.didvan.didvanapp; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -430,12 +511,19 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = W2PAW454F9; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.didvan.didvanapp; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140c..3db53b6 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + + diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png new file mode 100644 index 0000000..c89e7ed Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100644 index 0000000..55e86ba Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png new file mode 100644 index 0000000..0a984f2 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png new file mode 100644 index 0000000..54ff853 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png new file mode 100644 index 0000000..d525986 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png new file mode 100644 index 0000000..f00cef9 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png new file mode 100644 index 0000000..1cebf8d Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png new file mode 100644 index 0000000..eb8dc68 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png new file mode 100644 index 0000000..2692e97 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/172.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/172.png new file mode 100644 index 0000000..8836e48 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/172.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png new file mode 100644 index 0000000..0d0a595 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/196.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/196.png new file mode 100644 index 0000000..f7aee80 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/196.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png new file mode 100644 index 0000000..05a2b2c Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/216.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/216.png new file mode 100644 index 0000000..e60aeea Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/216.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png new file mode 100644 index 0000000..090d005 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100644 index 0000000..489b5c8 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png new file mode 100644 index 0000000..c9bf87c Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100644 index 0000000..05c32e8 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/48.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/48.png new file mode 100644 index 0000000..a246eff Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/48.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png new file mode 100644 index 0000000..d30c1cc Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png new file mode 100644 index 0000000..77f48a9 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/55.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/55.png new file mode 100644 index 0000000..d38ad5d Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/55.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png new file mode 100644 index 0000000..9e534dc Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png new file mode 100644 index 0000000..e1fc0c9 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100644 index 0000000..615b8f4 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png new file mode 100644 index 0000000..82cbc07 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png new file mode 100644 index 0000000..7ff37bc Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png new file mode 100644 index 0000000..f7388be Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png new file mode 100644 index 0000000..6702119 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png new file mode 100644 index 0000000..b1011a6 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/88.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/88.png new file mode 100644 index 0000000..6d433e1 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/88.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index d36b1fa..e138c0b 100644 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,122 +1 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} +{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"idiom":"watch","filename":"172.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"86x86","expected-size":"172","role":"quickLook"},{"idiom":"watch","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"40x40","expected-size":"80","role":"appLauncher"},{"idiom":"watch","filename":"88.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"40mm","scale":"2x","size":"44x44","expected-size":"88","role":"appLauncher"},{"idiom":"watch","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"44mm","scale":"2x","size":"50x50","expected-size":"100","role":"appLauncher"},{"idiom":"watch","filename":"196.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"42mm","scale":"2x","size":"98x98","expected-size":"196","role":"quickLook"},{"idiom":"watch","filename":"216.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"44mm","scale":"2x","size":"108x108","expected-size":"216","role":"quickLook"},{"idiom":"watch","filename":"48.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"24x24","expected-size":"48","role":"notificationCenter"},{"idiom":"watch","filename":"55.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"42mm","scale":"2x","size":"27.5x27.5","expected-size":"55","role":"notificationCenter"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch","role":"companionSettings","scale":"3x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch","role":"companionSettings","scale":"2x"},{"size":"1024x1024","expected-size":"1024","filename":"1024.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch-marketing","scale":"1x"},{"size":"128x128","expected-size":"128","filename":"128.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"256x256","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"128x128","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"256x256","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"512x512","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"16","filename":"16.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"64","filename":"64.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"512x512","expected-size":"1024","filename":"1024.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"}]} \ No newline at end of file diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index dc9ada4..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index 28c6bf0..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 2ccbfd9..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index f091b6b..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cde121..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index d0ef06e..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index dcdc230..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png deleted file mode 100644 index 2ccbfd9..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index c8f9ed8..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index a6d6b86..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index a6d6b86..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index 75b2d16..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index c4df70d..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png deleted file mode 100644 index 6a84f41..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index d0e1f58..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 8a877dd..4d67f3b 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -26,6 +26,12 @@ LaunchScreen UIMainStoryboardFile Main + NSMicrophoneUsageDescription +We need to access to the microphone to record audio file +NSPhotoLibraryUsageDescription +We need to access to the user gallery to add user profile photo +NSCameraUsageDescription +We need to access to the user gallery to add user profile photo UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/lib/assets/animations/loading.gif b/lib/assets/animations/loading.gif new file mode 100644 index 0000000..cf967e1 Binary files /dev/null and b/lib/assets/animations/loading.gif differ diff --git a/lib/assets/images/logos/studio-dark.svg b/lib/assets/images/logos/studio-dark.svg new file mode 100644 index 0000000..3d0e67d --- /dev/null +++ b/lib/assets/images/logos/studio-dark.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/assets/images/logos/studio-light.svg b/lib/assets/images/logos/studio-light.svg new file mode 100644 index 0000000..73d14b1 --- /dev/null +++ b/lib/assets/images/logos/studio-light.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/config/theme_data.dart b/lib/config/theme_data.dart index d605c2a..c2c4516 100644 --- a/lib/config/theme_data.dart +++ b/lib/config/theme_data.dart @@ -36,9 +36,9 @@ class LightThemeConfig { static const ColorScheme _colorScheme = ColorScheme( primary: _primary, - primaryVariant: _white, + primaryContainer: _white, secondary: Color(0xFFD61515), - secondaryVariant: _white, + secondaryContainer: _white, surface: _white, background: _background, error: Color(0xFFF00505), @@ -116,9 +116,9 @@ class DarkThemeConfig { static const ColorScheme _colorScheme = ColorScheme( primary: _primary, - primaryVariant: _white, + primaryContainer: _white, secondary: Color(0xFFE53939), - secondaryVariant: _white, + secondaryContainer: _white, surface: Color(0xFF181B1F), background: _background, error: Color(0xFFF53B3B), diff --git a/lib/constants/assets.dart b/lib/constants/assets.dart index a331516..e19e386 100644 --- a/lib/constants/assets.dart +++ b/lib/constants/assets.dart @@ -8,11 +8,13 @@ class Assets { static const String _baseEmptyStatesPath = _basePath + '/images/empty_states'; static const String _baseAnimationsPath = _basePath + '/animations'; static const String _baseRecordsPath = _basePath + '/images/records'; + static const String _baseLogosPath = _basePath + '/images/logos'; static String get verticalLogoWithText => - _baseImagesPath + '/logos/logo-vertical-$_themeSuffix.svg'; + _baseLogosPath + '/logo-vertical-$_themeSuffix.svg'; static String get horizontalLogoWithText => - _baseImagesPath + '/logos/logo-horizontal-$_themeSuffix.svg'; + _baseLogosPath + '/logo-horizontal-$_themeSuffix.svg'; + static String get studioLogo => _baseLogosPath + '/studio-$_themeSuffix.svg'; static String get logoLoadingAnimation => _baseAnimationsPath + '/indicator-$_themeSuffix.riv'; diff --git a/lib/main.dart b/lib/main.dart index 9c23e4d..01dbfff 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,5 @@ import 'package:bot_toast/bot_toast.dart'; import 'package:didvan/config/theme_data.dart'; -import 'package:didvan/providers/server_data_provider.dart'; import 'package:didvan/providers/theme_provider.dart'; import 'package:didvan/providers/user_provider.dart'; import 'package:didvan/routes/route_generator.dart'; @@ -24,9 +23,6 @@ class Didvan extends StatelessWidget { ChangeNotifierProvider( create: (context) => UserProvider(), ), - ChangeNotifierProvider( - create: (context) => ServerDataProvider(), - ), ChangeNotifierProvider( create: (context) => ThemeProvider(), ), diff --git a/lib/models/category.dart b/lib/models/category.dart index 6968552..3faf75a 100644 --- a/lib/models/category.dart +++ b/lib/models/category.dart @@ -1,10 +1,10 @@ -class Category { +class CategoryData { final int id; final String label; - const Category({required this.id, required this.label}); + const CategoryData({required this.id, required this.label}); - factory Category.fromJson(Map json) => Category( + factory CategoryData.fromJson(Map json) => CategoryData( id: json['id'], label: json['label'], ); diff --git a/lib/models/chat_room/chat_room.dart b/lib/models/chat_room/chat_room.dart index a741cc7..99457eb 100644 --- a/lib/models/chat_room/chat_room.dart +++ b/lib/models/chat_room/chat_room.dart @@ -4,10 +4,10 @@ class ChatRoom { final int id; final String type; final String updatedAt; - final int unread; + int unread; final LastMessage lastMessage; - const ChatRoom({ + ChatRoom({ required this.id, required this.type, required this.updatedAt, diff --git a/lib/models/item_overview.dart b/lib/models/item_overview.dart deleted file mode 100644 index 921d7db..0000000 --- a/lib/models/item_overview.dart +++ /dev/null @@ -1,26 +0,0 @@ -class OverviewData { - final int id; - final String title; - final String image; - final String description; - final String createdAt; - final String? type; - - const OverviewData({ - required this.id, - required this.title, - required this.image, - required this.description, - required this.createdAt, - required this.type, - }); - - factory OverviewData.fromJson(Map json) => OverviewData( - id: json['id'], - title: json['title'], - image: json['image'], - description: json['description'], - createdAt: json['createdAt'], - type: json['type'], - ); -} diff --git a/lib/models/message_data/message_data.dart b/lib/models/message_data/message_data.dart index 8cb4b45..1778abc 100644 --- a/lib/models/message_data/message_data.dart +++ b/lib/models/message_data/message_data.dart @@ -1,3 +1,6 @@ +import 'dart:convert'; +import 'dart:io'; + import 'radar_attachment.dart'; class MessageData { @@ -8,15 +11,19 @@ class MessageData { final bool readed; final String createdAt; final RadarAttachment? radar; + final File? audioFile; + final int? audioDuration; const MessageData({ required this.id, required this.writedByAdmin, required this.readed, required this.createdAt, - required this.text, - required this.audio, - required this.radar, + this.text, + this.audio, + this.radar, + this.audioFile, + this.audioDuration, }); factory MessageData.fromJson(Map json) => MessageData( @@ -26,9 +33,12 @@ class MessageData { writedByAdmin: json['writedByAdmin'], readed: json['readed'], createdAt: json['createdAt'], + audioDuration: json['waveform'] == null + ? null + : jsonDecode(json['waveform'])['duration'] ?? 0, radar: json['radar'] == null ? null - : RadarAttachment.fromJson(json['radar'] as Map), + : RadarAttachment.fromJson(json['radar']), ); Map toJson() => { diff --git a/lib/models/message_data/radar_attachment.dart b/lib/models/message_data/radar_attachment.dart index 13dbe76..6e55f2d 100644 --- a/lib/models/message_data/radar_attachment.dart +++ b/lib/models/message_data/radar_attachment.dart @@ -1,3 +1,5 @@ +import 'package:didvan/models/category.dart'; + class RadarAttachment { final int id; final String title; @@ -5,6 +7,8 @@ class RadarAttachment { final int timeToRead; final String image; final bool forManagers; + final String createdAt; + final List categories; const RadarAttachment({ required this.id, @@ -13,6 +17,8 @@ class RadarAttachment { required this.timeToRead, required this.image, required this.forManagers, + required this.categories, + required this.createdAt, }); factory RadarAttachment.fromJson(Map json) => @@ -22,7 +28,13 @@ class RadarAttachment { description: json['description'], timeToRead: json['timeToRead'], image: json['image'], + createdAt: json['createdAt'], forManagers: json['forManagers'], + categories: List.from( + json['categories'].map( + (cat) => CategoryData.fromJson(cat), + ), + ), ); Map toJson() => { diff --git a/lib/models/news_details_data.dart b/lib/models/news_details_data.dart index 1b603bd..0a1a68b 100644 --- a/lib/models/news_details_data.dart +++ b/lib/models/news_details_data.dart @@ -1,4 +1,5 @@ import 'package:didvan/models/content.dart'; +import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/tag.dart'; class NewsDetailsData { @@ -12,6 +13,7 @@ class NewsDetailsData { final int order; final List tags; final List contents; + final List relatedContents = []; NewsDetailsData({ required this.id, diff --git a/lib/models/news_overview.dart b/lib/models/news_overview.dart deleted file mode 100644 index 35d2161..0000000 --- a/lib/models/news_overview.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:didvan/models/item_overview.dart'; - -class NewsOverviewData extends OverviewData { - final String reference; - bool marked; - - NewsOverviewData({ - required this.reference, - required this.marked, - required id, - required createdAt, - required description, - required title, - required image, - }) : super( - createdAt: createdAt, - description: description, - id: id, - image: image, - title: title, - type: 'news', - ); - - factory NewsOverviewData.fromJson(Map json) => - NewsOverviewData( - id: json['id'], - title: json['title'], - reference: json['reference'], - description: json['description'], - image: json['image'], - createdAt: json['createdAt'], - marked: json['marked'] ?? true, - ); -} diff --git a/lib/models/overview_data.dart b/lib/models/overview_data.dart new file mode 100644 index 0000000..4d16449 --- /dev/null +++ b/lib/models/overview_data.dart @@ -0,0 +1,61 @@ +import 'package:didvan/models/category.dart'; + +class OverviewData { + final int id; + final String title; + final String image; + final String description; + final int? timeToRead; + final String? reference; + final bool forManagers; + final String createdAt; + final String type; + int comments; + bool marked; + final List? categories; + + OverviewData({ + required this.id, + required this.title, + required this.image, + required this.description, + required this.createdAt, + required this.type, + required this.marked, + required this.comments, + required this.forManagers, + this.timeToRead, + this.reference, + this.categories, + }); + + factory OverviewData.fromJson(Map json) => OverviewData( + id: json['id'], + title: json['title'], + image: json['image'], + description: json['description'], + timeToRead: json['timeToRead'], + reference: json['reference'], + forManagers: json['forManagers'] ?? false, + comments: json['comments'] ?? 0, + createdAt: json['createdAt'], + type: json['type'] ?? '', + marked: json['marked'] ?? false, + categories: (json['categories'] as List?) + ?.map((e) => CategoryData.fromJson(e as Map)) + .toList(), + ); + + Map toJson() => { + 'id': id, + 'title': title, + 'image': image, + 'description': description, + 'timeToRead': timeToRead, + 'reference': reference, + 'forManagers': forManagers, + 'createdAt': createdAt, + 'type': type, + 'categories': categories?.map((e) => e.toJson()).toList(), + }; +} diff --git a/lib/models/radar_details_data.dart b/lib/models/radar_details_data.dart index fb493e3..a41bb1e 100644 --- a/lib/models/radar_details_data.dart +++ b/lib/models/radar_details_data.dart @@ -1,4 +1,5 @@ import 'package:didvan/models/category.dart'; +import 'package:didvan/models/overview_data.dart'; import 'content.dart'; import 'tag.dart'; @@ -15,8 +16,9 @@ class RadarDetailsData { int comments; final List tags; final List contents; - final List categories; + final List categories; final int order; + final List relatedContents = []; RadarDetailsData({ required this.id, @@ -52,9 +54,9 @@ class RadarDetailsData { (content) => Content.fromJson(content), ), ), - categories: List.from( + categories: List.from( json['categories'].map( - (cat) => Category.fromJson(cat), + (cat) => CategoryData.fromJson(cat), ), ), ); diff --git a/lib/models/radar_overview.dart b/lib/models/radar_overview.dart deleted file mode 100644 index ba3469e..0000000 --- a/lib/models/radar_overview.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:didvan/models/item_overview.dart'; - -import 'category.dart'; - -class RadarOverviewData extends OverviewData { - final bool forManagers; - final List categories; - final int timeToRead; - int comments; - bool marked; - - RadarOverviewData({ - required this.forManagers, - required this.categories, - required this.comments, - required this.timeToRead, - required this.marked, - required createdAt, - required description, - required id, - required image, - required title, - }) : super( - createdAt: createdAt, - description: description, - id: id, - image: image, - title: title, - type: 'radar', - ); - - factory RadarOverviewData.fromJson(Map json) => - RadarOverviewData( - id: json['id'], - image: json['image'], - title: json['title'], - description: json['description'], - timeToRead: json['timeToRead'], - createdAt: json['createdAt'], - forManagers: json['forManagers'], - marked: json['marked'], - comments: json['comments'], - categories: List.from( - json['categories'].map( - (category) => Category.fromJson(category), - ), - ), - ); -} diff --git a/lib/models/user.dart b/lib/models/user.dart index 01feaf8..92011db 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -43,11 +43,11 @@ class User { }) { return User( id: id ?? this.id, - username: username ?? this.username, + username: this.username, phoneNumber: phoneNumber ?? this.phoneNumber, - photo: photo ?? this.photo, + photo: photo, fullName: fullName ?? this.fullName, - email: email ?? this.email, + email: email, ); } } diff --git a/lib/models/view/action_sheet_data.dart b/lib/models/view/action_sheet_data.dart index a95a4a2..6166bb6 100644 --- a/lib/models/view/action_sheet_data.dart +++ b/lib/models/view/action_sheet_data.dart @@ -10,6 +10,7 @@ class ActionSheetData { final IconData? titleIcon; final Color? titleColor; final bool hasDismissButton; + final bool hasConfirmButton; final bool withoutButtonMode; final bool smallDismissButton; @@ -20,6 +21,7 @@ class ActionSheetData { this.onConfirmed, this.titleColor, this.hasDismissButton = true, + this.hasConfirmButton = true, this.titleIcon, this.dismissTitle, this.onDismissed, diff --git a/lib/pages/home/direct/direct.dart b/lib/pages/home/direct/direct.dart deleted file mode 100644 index 095f8d7..0000000 --- a/lib/pages/home/direct/direct.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:didvan/pages/home/direct/direct_state.dart'; -import 'package:didvan/pages/home/direct/widgets/message_box.dart'; -import 'package:didvan/models/view/app_bar_data.dart'; -import 'package:didvan/widgets/didvan/scaffold.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -class Direct extends StatefulWidget { - final int id; - const Direct({Key? key, required this.id}) : super(key: key); - - @override - State createState() => _DirectState(); -} - -class _DirectState extends State { - @override - void initState() { - Future.delayed(Duration.zero, () { - context.read().getMessages(widget.id); - }); - super.initState(); - } - - @override - Widget build(BuildContext context) { - return Material( - child: Stack( - children: [ - Positioned( - top: 0, - bottom: 56, - left: 0, - right: 0, - child: DidvanScaffold( - appBarData: AppBarData( - hasBack: true, - subtitle: 'ارتباط با سردبیر', - title: 'رادار اقتصادی', - ), - slivers: const [], - ), - ), - Positioned( - bottom: MediaQuery.of(context).viewInsets.bottom, - right: 0, - left: 0, - child: const MessageBox(), - ), - ], - ), - ); - } -} diff --git a/lib/pages/home/direct/direct_state.dart b/lib/pages/home/direct/direct_state.dart deleted file mode 100644 index 18b9b86..0000000 --- a/lib/pages/home/direct/direct_state.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'dart:io'; - -import 'package:didvan/providers/core_provider.dart'; -import 'package:didvan/services/network/request.dart'; -import 'package:didvan/services/network/request_helper.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter_vibrate/flutter_vibrate.dart'; -import 'package:record/record.dart'; - -class DirectState extends CoreProvier { - final _recorder = Record(); - - File? recordedFile; - - bool isRecording = false; - - Future getMessages(int id) async { - final RequestService service = RequestService(RequestHelper.direct(id)); - await service.httpGet(); - if (service.isSuccess) {} - } - - void deleteRecordedFile() { - recordedFile!.delete(); - recordedFile = null; - notifyListeners(); - } - - Future startRecording() async { - await _recorder.hasPermission(); - if (!kIsWeb) { - Vibrate.feedback(FeedbackType.medium); - } - isRecording = true; - _recorder.start(); - notifyListeners(); - } - - Future stopRecording(bool sendImidiately) async { - final path = await _recorder.stop(); - isRecording = false; - if (path == null) { - notifyListeners(); - return; - } - if (kIsWeb) { - final uri = Uri.file(path); - recordedFile = File.fromUri(uri); - } else { - recordedFile = File(path); - } - if (sendImidiately) { - await sendMessage(); - } else { - notifyListeners(); - } - } - - Future sendMessage() async {} -} diff --git a/lib/pages/home/direct/widgets/message_box.dart b/lib/pages/home/direct/widgets/message_box.dart deleted file mode 100644 index 3f2ccf4..0000000 --- a/lib/pages/home/direct/widgets/message_box.dart +++ /dev/null @@ -1,131 +0,0 @@ -import 'package:didvan/config/theme_data.dart'; -import 'package:didvan/constants/app_icons.dart'; -import 'package:didvan/pages/home/direct/direct_state.dart'; -import 'package:didvan/pages/home/widgets/audio_visualizer.dart'; -import 'package:didvan/widgets/didvan/icon_button.dart'; -import 'package:didvan/widgets/didvan/text.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -class MessageBox extends StatelessWidget { - const MessageBox({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Container( - height: 56, - decoration: BoxDecoration( - border: Border( - top: BorderSide( - color: Theme.of(context).colorScheme.cardBorder, - ), - ), - color: Theme.of(context).colorScheme.surface, - ), - child: Consumer( - builder: (context, state, child) { - if (state.isRecording) { - return const _Recording(); - } else if (!state.isRecording && state.recordedFile != null) { - return const _RecordChecking(); - } - return const _Typing(); - }, - ), - ); - } -} - -class _Typing extends StatelessWidget { - const _Typing({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final state = context.read(); - return Row( - children: [ - DidvanIconButton( - icon: DidvanIcons.mic_solid, - onPressed: state.startRecording, - size: 32, - color: Theme.of(context).colorScheme.focusedBorder, - ), - Expanded( - child: TextField( - textInputAction: TextInputAction.send, - decoration: InputDecoration( - border: InputBorder.none, - hintText: 'بنویسید یا پیام صوتی بگذارید...', - hintStyle: Theme.of(context) - .textTheme - .caption! - .copyWith(color: Theme.of(context).colorScheme.disabledText), - ), - onChanged: (value) {}, - ), - ), - ], - ); - } -} - -class _Recording extends StatelessWidget { - const _Recording({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final state = context.read(); - return Row( - children: [ - DidvanIconButton( - icon: DidvanIcons.send_solid, - onPressed: () => state.stopRecording(true), - gestureSize: 52, - ), - Expanded( - child: DidvanText( - 'در حال ضبط صدا ...', - style: Theme.of(context).textTheme.caption, - ), - ), - DidvanIconButton( - icon: DidvanIcons.stop_circle_solid, - color: Theme.of(context).colorScheme.secondary, - onPressed: () => state.stopRecording(false), - size: 32, - ), - ], - ); - } -} - -class _RecordChecking extends StatelessWidget { - const _RecordChecking({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final state = context.read(); - return Row( - children: [ - DidvanIconButton( - icon: DidvanIcons.send_solid, - onPressed: () => state.stopRecording(true), - color: Theme.of(context).colorScheme.focusedBorder, - ), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: AudioVisualizer( - audioFile: state.recordedFile!, - ), - ), - ), - DidvanIconButton( - icon: DidvanIcons.trash_solid, - color: Theme.of(context).colorScheme.secondary, - onPressed: state.deleteRecordedFile, - ), - ], - ); - } -} diff --git a/lib/pages/home/studio/studio.dart b/lib/pages/home/studio/studio.dart deleted file mode 100644 index dd7eed3..0000000 --- a/lib/pages/home/studio/studio.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:didvan/config/theme_data.dart'; -import 'package:didvan/constants/assets.dart'; -import 'package:didvan/pages/home/widgets/logo_app_bar.dart'; -import 'package:didvan/widgets/state_handlers/empty_state.dart'; -import 'package:flutter/material.dart'; - -class Studio extends StatelessWidget { - const Studio({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - const LogoAppBar(), - Expanded( - child: EmptyState( - asset: Assets.emptyStudio, - title: 'استودیو آینده', - subtitle: 'به زودی...', - titleColor: Theme.of(context).colorScheme.title, - ), - ), - ], - ); - } -} diff --git a/lib/pages/home/widgets/audio_visualizer.dart b/lib/pages/home/widgets/audio_visualizer.dart deleted file mode 100644 index 94fc277..0000000 --- a/lib/pages/home/widgets/audio_visualizer.dart +++ /dev/null @@ -1,314 +0,0 @@ -import 'dart:io'; -import 'dart:math'; - -import 'package:didvan/config/design_config.dart'; -import 'package:didvan/config/theme_data.dart'; -import 'package:didvan/constants/app_icons.dart'; -import 'package:didvan/constants/assets.dart'; -import 'package:didvan/services/storage/storage.dart'; -import 'package:didvan/utils/date_time.dart'; -import 'package:didvan/widgets/didvan/icon_button.dart'; -import 'package:didvan/widgets/didvan/text.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:just_audio/just_audio.dart'; -import 'package:just_waveform/just_waveform.dart'; - -class AudioVisualizer extends StatefulWidget { - final File audioFile; - - const AudioVisualizer({ - Key? key, - required this.audioFile, - }) : super(key: key); - - @override - State createState() => _AudioVisualizerState(); -} - -class _AudioVisualizerState extends State { - final AudioPlayer _audioPlayer = AudioPlayer(); - - Stream? waveDataStream; - - @override - void initState() { - if (!kIsWeb) { - waveDataStream = JustWaveform.extract( - audioInFile: widget.audioFile, - waveOutFile: File(StorageService.appTempsDir + '/rec-wave.wave'), - zoom: const WaveformZoom.pixelsPerSecond(100), - ); - } - _setupAudioPlayer(); - super.initState(); - } - - @override - Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - color: DesignConfig.isDark - ? Theme.of(context).colorScheme.black - : Theme.of(context).colorScheme.background, - borderRadius: DesignConfig.mediumBorderRadius, - ), - child: Row( - children: [ - const SizedBox(width: 12), - StreamBuilder( - stream: _audioPlayer.positionStream, - builder: (context, snapshot) { - String text = ''; - if (_audioPlayer.duration == null) { - Future.delayed(Duration.zero, () { - setState(() {}); - }); - } - if (snapshot.data == null || snapshot.data == Duration.zero) { - text = DateTimeUtils.normalizeTimeDuration( - _audioPlayer.duration ?? Duration.zero); - } else { - text = DateTimeUtils.normalizeTimeDuration(snapshot.data!); - } - return DidvanText( - text, - color: Theme.of(context).colorScheme.focusedBorder, - isEnglishFont: true, - ); - }, - ), - const SizedBox(width: 12), - Expanded( - child: Builder( - builder: (context) { - if (kIsWeb) { - return SvgPicture.asset(Assets.record); - } - return StreamBuilder( - stream: waveDataStream, - builder: (context, snapshot) { - if (snapshot.data == null) { - return const SizedBox(); - } - if (snapshot.data!.waveform == null) { - return const SizedBox(); - } - final waveform = snapshot.data!.waveform!; - return GestureDetector( - onHorizontalDragUpdate: _changePosition, - onTapDown: _changePosition, - child: SizedBox( - height: double.infinity, - width: double.infinity, - child: _AudioWaveformWidget( - waveform: waveform, - audioPlayer: _audioPlayer, - start: Duration.zero, - scale: 2, - strokeWidth: 3, - duration: waveform.duration, - waveColor: - Theme.of(context).colorScheme.focusedBorder, - ), - ), - ); - }, - ); - }, - ), - ), - StreamBuilder( - stream: _audioPlayer.playingStream, - builder: (context, snapshot) { - return DidvanIconButton( - icon: snapshot.data == true - ? DidvanIcons.pause_circle_solid - : DidvanIcons.play_circle_solid, - color: Theme.of(context).colorScheme.focusedBorder, - onPressed: _playAndPouse, - ); - }, - ), - ], - ), - ); - } - - void _changePosition(details) { - double posper = - details.localPosition.dx / (MediaQuery.of(context).size.width - 200); - if (posper >= 1 || posper < 0) return; - final position = _audioPlayer.duration!.inMilliseconds; - _audioPlayer.seek( - Duration(milliseconds: (posper * position).toInt()), - ); - setState(() {}); - } - - Future _setupAudioPlayer() async { - if (kIsWeb) { - await _audioPlayer.setUrl( - widget.audioFile.uri.path.replaceAll('%3A', ':'), - ); - } else { - await _audioPlayer.setFilePath(widget.audioFile.path); - } - } - - Future _playAndPouse() async { - if (_audioPlayer.playing) { - _audioPlayer.pause(); - return; - } - await _audioPlayer.play(); - } - - @override - void dispose() { - _audioPlayer.dispose(); - super.dispose(); - } -} - -class _AudioWaveformWidget extends StatefulWidget { - final Color waveColor; - final double scale; - final double strokeWidth; - final double pixelsPerStep; - final Waveform waveform; - final Duration start; - final Duration duration; - final AudioPlayer audioPlayer; - - const _AudioWaveformWidget({ - Key? key, - required this.waveform, - required this.start, - required this.duration, - required this.audioPlayer, - this.waveColor = Colors.blue, - this.scale = 1.0, - this.strokeWidth = 5.0, - this.pixelsPerStep = 8.0, - }) : super(key: key); - - @override - __AudioWaveformWidgetState createState() => __AudioWaveformWidgetState(); -} - -class __AudioWaveformWidgetState extends State<_AudioWaveformWidget> - with SingleTickerProviderStateMixin { - double progress = 0; - - @override - void initState() { - widget.audioPlayer.positionStream.listen((event) { - if (widget.audioPlayer.duration == null) return; - setState(() { - progress = event.inMilliseconds / - widget.audioPlayer.duration!.inMilliseconds * - 100; - if (progress >= 100) { - progress = 0; - widget.audioPlayer.stop(); - widget.audioPlayer.seek(Duration.zero); - } - }); - }); - super.initState(); - } - - @override - Widget build(BuildContext context) { - return ClipRect( - child: CustomPaint( - painter: _AudioWaveformPainter( - waveColor: widget.waveColor, - waveform: widget.waveform, - start: widget.start, - duration: widget.duration, - scale: widget.scale, - strokeWidth: widget.strokeWidth, - pixelsPerStep: widget.pixelsPerStep, - progressPercentage: progress, - progressColor: Theme.of(context).colorScheme.focusedBorder, - color: Theme.of(context).colorScheme.border, - ), - ), - ); - } -} - -class _AudioWaveformPainter extends CustomPainter { - final double scale; - final double strokeWidth; - final double pixelsPerStep; - final Waveform waveform; - final Duration start; - final Duration duration; - final double progressPercentage; - final Color progressColor; - final Color color; - - _AudioWaveformPainter({ - required this.waveform, - required this.start, - required this.duration, - required this.progressPercentage, - required this.color, - required this.progressColor, - Color waveColor = Colors.blue, - this.scale = 1.0, - this.strokeWidth = 5.0, - this.pixelsPerStep = 8.0, - }); - - @override - void paint(Canvas canvas, Size size) { - if (duration == Duration.zero) return; - double width = size.width; - double height = size.height; - - final waveformPixelsPerWindow = waveform.positionToPixel(duration).toInt(); - final waveformPixelsPerDevicePixel = waveformPixelsPerWindow / width; - final waveformPixelsPerStep = waveformPixelsPerDevicePixel * pixelsPerStep; - final sampleOffset = waveform.positionToPixel(start); - final sampleStart = -sampleOffset % waveformPixelsPerStep; - final totalLength = waveformPixelsPerWindow; - final wavePaintB = Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = strokeWidth - ..strokeCap = StrokeCap.round - ..color = progressColor; - final wavePaintA = Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = strokeWidth - ..strokeCap = StrokeCap.round - ..color = color; - for (var i = sampleStart.toDouble(); - i <= waveformPixelsPerWindow + 1.0; - i += waveformPixelsPerStep) { - final sampleIdx = (sampleOffset + i).toInt(); - final x = i / waveformPixelsPerDevicePixel; - final minY = normalise(waveform.getPixelMin(sampleIdx), height); - final maxY = normalise(waveform.getPixelMax(sampleIdx), height); - canvas.drawLine( - Offset(x + strokeWidth / 2, max(strokeWidth * 0.75, minY)), - Offset(x + strokeWidth / 2, min(height - strokeWidth * 0.75, maxY)), - i / totalLength < progressPercentage / 100 ? wavePaintB : wavePaintA, - ); - } - } - - @override - bool shouldRepaint(covariant _AudioWaveformPainter oldDelegate) { - return oldDelegate.progressPercentage != progressPercentage; - } - - double normalise(int s, double height) { - final y = 32768 + (scale * s).clamp(-32768.0, 32767.0).toDouble(); - return height - 1 - y * height / 65536; - } -} diff --git a/lib/pages/home/widgets/tag_item.dart b/lib/pages/home/widgets/tag_item.dart deleted file mode 100644 index 24d9fa3..0000000 --- a/lib/pages/home/widgets/tag_item.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:didvan/config/design_config.dart'; -import 'package:didvan/config/theme_data.dart'; -import 'package:didvan/constants/app_icons.dart'; -import 'package:didvan/widgets/didvan/text.dart'; -import 'package:flutter/material.dart'; - -class TagItem extends StatelessWidget { - final String label; - - const TagItem({ - Key? key, - required this.label, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.symmetric( - vertical: 4, - horizontal: 8, - ), - decoration: BoxDecoration( - borderRadius: DesignConfig.lowBorderRadius, - border: Border.all( - color: Theme.of(context).colorScheme.focusedBorder, - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - DidvanIcons.hashtag_regular, - color: Theme.of(context).colorScheme.focusedBorder, - ), - DidvanText( - label, - color: Theme.of(context).colorScheme.focusedBorder, - style: Theme.of(context).textTheme.bodyText1, - ), - ], - ), - ); - } -} diff --git a/lib/providers/server_data_provider.dart b/lib/providers/server_data_provider.dart index 53eb006..e9b02a5 100644 --- a/lib/providers/server_data_provider.dart +++ b/lib/providers/server_data_provider.dart @@ -1,15 +1,18 @@ -import 'package:didvan/providers/core_provider.dart'; import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request_helper.dart'; -class ServerDataProvider extends CoreProvier { - final List directTypes = []; +class ServerDataProvider { + static final List directTypes = []; - Future getData() async { + static Future getData() async { await _getDirectTypes(); } - Future _getDirectTypes() async { + static int labelToTypeId(String? label) => label == null + ? 7 + : directTypes.firstWhere((element) => element.value.contains(label)).key; + + static Future _getDirectTypes() async { final service = RequestService(RequestHelper.directTypes); await service.httpGet(); if (service.isSuccess) { @@ -17,6 +20,8 @@ class ServerDataProvider extends CoreProvier { for (var i = 0; i < types.length; i++) { directTypes.add(MapEntry(types[i]['id'], types[i]['label'])); } + } else { + throw 'Fetchin direct types failed!'; } } } diff --git a/lib/providers/user_provider.dart b/lib/providers/user_provider.dart index 3be9ea5..219acd1 100644 --- a/lib/providers/user_provider.dart +++ b/lib/providers/user_provider.dart @@ -20,6 +20,7 @@ class UserProvider extends CoreProvier { return token; } await StorageService.setValue(key: 'token', value: newToken); + return null; } Future getUserInfo() async { @@ -35,8 +36,15 @@ class UserProvider extends CoreProvier { Future setProfilePhoto(dynamic file) async { appState = AppState.isolatedBusy; final RequestService service = - RequestService(RequestHelper.updateUserProfile); - await service.multipart(file); + RequestService(RequestHelper.updateProfilePhoto); + await service.multipart( + file: file, + method: 'PUT', + fileName: 'user-profile', + fieldName: 'photo', + mediaExtension: 'jpg', + mediaFormat: 'image', + ); if (service.isSuccess) { user = user.copyWith(photo: service.result['photo']); appState = AppState.idle; @@ -46,6 +54,20 @@ class UserProvider extends CoreProvier { return false; } + Future deleteProfilePhoto() async { + appState = AppState.isolatedBusy; + final RequestService service = + RequestService(RequestHelper.updateProfilePhoto); + await service.delete(); + if (service.isSuccess) { + user = user.copyWith(photo: null); + appState = AppState.idle; + return true; + } + appState = AppState.idle; + return false; + } + Future checkUsername(String username) async { if (user.username == username) return true; final RequestService service = RequestService( diff --git a/lib/routes/route_generator.dart b/lib/routes/route_generator.dart index 15452b7..9bf6300 100644 --- a/lib/routes/route_generator.dart +++ b/lib/routes/route_generator.dart @@ -1,30 +1,32 @@ -import 'package:didvan/pages/authentication/authentication.dart'; -import 'package:didvan/pages/authentication/authentication_state.dart'; -import 'package:didvan/pages/home/comments/comments.dart'; -import 'package:didvan/pages/home/comments/comments_state.dart'; -import 'package:didvan/pages/home/direct/direct.dart'; -import 'package:didvan/pages/home/direct/direct_state.dart'; -import 'package:didvan/pages/home/home.dart'; -import 'package:didvan/pages/home/home_state.dart'; -import 'package:didvan/pages/home/news/news_details/news_details.dart'; -import 'package:didvan/pages/home/news/news_details/news_details_state.dart'; -import 'package:didvan/pages/home/news/news_state.dart'; -import 'package:didvan/pages/home/radar/radar_details/radar_details.dart'; -import 'package:didvan/pages/home/radar/radar_details/radar_details_state.dart'; -import 'package:didvan/pages/home/radar/radar_state.dart'; -import 'package:didvan/pages/home/settings/about_us/about_us.dart'; -import 'package:didvan/pages/home/settings/bookmarks/bookmarks.dart'; -import 'package:didvan/pages/home/settings/bookmarks/bookmark_state.dart'; -import 'package:didvan/pages/home/settings/bookmarks/filtered_bookmark/filtered_bookmark.dart'; -import 'package:didvan/pages/home/settings/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart'; -import 'package:didvan/pages/home/settings/direct_list/direct_list.dart'; -import 'package:didvan/pages/home/settings/direct_list/direct_list_state.dart'; -import 'package:didvan/pages/home/settings/general_settings/settings.dart'; -import 'package:didvan/pages/home/settings/general_settings/settings_state.dart'; -import 'package:didvan/pages/home/settings/profile/profile.dart'; -import 'package:didvan/pages/splash/splash.dart'; +import 'package:didvan/models/tag.dart'; +import 'package:didvan/views/authentication/authentication.dart'; +import 'package:didvan/views/authentication/authentication_state.dart'; +import 'package:didvan/views/home/comments/comments.dart'; +import 'package:didvan/views/home/comments/comments_state.dart'; +import 'package:didvan/views/home/direct/direct.dart'; +import 'package:didvan/views/home/direct/direct_state.dart'; +import 'package:didvan/views/home/hashtag/hashtag.dart'; +import 'package:didvan/views/home/hashtag/hashtag_state.dart'; +import 'package:didvan/views/home/home.dart'; +import 'package:didvan/views/home/home_state.dart'; +import 'package:didvan/views/home/news/news_details/news_details.dart'; +import 'package:didvan/views/home/news/news_details/news_details_state.dart'; +import 'package:didvan/views/home/news/news_state.dart'; +import 'package:didvan/views/home/radar/radar_details/radar_details.dart'; +import 'package:didvan/views/home/radar/radar_details/radar_details_state.dart'; +import 'package:didvan/views/home/radar/radar_state.dart'; +import 'package:didvan/views/home/settings/about_us/about_us.dart'; +import 'package:didvan/views/home/settings/bookmarks/bookmarks.dart'; +import 'package:didvan/views/home/settings/bookmarks/bookmark_state.dart'; +import 'package:didvan/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmark.dart'; +import 'package:didvan/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart'; +import 'package:didvan/views/home/settings/direct_list/direct_list.dart'; +import 'package:didvan/views/home/settings/direct_list/direct_list_state.dart'; +import 'package:didvan/views/home/settings/general_settings/settings.dart'; +import 'package:didvan/views/home/settings/general_settings/settings_state.dart'; +import 'package:didvan/views/home/settings/profile/profile.dart'; +import 'package:didvan/views/splash/splash.dart'; import 'package:didvan/routes/routes.dart'; -import 'package:didvan/widgets/image_cropper.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -67,12 +69,6 @@ class RouteGenerator { return _createRoute( const AboutUs(), ); - case Routes.imageCropper: - return _createRoute( - ImageCropper( - data: settings.arguments as Map, - ), - ); case Routes.generalSettings: return _createRoute( ChangeNotifierProvider( @@ -109,7 +105,7 @@ class RouteGenerator { return _createRoute( ChangeNotifierProvider( create: (context) => DirectState(), - child: Direct(id: settings.arguments as int), + child: Direct(pageData: settings.arguments as Map), ), ); case Routes.comments: @@ -128,6 +124,14 @@ class RouteGenerator { child: const Bookmarks(), ), ); + case Routes.hashtag: + return _createRoute( + ChangeNotifierProvider( + create: (context) => HashtagState(), + child: Hashtag(tag: settings.arguments as Tag), + ), + ); + case Routes.filteredBookmarks: return _createRoute( ChangeNotifierProvider( @@ -157,7 +161,13 @@ class RouteGenerator { static Route _createRoute(page) { return MaterialPageRoute( - builder: (context) => page, + builder: (context) => Container( + color: Theme.of(context).colorScheme.surface, + child: SafeArea( + child: page, + top: false, + ), + ), ); } } diff --git a/lib/routes/routes.dart b/lib/routes/routes.dart index 5bc9c90..7cbab35 100644 --- a/lib/routes/routes.dart +++ b/lib/routes/routes.dart @@ -14,4 +14,5 @@ class Routes { static const String bookmarks = '/bookmarks'; static const String filteredBookmarks = '/filtered-bookmarks'; static const String imageCropper = '/image-cropper'; + static const String hashtag = '/hashtag'; } diff --git a/lib/services/app_initalizer.dart b/lib/services/app_initalizer.dart index 2e9b1aa..236fc19 100644 --- a/lib/services/app_initalizer.dart +++ b/lib/services/app_initalizer.dart @@ -1,6 +1,5 @@ +import 'package:didvan/services/media/media.dart'; import 'package:didvan/services/storage/storage.dart'; -import 'package:firebase_core/firebase_core.dart'; -import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; @@ -11,8 +10,8 @@ class AppInitializer { StorageService.appDocsDir = (await getApplicationDocumentsDirectory()).path; StorageService.appTempsDir = (await getTemporaryDirectory()).path; + MediaService.init(); } - await _initializeFirebase(); } static Future initilizeSettings() async { @@ -43,29 +42,4 @@ class AppInitializer { return ThemeMode.light; } } - - static Future _initializeFirebase() async { - try { - await Firebase.initializeApp( - options: const FirebaseOptions( - apiKey: 'AIzaSyBp-UHjWeM0H0UHtX5yguFKG-riMzvvCzw', - appId: '1:935017686266:android:f9cbc9aba8e3d65ed2d543', - messagingSenderId: '935017686266', - projectId: 'didvan-9b7da', - ), - ); - } catch (e) { - Firebase.app(); - } - final FirebaseMessaging fcm = FirebaseMessaging.instance; - await fcm.requestPermission( - alert: true, - announcement: false, - badge: true, - carPlay: false, - criticalAlert: false, - provisional: false, - sound: true, - ); - } } diff --git a/lib/services/media/media.dart b/lib/services/media/media.dart index de0dba3..7e173cf 100644 --- a/lib/services/media/media.dart +++ b/lib/services/media/media.dart @@ -1,6 +1,59 @@ +import 'package:didvan/services/network/request.dart'; +import 'package:didvan/services/network/request_helper.dart'; +import 'package:flutter/foundation.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:just_audio/just_audio.dart'; class MediaService { + static final AudioPlayer audioPlayer = AudioPlayer(); + static String? audioPlayerTag; + + static void init() { + audioPlayer.positionStream.listen((event) { + if (audioPlayer.duration != null && audioPlayer.duration! < event) { + audioPlayer.stop(); + audioPlayer.seek(const Duration(seconds: 0)); + } + }); + } + + static Future handleAudioPlayback({ + required dynamic audioSource, + }) async { + bool isNetworkAudio = audioSource.runtimeType == String; + String tag; + if (isNetworkAudio) { + tag = audioSource; + } else { + tag = audioSource.path; + } + if (audioPlayerTag == tag) { + if (audioPlayer.playing) { + await audioPlayer.pause(); + } else { + await audioPlayer.play(); + } + } else { + await audioPlayer.stop(); + audioPlayerTag = tag; + if (isNetworkAudio) { + await audioPlayer.setUrl( + RequestHelper.baseUrl + + audioSource + + '?accessToken=${RequestService.token}', + ); + } else { + if (kIsWeb) { + await audioPlayer + .setUrl(audioSource!.uri.path.replaceAll('%3A', ':')); + } else { + await audioPlayer.setFilePath(audioSource.path); + } + } + audioPlayer.play(); + } + } + static Future pickImage({required ImageSource source}) async { final imagePicker = ImagePicker(); final XFile? pickedFile = await imagePicker.pickImage(source: source); diff --git a/lib/services/network/request.dart b/lib/services/network/request.dart index 1b11098..1614cbc 100644 --- a/lib/services/network/request.dart +++ b/lib/services/network/request.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'dart:developer'; import 'package:http/http.dart' as http; import 'package:http_parser/http_parser.dart' as parser; -import 'package:image_picker/image_picker.dart'; class RequestService { static late String token; @@ -100,19 +99,31 @@ class RequestService { } } - Future multipart(XFile file) async { + Future multipart({ + required dynamic file, + required String method, + required String fileName, + required String fieldName, + required String mediaFormat, + required String mediaExtension, + }) async { try { - final request = http.MultipartRequest('PUT', Uri.parse(url)); + final request = http.MultipartRequest(method, Uri.parse(url)); _headers.update('Content-Type', (_) => 'multipart/form-data'); request.headers.addAll(_headers); final length = await file.length(); + if (_requestBody != null) { + _requestBody!.forEach((key, value) { + request.fields.addAll({key.toString(): value.toString()}); + }); + } request.files.add( http.MultipartFile( - 'photo', + fieldName, file.readAsBytes().asStream(), length, - filename: 'profile-photo', - contentType: parser.MediaType('image', 'jpg'), + filename: fileName + '.' + mediaExtension, + contentType: parser.MediaType(mediaFormat, mediaExtension), ), ); final streamedResponse = await request diff --git a/lib/services/network/request_helper.dart b/lib/services/network/request_helper.dart index b9f2876..1eb9a6f 100644 --- a/lib/services/network/request_helper.dart +++ b/lib/services/network/request_helper.dart @@ -6,19 +6,37 @@ class RequestHelper { static const String _baseUserUrl = baseUrl + '/user'; static const String _baseRadarUrl = baseUrl + '/radar'; static const String _baseNewsUrl = baseUrl + '/news'; + static const String _baseDirectUrl = _baseUserUrl + '/direct'; static const String confirmUsername = _baseUserUrl + '/confirmUsername'; static const String login = _baseUserUrl + '/login'; static const String directs = _baseUserUrl + '/direct'; static const String userInfo = _baseUserUrl + '/info'; - static const String updateUserProfile = _baseUserUrl + '/profile/photo'; + static const String updateProfilePhoto = _baseUserUrl + '/profile/photo'; static const String checkUsername = _baseUserUrl + '/CheckUsername'; static const String updateProfile = _baseUserUrl + '/profile/edit'; static String bookmarks({String? type}) => _baseUserUrl + '/marked/${type ?? ''}'; static const String directTypes = baseUrl + '/direct/types'; - static String direct(int id) => _baseUserUrl + '/direct/$id'; + static String direct(int id) => _baseDirectUrl + '/$id'; + static String sendDirectMessage(int id) => + _baseDirectUrl + '/$id/sendMessage'; + static String tag({ + required List ids, + String? type, + int? itemId, + int? page, + int? limit, + }) => + baseUrl + + '/tag' + + _urlConcatGenerator([ + MapEntry('limit', limit?.toString() ?? '3'), + MapEntry('type', type), + MapEntry('id', itemId.toString()), + MapEntry('tags', _urlListConcatGenerator(ids)) + ]); static String markRadar(int id) => _baseRadarUrl + '/$id/mark'; static String radarComments(int id) => _baseRadarUrl + '/$id/comments'; @@ -96,5 +114,6 @@ class RequestHelper { } return result; } + return null; } } diff --git a/lib/utils/action_sheet.dart b/lib/utils/action_sheet.dart index f121af0..0bdb571 100644 --- a/lib/utils/action_sheet.dart +++ b/lib/utils/action_sheet.dart @@ -7,8 +7,8 @@ import 'package:didvan/constants/assets.dart'; import 'package:didvan/models/enums.dart'; import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/models/view/alert_data.dart'; -import 'package:didvan/widgets/didvan/button.dart'; -import 'package:didvan/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/didvan/button.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; import 'package:rive/rive.dart'; @@ -83,9 +83,7 @@ class ActionSheetUtils { decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: const BorderRadius.vertical( - top: Radius.circular( - 10, - ), + top: Radius.circular(10), ), ), child: SingleChildScrollView( @@ -136,17 +134,18 @@ class ActionSheetUtils { ), ), if (data.hasDismissButton) const SizedBox(width: 20), - Expanded( - flex: data.smallDismissButton ? 2 : 1, - child: DidvanButton( - style: ButtonStyleMode.primary, - onPressed: () { - Navigator.of(context).pop(); - data.onConfirmed?.call(); - }, - title: data.confrimTitle ?? 'تایید', + if (data.hasConfirmButton) + Expanded( + flex: data.smallDismissButton ? 2 : 1, + child: DidvanButton( + style: ButtonStyleMode.primary, + onPressed: () { + Navigator.of(context).pop(); + data.onConfirmed?.call(); + }, + title: data.confrimTitle ?? 'تایید', + ), ), - ), ], ), ], @@ -160,6 +159,7 @@ class ActionSheetUtils { await showDialog( context: context, builder: (context) => Dialog( + backgroundColor: Theme.of(context).colorScheme.surface, shape: const RoundedRectangleBorder( borderRadius: DesignConfig.mediumBorderRadius, ), diff --git a/lib/utils/date_time.dart b/lib/utils/date_time.dart index e2332a4..202a9ec 100644 --- a/lib/utils/date_time.dart +++ b/lib/utils/date_time.dart @@ -52,6 +52,31 @@ class DateTimeUtils { return result?.toDateTime().toString(); } + static String timeWithAmPm(String input) { + final dateTime = utcToLocalTime(input); + bool isAm = true; + int hour = 0; + int minute = 0; + if (dateTime.hour > 12) { + isAm = false; + hour = dateTime.hour - 12; + } else { + hour = dateTime.hour; + } + minute = dateTime.minute; + return '$hour:${_timeNormalizer(minute)} ${isAm ? 'ق.ظ' : 'ب.ظ'}'; + } + + static DateTime utcToLocalTime(String input) { + final dateTime = DateTime.parse(input); + return dateTime.add(const Duration(hours: 3, minutes: 30)); + } + + static String _timeNormalizer(int input) { + if (input < 10) return '0$input'; + return input.toString(); + } + static String momentGenerator(String input) { final date = DateTime.parse(input); final int seconds = (DateTime.now().difference(date).inSeconds).floor(); @@ -67,6 +92,7 @@ class DateTimeUtils { } interval = seconds / 86400; if (interval > 1) { + if (interval.floor() == 1) return 'دیروز'; return interval.floor().toString() + " روز پیش"; } interval = seconds / 3600; diff --git a/lib/pages/authentication/authentication.dart b/lib/views/authentication/authentication.dart similarity index 81% rename from lib/pages/authentication/authentication.dart rename to lib/views/authentication/authentication.dart index cd06164..9119411 100644 --- a/lib/pages/authentication/authentication.dart +++ b/lib/views/authentication/authentication.dart @@ -1,9 +1,9 @@ import 'package:didvan/config/design_config.dart'; -import 'package:didvan/pages/authentication/authentication_state.dart'; -import 'package:didvan/pages/authentication/screens/password.dart'; -import 'package:didvan/pages/authentication/screens/username.dart'; -import 'package:didvan/pages/authentication/screens/reset_password.dart'; -import 'package:didvan/pages/authentication/screens/verification.dart'; +import 'package:didvan/views/authentication/authentication_state.dart'; +import 'package:didvan/views/authentication/screens/password.dart'; +import 'package:didvan/views/authentication/screens/reset_password.dart'; +import 'package:didvan/views/authentication/screens/username.dart'; +import 'package:didvan/views/authentication/screens/verification.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/lib/pages/authentication/authentication_state.dart b/lib/views/authentication/authentication_state.dart similarity index 99% rename from lib/pages/authentication/authentication_state.dart rename to lib/views/authentication/authentication_state.dart index 7894f4e..7efa3ba 100644 --- a/lib/pages/authentication/authentication_state.dart +++ b/lib/views/authentication/authentication_state.dart @@ -55,5 +55,6 @@ class AuthenticationState extends CoreProvier { appState = AppState.failed; ActionSheetUtils.showAlert(AlertData(message: service.errorMessage)); } + return null; } } diff --git a/lib/pages/authentication/screens/password.dart b/lib/views/authentication/screens/password.dart similarity index 83% rename from lib/pages/authentication/screens/password.dart rename to lib/views/authentication/screens/password.dart index daada7e..391556b 100644 --- a/lib/pages/authentication/screens/password.dart +++ b/lib/views/authentication/screens/password.dart @@ -1,10 +1,13 @@ -import 'package:didvan/pages/authentication/authentication_state.dart'; -import 'package:didvan/pages/authentication/widgets/authentication_layout.dart'; +import 'dart:developer'; + +import 'package:didvan/providers/server_data_provider.dart'; import 'package:didvan/providers/user_provider.dart'; import 'package:didvan/routes/routes.dart'; -import 'package:didvan/widgets/didvan/button.dart'; -import 'package:didvan/widgets/didvan/text.dart'; -import 'package:didvan/widgets/didvan/text_field.dart'; +import 'package:didvan/views/authentication/authentication_state.dart'; +import 'package:didvan/views/authentication/widgets/authentication_layout.dart'; +import 'package:didvan/views/widgets/didvan/button.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/didvan/text_field.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -70,6 +73,8 @@ class _PasswordInputState extends State { final userProvider = context.read(); final token = await state.login(userProvider); if (token != null) { + log(token); + await ServerDataProvider.getData(); Navigator.of(context).pushReplacementNamed(Routes.home); } } diff --git a/lib/pages/authentication/screens/reset_password.dart b/lib/views/authentication/screens/reset_password.dart similarity index 83% rename from lib/pages/authentication/screens/reset_password.dart rename to lib/views/authentication/screens/reset_password.dart index 328265b..212dd2d 100644 --- a/lib/pages/authentication/screens/reset_password.dart +++ b/lib/views/authentication/screens/reset_password.dart @@ -1,6 +1,6 @@ -import 'package:didvan/pages/authentication/widgets/authentication_layout.dart'; -import 'package:didvan/widgets/didvan/button.dart'; -import 'package:didvan/widgets/didvan/text_field.dart'; +import 'package:didvan/views/authentication/widgets/authentication_layout.dart'; +import 'package:didvan/views/widgets/didvan/button.dart'; +import 'package:didvan/views/widgets/didvan/text_field.dart'; import 'package:flutter/material.dart'; class ResetPassword extends StatelessWidget { diff --git a/lib/pages/authentication/screens/username.dart b/lib/views/authentication/screens/username.dart similarity index 92% rename from lib/pages/authentication/screens/username.dart rename to lib/views/authentication/screens/username.dart index 395db9d..fcec2fd 100644 --- a/lib/pages/authentication/screens/username.dart +++ b/lib/views/authentication/screens/username.dart @@ -1,7 +1,7 @@ -import 'package:didvan/pages/authentication/authentication_state.dart'; -import 'package:didvan/pages/authentication/widgets/authentication_layout.dart'; -import 'package:didvan/widgets/didvan/button.dart'; -import 'package:didvan/widgets/didvan/text_field.dart'; +import 'package:didvan/views/authentication/authentication_state.dart'; +import 'package:didvan/views/authentication/widgets/authentication_layout.dart'; +import 'package:didvan/views/widgets/didvan/button.dart'; +import 'package:didvan/views/widgets/didvan/text_field.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/lib/pages/authentication/screens/verification.dart b/lib/views/authentication/screens/verification.dart similarity index 90% rename from lib/pages/authentication/screens/verification.dart rename to lib/views/authentication/screens/verification.dart index e6f5d4f..80bfeef 100644 --- a/lib/pages/authentication/screens/verification.dart +++ b/lib/views/authentication/screens/verification.dart @@ -1,9 +1,9 @@ import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; -import 'package:didvan/pages/authentication/authentication_state.dart'; -import 'package:didvan/pages/authentication/widgets/authentication_layout.dart'; -import 'package:didvan/widgets/didvan/button.dart'; -import 'package:didvan/widgets/didvan/text.dart'; +import 'package:didvan/views/authentication/authentication_state.dart'; +import 'package:didvan/views/authentication/widgets/authentication_layout.dart'; +import 'package:didvan/views/widgets/didvan/button.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; import 'package:pin_code_fields/pin_code_fields.dart'; import 'package:provider/provider.dart'; diff --git a/lib/pages/authentication/widgets/authentication_app_bar.dart b/lib/views/authentication/widgets/authentication_app_bar.dart similarity index 85% rename from lib/pages/authentication/widgets/authentication_app_bar.dart rename to lib/views/authentication/widgets/authentication_app_bar.dart index e9cb490..81e86f3 100644 --- a/lib/pages/authentication/widgets/authentication_app_bar.dart +++ b/lib/views/authentication/widgets/authentication_app_bar.dart @@ -1,8 +1,8 @@ import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; -import 'package:didvan/pages/authentication/authentication_state.dart'; -import 'package:didvan/widgets/didvan/icon_button.dart'; -import 'package:didvan/widgets/didvan/text.dart'; +import 'package:didvan/views/authentication/authentication_state.dart'; +import 'package:didvan/views/widgets/didvan/icon_button.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/lib/pages/authentication/widgets/authentication_layout.dart b/lib/views/authentication/widgets/authentication_layout.dart similarity index 92% rename from lib/pages/authentication/widgets/authentication_layout.dart rename to lib/views/authentication/widgets/authentication_layout.dart index 4d063d4..e3fceca 100644 --- a/lib/pages/authentication/widgets/authentication_layout.dart +++ b/lib/views/authentication/widgets/authentication_layout.dart @@ -1,5 +1,5 @@ -import 'package:didvan/pages/authentication/widgets/authentication_app_bar.dart'; -import 'package:didvan/widgets/logos/didvan_horizontal_logo.dart'; +import 'package:didvan/views/authentication/widgets/authentication_app_bar.dart'; +import 'package:didvan/views/widgets/logos/didvan_horizontal_logo.dart'; import 'package:flutter/material.dart'; class AuthenticationLayout extends StatelessWidget { diff --git a/lib/pages/home/comments/comments.dart b/lib/views/home/comments/comments.dart similarity index 93% rename from lib/pages/home/comments/comments.dart rename to lib/views/home/comments/comments.dart index ce45163..0bf0c72 100644 --- a/lib/pages/home/comments/comments.dart +++ b/lib/views/home/comments/comments.dart @@ -2,14 +2,14 @@ import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/view/app_bar_data.dart'; -import 'package:didvan/pages/home/comments/comments_state.dart'; -import 'package:didvan/pages/home/comments/widgets/comment_item.dart'; -import 'package:didvan/widgets/animated_visibility.dart'; -import 'package:didvan/widgets/didvan/icon_button.dart'; -import 'package:didvan/widgets/didvan/scaffold.dart'; -import 'package:didvan/widgets/didvan/text.dart'; -import 'package:didvan/widgets/shimmer_placeholder.dart'; -import 'package:didvan/widgets/state_handlers/sliver_state_handler.dart'; +import 'package:didvan/views/home/comments/comments_state.dart'; +import 'package:didvan/views/home/comments/widgets/comment_item.dart'; +import 'package:didvan/views/widgets/animated_visibility.dart'; +import 'package:didvan/views/widgets/didvan/icon_button.dart'; +import 'package:didvan/views/widgets/didvan/scaffold.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/shimmer_placeholder.dart'; +import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/lib/pages/home/comments/comments_state.dart b/lib/views/home/comments/comments_state.dart similarity index 100% rename from lib/pages/home/comments/comments_state.dart rename to lib/views/home/comments/comments_state.dart diff --git a/lib/pages/home/comments/widgets/comment_item.dart b/lib/views/home/comments/widgets/comment_item.dart similarity index 96% rename from lib/pages/home/comments/widgets/comment_item.dart rename to lib/views/home/comments/widgets/comment_item.dart index 1230d14..62fc0a2 100644 --- a/lib/pages/home/comments/widgets/comment_item.dart +++ b/lib/views/home/comments/widgets/comment_item.dart @@ -2,13 +2,13 @@ import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/comment/comment.dart'; -import 'package:didvan/pages/home/comments/comments_state.dart'; import 'package:didvan/utils/date_time.dart'; -import 'package:didvan/widgets/animated_visibility.dart'; -import 'package:didvan/widgets/didvan/icon_button.dart'; -import 'package:didvan/widgets/didvan/text.dart'; -import 'package:didvan/widgets/ink_wrapper.dart'; -import 'package:didvan/widgets/skeleton_image.dart'; +import 'package:didvan/views/home/comments/comments_state.dart'; +import 'package:didvan/views/widgets/animated_visibility.dart'; +import 'package:didvan/views/widgets/didvan/icon_button.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/ink_wrapper.dart'; +import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/lib/views/home/direct/direct.dart b/lib/views/home/direct/direct.dart new file mode 100644 index 0000000..e3cf43e --- /dev/null +++ b/lib/views/home/direct/direct.dart @@ -0,0 +1,94 @@ +import 'package:didvan/models/enums.dart'; +import 'package:didvan/models/view/app_bar_data.dart'; +import 'package:didvan/providers/server_data_provider.dart'; +import 'package:didvan/views/home/direct/direct_state.dart'; +import 'package:didvan/views/home/direct/widgets/message.dart'; +import 'package:didvan/views/home/direct/widgets/message_box.dart'; +import 'package:didvan/views/widgets/didvan/scaffold.dart'; +import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:provider/provider.dart'; + +class Direct extends StatefulWidget { + final Map pageData; + const Direct({Key? key, required this.pageData}) : super(key: key); + + @override + State createState() => _DirectState(); +} + +class _DirectState extends State { + @override + void initState() { + final state = context.read(); + state.replyRadar = widget.pageData['radarAttachment']; + final typeId = ServerDataProvider.labelToTypeId(widget.pageData['type']); + state.typeId = typeId; + Future.delayed(Duration.zero, () { + state.getMessages(); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + final state = context.watch(); + final d = MediaQuery.of(context); + return Material( + child: Stack( + children: [ + Positioned( + top: 0, + bottom: 56, + left: 0, + right: 0, + child: DidvanScaffold( + reverse: true, + backgroundColor: Theme.of(context).colorScheme.surface, + appBarData: AppBarData( + hasBack: true, + subtitle: 'ارتباط با سردبیر', + title: widget.pageData['type'] ?? 'پشتیبانی اپلیکیشن', + ), + slivers: [ + if (state.appState != AppState.busy) + SliverPadding( + padding: state.replyRadar == null + ? EdgeInsets.zero + : const EdgeInsets.only(bottom: 68), + sliver: SliverStateHandler( + itemPadding: const EdgeInsets.only(bottom: 12), + state: state, + builder: (context, state, index) => Message( + message: state.messages[index], + ), + childCount: state.messages.length, + onRetry: state.getMessages, + ), + ), + ], + children: [ + if (state.appState == AppState.busy) + SizedBox( + height: d.size.height - kToolbarHeight - d.padding.top, + child: Center( + child: SpinKitSpinningLines( + color: Theme.of(context).colorScheme.primary, + ), + ), + ), + ], + ), + ), + Positioned( + bottom: d.viewInsets.bottom, + right: 0, + left: 0, + child: const MessageBox(), + ), + ], + ), + ); + } +} diff --git a/lib/views/home/direct/direct_state.dart b/lib/views/home/direct/direct_state.dart new file mode 100644 index 0000000..86cf3a9 --- /dev/null +++ b/lib/views/home/direct/direct_state.dart @@ -0,0 +1,134 @@ +import 'dart:io'; + +import 'package:didvan/models/enums.dart'; +import 'package:didvan/models/message_data/message_data.dart'; +import 'package:didvan/models/message_data/radar_attachment.dart'; +import 'package:didvan/providers/core_provider.dart'; +import 'package:didvan/services/network/request.dart'; +import 'package:didvan/services/network/request_helper.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_vibrate/flutter_vibrate.dart'; +import 'package:record/record.dart'; + +class DirectState extends CoreProvier { + final _recorder = Record(); + final List messages = []; + late final int typeId; + final Map> dailyMessages = {}; + + String? text; + RadarAttachment? replyRadar; + File? recordedFile; + int? audioDuration; + + bool isRecording = false; + + Future getMessages() async { + appState = AppState.busy; + final RequestService service = RequestService(RequestHelper.direct(typeId)); + await service.httpGet(); + if (service.isSuccess) { + final messageDatas = service.result['messages']; + for (var i = 0; i < messageDatas.length; i++) { + messages.add(MessageData.fromJson(messageDatas[i])); + _addToDailyGrouped(); + } + appState = AppState.idle; + return; + } + appState = AppState.failed; + } + + void deleteRecordedFile() { + recordedFile!.delete(); + recordedFile = null; + notifyListeners(); + } + + Future startRecording() async { + await _recorder.hasPermission(); + if (!kIsWeb) { + Vibrate.feedback(FeedbackType.medium); + } + isRecording = true; + _recorder.start(); + notifyListeners(); + } + + Future stopRecording({required bool sendImidiately}) async { + final path = await _recorder.stop(); + isRecording = false; + if (path == null) { + notifyListeners(); + return; + } + if (kIsWeb) { + final uri = Uri.file(path); + recordedFile = File.fromUri(uri); + } else { + recordedFile = File(path); + } + if (sendImidiately) { + await sendMessage(); + } else { + notifyListeners(); + } + } + + void _addToDailyGrouped() { + final createdAt = messages.last.createdAt.split('T').first; + if (!dailyMessages.containsKey(createdAt)) { + dailyMessages.addAll({ + createdAt: [messages.last.id] + }); + } else { + dailyMessages[createdAt]!.add(messages.last.id); + } + } + + Future sendMessage() async { + if ((text == null || text!.isEmpty) && recordedFile == null) return; + replyRadar = null; + messages.insert( + 0, + MessageData( + id: 0, + writedByAdmin: false, + readed: false, + createdAt: + DateTime.now().subtract(const Duration(minutes: 210)).toString(), + text: text, + audio: null, + audioFile: recordedFile, + radar: replyRadar, + audioDuration: audioDuration, + ), + ); + _addToDailyGrouped(); + final body = {}; + if (text != null) { + body.addAll({'text': text}); + } + if (replyRadar != null) { + body.addAll({'radarId': replyRadar!.id}); + } + final uploadFile = recordedFile; + text = null; + recordedFile = null; + notifyListeners(); + final service = + RequestService(RequestHelper.sendDirectMessage(typeId), body: body); + if (uploadFile == null) { + service.post(); + } else { + service.multipart( + file: uploadFile, + method: 'POST', + fieldName: 'audio', + fileName: 'voice-message', + mediaExtension: 'm4a', + mediaFormat: 'audio', + ); + } + } +} diff --git a/lib/views/home/direct/widgets/audio_widget.dart b/lib/views/home/direct/widgets/audio_widget.dart new file mode 100644 index 0000000..5bb7ba7 --- /dev/null +++ b/lib/views/home/direct/widgets/audio_widget.dart @@ -0,0 +1,35 @@ +import 'dart:io'; + +import 'package:didvan/services/media/media.dart'; +import 'package:didvan/views/home/widgets/audio_slider.dart'; +import 'package:didvan/views/home/widgets/player_controller_button.dart'; +import 'package:flutter/material.dart'; + +class AudioWidget extends StatelessWidget { + final String? audioUrl; + final File? audioFile; + const AudioWidget({Key? key, this.audioUrl, this.audioFile}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: MediaService.audioPlayer.playingStream, + builder: (context, snapshot) { + return Row( + children: [ + Expanded( + child: AudioSlider( + tag: audioUrl ?? audioFile!.path, + ), + ), + AudioControllerButton( + audioFile: audioFile, + audioUrl: audioUrl, + ), + ], + ); + }, + ); + } +} diff --git a/lib/views/home/direct/widgets/message.dart b/lib/views/home/direct/widgets/message.dart new file mode 100644 index 0000000..818b2ff --- /dev/null +++ b/lib/views/home/direct/widgets/message.dart @@ -0,0 +1,187 @@ +import 'package:didvan/config/design_config.dart'; +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/models/message_data/message_data.dart'; +import 'package:didvan/utils/date_time.dart'; +import 'package:didvan/views/home/direct/direct_state.dart'; +import 'package:didvan/views/home/direct/widgets/audio_widget.dart'; +import 'package:didvan/views/widgets/didvan/divider.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/skeleton_image.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:persian_number_utility/persian_number_utility.dart'; + +class Message extends StatelessWidget { + final MessageData message; + const Message({Key? key, required this.message}) : super(key: key); + + @override + Widget build(BuildContext context) { + final firstMessageOfGroupId = context + .read() + .dailyMessages[message.createdAt.replaceAll('T', ' ').split(' ').first]! + .last; + return Column( + crossAxisAlignment: message.writedByAdmin + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + children: [ + if (message.id == firstMessageOfGroupId) + Center( + child: Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.splash, + borderRadius: DesignConfig.lowBorderRadius, + ), + child: DidvanText( + DateTime.parse(message.createdAt).toPersianDateStr(), + style: Theme.of(context).textTheme.overline, + color: DesignConfig.isDark + ? Theme.of(context).colorScheme.white + : Theme.of(context).colorScheme.black, + ), + ), + ), + Padding( + padding: EdgeInsets.only( + right: message.writedByAdmin ? 20 : 0, + left: !message.writedByAdmin ? 20 : 0, + ), + child: Column( + crossAxisAlignment: message.writedByAdmin + ? CrossAxisAlignment.start + : CrossAxisAlignment.end, + children: [ + _MessageContainer( + writedByAdmin: message.writedByAdmin, + child: Column( + children: [ + if (message.text != null) DidvanText(message.text!), + if (message.audio != null || message.audioFile != null) + AudioWidget( + audioFile: message.audioFile, + audioUrl: message.audio, + ), + if (message.radar != null) const DidvanDivider(), + if (message.radar != null) const SizedBox(height: 4), + if (message.radar != null) + _ReplyRadarOverview(message: message), + if (message.radar != null) const SizedBox(height: 4), + ], + ), + ), + const SizedBox(height: 4), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + DidvanText( + DateTimeUtils.timeWithAmPm(message.createdAt), + style: Theme.of(context).textTheme.overline, + color: Theme.of(context).colorScheme.caption, + ), + if (!message.writedByAdmin) + Icon( + message.readed + ? DidvanIcons.check_double_light + : DidvanIcons.check_light, + size: 16, + ) + ], + ), + ], + ), + ), + ], + ); + } +} + +class _ReplyRadarOverview extends StatelessWidget { + final MessageData message; + const _ReplyRadarOverview({Key? key, required this.message}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SkeletonImage( + imageUrl: message.radar!.image, + height: 52, + width: 52, + ), + const SizedBox(width: 8), + Expanded( + child: SizedBox( + height: 52, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + DidvanText( + message.radar!.title, + style: Theme.of(context).textTheme.bodyText1, + maxLines: 1, + overflow: TextOverflow.ellipsis, + color: Theme.of(context).colorScheme.focusedBorder, + ), + Row( + children: [ + DidvanText( + 'رادار ' + message.radar!.categories.first.label, + style: Theme.of(context).textTheme.overline, + color: Theme.of(context).colorScheme.focusedBorder, + ), + const Spacer(), + DidvanText( + DateTimeUtils.momentGenerator(message.radar!.createdAt) + + ' | خواندن در ' + + message.radar!.timeToRead.toString() + + ' دقیقه', + color: Theme.of(context).colorScheme.focusedBorder, + style: Theme.of(context).textTheme.overline, + ), + // DidvanText('text'), + ], + ), + ], + ), + ), + ), + ], + ); + } +} + +class _MessageContainer extends StatelessWidget { + final bool writedByAdmin; + final Widget child; + const _MessageContainer({ + Key? key, + required this.writedByAdmin, + required this.child, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + decoration: BoxDecoration( + borderRadius: DesignConfig.mediumBorderRadius.copyWith( + bottomLeft: writedByAdmin ? Radius.zero : null, + bottomRight: !writedByAdmin ? Radius.zero : null, + ), + color: writedByAdmin ? null : Theme.of(context).colorScheme.focused, + border: Border.all( + color: Theme.of(context).colorScheme.border, + width: 0.5, + ), + ), + child: child, + ); + } +} diff --git a/lib/views/home/direct/widgets/message_box.dart b/lib/views/home/direct/widgets/message_box.dart new file mode 100644 index 0000000..090f409 --- /dev/null +++ b/lib/views/home/direct/widgets/message_box.dart @@ -0,0 +1,220 @@ +import 'package:didvan/config/design_config.dart'; +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/views/home/direct/direct_state.dart'; +import 'package:didvan/views/home/direct/widgets/audio_widget.dart'; +import 'package:didvan/views/widgets/didvan/icon_button.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class MessageBox extends StatelessWidget { + const MessageBox({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Consumer( + builder: (context, state, child) => state.replyRadar != null + ? _MessageBoxContainer( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const DidvanText( + 'لینک به مطلب:', + ), + DidvanText( + state.replyRadar!.title, + overflow: TextOverflow.ellipsis, + maxLines: 1, + color: Theme.of(context).colorScheme.primary, + ), + ], + ), + ), + DidvanIconButton( + icon: DidvanIcons.close_regular, + gestureSize: 24, + onPressed: () { + state.replyRadar = null; + state.update(); + }, + ), + ], + ), + ), + ) + : const SizedBox(), + ), + _MessageBoxContainer( + child: Consumer( + builder: (context, state, child) { + if (state.isRecording) { + return const _Recording(); + } else if (!state.isRecording && state.recordedFile != null) { + return const _RecordChecking(); + } + return const _Typing(); + }, + ), + ), + ], + ); + } +} + +class _MessageBoxContainer extends StatelessWidget { + final Widget child; + const _MessageBoxContainer({Key? key, required this.child}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + height: 68, + decoration: BoxDecoration( + border: Border( + top: BorderSide( + color: Theme.of(context).colorScheme.cardBorder, + ), + ), + color: Theme.of(context).colorScheme.surface, + ), + child: child, + ); + } +} + +class _Typing extends StatefulWidget { + const _Typing({Key? key}) : super(key: key); + + @override + State<_Typing> createState() => _TypingState(); +} + +class _TypingState extends State<_Typing> { + final _formKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + final state = context.read(); + return Row( + children: [ + Expanded( + flex: 2, + child: AnimatedSwitcher( + duration: DesignConfig.lowAnimationDuration, + transitionBuilder: (child, animation) => ScaleTransition( + scale: animation, + child: child, + ), + child: state.text != null && state.text!.isNotEmpty + ? DidvanIconButton( + key: const ValueKey(1), + icon: DidvanIcons.send_solid, + onPressed: () { + _formKey.currentState!.reset(); + state.sendMessage(); + }, + size: 32, + color: Theme.of(context).colorScheme.focusedBorder, + ) + : DidvanIconButton( + key: const ValueKey(2), + icon: DidvanIcons.mic_solid, + onPressed: state.startRecording, + size: 32, + color: Theme.of(context).colorScheme.focusedBorder, + ), + ), + ), + Expanded( + flex: 15, + child: Form( + key: _formKey, + child: TextFormField( + textInputAction: TextInputAction.send, + style: Theme.of(context).textTheme.bodyText2, + decoration: InputDecoration( + border: InputBorder.none, + hintText: 'بنویسید یا پیام صوتی بگذارید...', + hintStyle: Theme.of(context).textTheme.caption!.copyWith( + color: Theme.of(context).colorScheme.disabledText), + ), + onChanged: (value) { + if (value.length <= 1) { + setState(() {}); + } + state.text = value; + }, + ), + ), + ), + ], + ); + } +} + +class _Recording extends StatelessWidget { + const _Recording({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final state = context.read(); + return Row( + children: [ + DidvanIconButton( + icon: DidvanIcons.send_solid, + onPressed: () => state.stopRecording(sendImidiately: true), + gestureSize: 52, + ), + Expanded( + child: DidvanText( + 'در حال ضبط صدا ...', + style: Theme.of(context).textTheme.caption, + ), + ), + DidvanIconButton( + icon: DidvanIcons.stop_circle_solid, + color: Theme.of(context).colorScheme.secondary, + onPressed: () => state.stopRecording(sendImidiately: false), + size: 32, + ), + ], + ); + } +} + +class _RecordChecking extends StatelessWidget { + const _RecordChecking({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final state = context.read(); + return Row( + children: [ + DidvanIconButton( + icon: DidvanIcons.send_solid, + onPressed: state.sendMessage, + color: Theme.of(context).colorScheme.focusedBorder, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: AudioWidget(audioFile: state.recordedFile!), + ), + ), + DidvanIconButton( + icon: DidvanIcons.trash_solid, + color: Theme.of(context).colorScheme.secondary, + onPressed: state.deleteRecordedFile, + ), + ], + ); + } +} diff --git a/lib/views/home/hashtag/hashtag.dart b/lib/views/home/hashtag/hashtag.dart new file mode 100644 index 0000000..eb95e99 --- /dev/null +++ b/lib/views/home/hashtag/hashtag.dart @@ -0,0 +1,60 @@ +import 'package:didvan/models/tag.dart'; +import 'package:didvan/models/view/app_bar_data.dart'; +import 'package:didvan/views/home/hashtag/hashtag_state.dart'; +import 'package:didvan/views/home/widgets/news_overview.dart'; +import 'package:didvan/views/home/widgets/radar_overview.dart'; +import 'package:didvan/views/widgets/didvan/scaffold.dart'; +import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class Hashtag extends StatefulWidget { + final Tag tag; + const Hashtag({Key? key, required this.tag}) : super(key: key); + + @override + _HashtagState createState() => _HashtagState(); +} + +class _HashtagState extends State { + @override + void initState() { + final state = context.read(); + state.id = widget.tag.id; + Future.delayed(Duration.zero, () => state.getTagItems(page: 1)); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return DidvanScaffold( + appBarData: AppBarData(title: widget.tag.label, hasBack: true), + slivers: [ + Consumer( + builder: (context, state, child) => SliverStateHandler( + state: state, + builder: (context, state, index) { + final item = state.items[index]; + final type = item.type; + if (type == 'radar') { + return RadarOverview( + radar: item, + onCommentsChanged: (id, count) => item.comments = count, + onMarkChanged: (id, value) => item.marked = value, + ); + } else if (type == 'news') { + return NewsOverview( + news: item, + onMarkChanged: (id, value) => item.marked = value, + ); + } + return Container(); + }, + childCount: state.items.length, + onRetry: () {}, + ), + ) + ], + ); + } +} diff --git a/lib/views/home/hashtag/hashtag_state.dart b/lib/views/home/hashtag/hashtag_state.dart new file mode 100644 index 0000000..3dcb2f3 --- /dev/null +++ b/lib/views/home/hashtag/hashtag_state.dart @@ -0,0 +1,34 @@ +import 'package:didvan/models/enums.dart'; +import 'package:didvan/models/overview_data.dart'; +import 'package:didvan/providers/core_provider.dart'; +import 'package:didvan/services/network/request.dart'; +import 'package:didvan/services/network/request_helper.dart'; + +class HashtagState extends CoreProvier { + final List items = []; + + late final int id; + int page = 1; + + Future getTagItems({required int page}) async { + this.page = page; + if (this.page == 1) { + appState = AppState.busy; + } + final service = RequestService(RequestHelper.tag( + ids: [id], + limit: 15, + page: page, + )); + await service.httpGet(); + if (service.isSuccess) { + final contents = service.result['contents']; + for (var i = 0; i < contents.length; i++) { + items.add(OverviewData.fromJson(contents[i])); + } + appState = AppState.idle; + return; + } + appState = AppState.failed; + } +} diff --git a/lib/pages/home/home.dart b/lib/views/home/home.dart similarity index 78% rename from lib/pages/home/home.dart rename to lib/views/home/home.dart index 880e84a..0e1d5a8 100644 --- a/lib/pages/home/home.dart +++ b/lib/views/home/home.dart @@ -1,11 +1,11 @@ import 'package:didvan/config/design_config.dart'; -import 'package:didvan/pages/home/home_state.dart'; -import 'package:didvan/pages/home/news/news.dart'; -import 'package:didvan/pages/home/radar/radar.dart'; -import 'package:didvan/pages/home/settings/settings.dart'; -import 'package:didvan/pages/home/statistics/statistics.dart'; -import 'package:didvan/pages/home/studio/studio.dart'; -import 'package:didvan/pages/home/widgets/bnb.dart'; +import 'package:didvan/views/home/home_state.dart'; +import 'package:didvan/views/home/news/news.dart'; +import 'package:didvan/views/home/radar/radar.dart'; +import 'package:didvan/views/home/settings/settings.dart'; +import 'package:didvan/views/home/statistics/statistics.dart'; +import 'package:didvan/views/home/studio/studio.dart'; +import 'package:didvan/views/home/widgets/bnb.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/lib/pages/home/home_state.dart b/lib/views/home/home_state.dart similarity index 100% rename from lib/pages/home/home_state.dart rename to lib/views/home/home_state.dart diff --git a/lib/pages/home/news/news.dart b/lib/views/home/news/news.dart similarity index 82% rename from lib/pages/home/news/news.dart rename to lib/views/home/news/news.dart index 2adeb13..df3950e 100644 --- a/lib/pages/home/news/news.dart +++ b/lib/views/home/news/news.dart @@ -4,15 +4,15 @@ import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/enums.dart'; import 'package:didvan/models/requests/news.dart'; import 'package:didvan/models/view/action_sheet_data.dart'; -import 'package:didvan/pages/home/news/news_state.dart'; -import 'package:didvan/pages/home/widgets/news_overview.dart'; import 'package:didvan/utils/action_sheet.dart'; -import 'package:didvan/pages/home/widgets/date_picker_button.dart'; -import 'package:didvan/widgets/item_title.dart'; -import 'package:didvan/pages/home/widgets/search_field.dart'; -import 'package:didvan/pages/home/widgets/logo_app_bar.dart'; -import 'package:didvan/widgets/state_handlers/empty_result.dart'; -import 'package:didvan/widgets/state_handlers/sliver_state_handler.dart'; +import 'package:didvan/views/home/news/news_state.dart'; +import 'package:didvan/views/home/widgets/date_picker_button.dart'; +import 'package:didvan/views/home/widgets/logo_app_bar.dart'; +import 'package:didvan/views/home/widgets/news_overview.dart'; +import 'package:didvan/views/home/widgets/search_field.dart'; +import 'package:didvan/views/widgets/item_title.dart'; +import 'package:didvan/views/widgets/state_handlers/empty_result.dart'; +import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -55,6 +55,14 @@ class _NewsState extends State { onRetry: () => state.getNews(page: state.page), state: state, builder: (context, state, index) { + index += 2; + if (index % 15 == 0 && index / 15 >= state.page) { + state.getNews(page: index ~/ 15 + 1); + } + index -= 2; + if (index >= state.news.length) { + return NewsOverview.placeholder; + } final news = state.news[index]; return NewsOverview( news: news, @@ -71,7 +79,8 @@ class _NewsState extends State { emptyState: EmptyResult( onNewSearch: () => _focusNode.requestFocus(), ), - childCount: state.news.length, + childCount: + state.news.length + (state.lastPage == state.page ? 0 : 3), itemPadding: const EdgeInsets.only(left: 16, right: 16, bottom: 16), placeholder: NewsOverview.placeholder, ), diff --git a/lib/pages/home/news/news_details/news_details.dart b/lib/views/home/news/news_details/news_details.dart similarity index 67% rename from lib/pages/home/news/news_details/news_details.dart rename to lib/views/home/news/news_details/news_details.dart index 9b55a2d..55d09d4 100644 --- a/lib/pages/home/news/news_details/news_details.dart +++ b/lib/views/home/news/news_details/news_details.dart @@ -1,8 +1,8 @@ import 'package:didvan/models/requests/news.dart'; -import 'package:didvan/pages/home/news/news_details/news_details_state.dart'; -import 'package:didvan/widgets/didvan/page_view.dart'; -import 'package:didvan/pages/home/widgets/floating_navigation_bar.dart'; -import 'package:didvan/widgets/state_handlers/state_handler.dart'; +import 'package:didvan/views/home/news/news_details/news_details_state.dart'; +import 'package:didvan/views/home/widgets/floating_navigation_bar.dart'; +import 'package:didvan/views/widgets/didvan/page_view.dart'; +import 'package:didvan/views/widgets/state_handlers/state_handler.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -20,6 +20,13 @@ class _NewsDetailsState extends State { @override void initState() { final state = context.read(); + _scrollController.addListener(() { + if (_scrollController.position.pixels > + _scrollController.position.maxScrollExtent - 200 && + !state.relatedQueue.contains(state.currentNews.id)) { + state.getRelatedContents(); + } + }); state.args = widget.pageData['args'] ?? const NewsRequestArgs(page: 0); Future.delayed(Duration.zero, () { state.getNewsDetails(widget.pageData['id']); @@ -37,13 +44,16 @@ class _NewsDetailsState extends State { builder: (context, state) => Stack( children: [ if (state.news.isNotEmpty) - DidvanPageView( - isRadar: false, - initialIndex: state.initialIndex, - onPageChanged: _onPageChnaged, - scrollController: _scrollController, - items: state.news, - currentIndex: state.currentIndex, + IgnorePointer( + ignoring: state.isFetchingNewItem, + child: DidvanPageView( + isRadar: false, + initialIndex: state.initialIndex, + onPageChanged: _onPageChnaged, + scrollController: _scrollController, + items: state.news, + currentIndex: state.currentIndex, + ), ), if (state.news.isNotEmpty) Positioned( @@ -54,10 +64,7 @@ class _NewsDetailsState extends State { hasUnmarkConfirmation: widget.pageData['hasUnmarkConfirmation'], scrollController: _scrollController, - comments: state.currentNews.comments, - id: state.currentNews.id, - marked: state.currentNews.marked, - title: state.currentNews.title, + item: state.currentNews, onCommentsChanged: state.onCommentsChanged, onMarkChanged: (value) => widget.pageData['onMarkChanged']( state.currentNews.id, diff --git a/lib/pages/home/news/news_details/news_details_state.dart b/lib/views/home/news/news_details/news_details_state.dart similarity index 75% rename from lib/pages/home/news/news_details/news_details_state.dart rename to lib/views/home/news/news_details/news_details_state.dart index ba97db3..879a178 100644 --- a/lib/pages/home/news/news_details/news_details_state.dart +++ b/lib/views/home/news/news_details/news_details_state.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'package:didvan/models/enums.dart'; import 'package:didvan/models/news_details_data.dart'; +import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/requests/news.dart'; import 'package:didvan/providers/core_provider.dart'; import 'package:didvan/services/network/request.dart'; @@ -14,6 +15,8 @@ class NewsDetailsState extends CoreProvier { late final NewsRequestArgs args; late Timer _trackingTimer; int _trackingTimerCounter = 0; + bool isFetchingNewItem = false; + final List relatedQueue = []; int _currentIndex = 0; int get currentIndex => _currentIndex; @@ -23,6 +26,9 @@ class NewsDetailsState extends CoreProvier { Future getNewsDetails(int id, {bool? isForward}) async { if (isForward == null) { appState = AppState.busy; + } else { + isFetchingNewItem = true; + notifyListeners(); } final service = RequestService(RequestHelper.newsDetails(id, args)); await service.httpGet(); @@ -65,6 +71,7 @@ class NewsDetailsState extends CoreProvier { } _currentIndex--; } + isFetchingNewItem = false; appState = AppState.idle; return; } @@ -92,6 +99,28 @@ class NewsDetailsState extends CoreProvier { _trackingTimerCounter = 0; } + Future getRelatedContents() async { + if (currentNews.relatedContents.isNotEmpty) return; + relatedQueue.add(currentNews.id); + final service = RequestService(RequestHelper.tag( + ids: currentNews.tags.map((tag) => tag.id).toList(), + itemId: currentNews.id, + type: 'news', + )); + await service.httpGet(); + if (service.isSuccess) { + final relateds = service.result['contents']; + for (var i = 0; i < relateds.length; i++) { + news + .where((element) => element != null) + .firstWhere((element) => element!.id == currentNews.id)! + .relatedContents + .add(OverviewData.fromJson(relateds[i])); + } + notifyListeners(); + } + } + @override void dispose() { _trackingTimer.cancel(); diff --git a/lib/pages/home/news/news_state.dart b/lib/views/home/news/news_state.dart similarity index 84% rename from lib/pages/home/news/news_state.dart rename to lib/views/home/news/news_state.dart index 2c54cef..47f3366 100644 --- a/lib/pages/home/news/news_state.dart +++ b/lib/views/home/news/news_state.dart @@ -1,5 +1,5 @@ import 'package:didvan/models/enums.dart'; -import 'package:didvan/models/news_overview.dart'; +import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/requests/news.dart'; import 'package:didvan/providers/core_provider.dart'; import 'package:didvan/providers/user_provider.dart'; @@ -12,8 +12,9 @@ class NewsState extends CoreProvier { String? startDate; String? endDate; int page = 1; + int lastPage = 0; - final List news = []; + final List news = []; void init() { search = ''; @@ -34,19 +35,21 @@ class NewsState extends CoreProvier { Future getNews({ required int page, }) async { - if (this.page == page) { + this.page = page; + if (this.page == 1) { news.clear(); } - this.page = page; if (search != '') { lastSearch = search; } lastSearch = search; - appState = AppState.busy; + if (page == 1) { + appState = AppState.busy; + } final service = RequestService( RequestHelper.newsOverviews( args: NewsRequestArgs( - page: 1, + page: page, startDate: startDate?.split(' ').first, endDate: endDate?.split(' ').first, search: search == '' ? null : search, @@ -55,9 +58,10 @@ class NewsState extends CoreProvier { ); await service.httpGet(); if (service.isSuccess) { + lastPage = service.result['lastPage']; final newsList = service.result['news']; for (var i = 0; i < newsList.length; i++) { - news.add(NewsOverviewData.fromJson(newsList[i])); + news.add(OverviewData.fromJson(newsList[i])); } appState = AppState.idle; return; diff --git a/lib/pages/home/radar/radar.dart b/lib/views/home/radar/radar.dart similarity index 85% rename from lib/pages/home/radar/radar.dart rename to lib/views/home/radar/radar.dart index a6bbf3b..0c97ddb 100644 --- a/lib/pages/home/radar/radar.dart +++ b/lib/views/home/radar/radar.dart @@ -9,20 +9,20 @@ import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/enums.dart'; import 'package:didvan/models/requests/radar.dart'; import 'package:didvan/models/view/action_sheet_data.dart'; -import 'package:didvan/pages/home/radar/radar_state.dart'; -import 'package:didvan/pages/home/radar/widgets/categories_gird.dart'; -import 'package:didvan/pages/home/radar/widgets/categories_list.dart'; -import 'package:didvan/pages/home/widgets/radar_overview.dart'; -import 'package:didvan/widgets/animated_visibility.dart'; -import 'package:didvan/pages/home/widgets/search_field.dart'; -import 'package:didvan/pages/home/widgets/logo_app_bar.dart'; +import 'package:didvan/views/home/radar/radar_state.dart'; +import 'package:didvan/views/home/radar/widgets/categories_gird.dart'; +import 'package:didvan/views/home/radar/widgets/categories_list.dart'; +import 'package:didvan/views/home/widgets/date_picker_button.dart'; +import 'package:didvan/views/home/widgets/logo_app_bar.dart'; import 'package:didvan/utils/action_sheet.dart'; -import 'package:didvan/pages/home/widgets/date_picker_button.dart'; -import 'package:didvan/widgets/didvan/checkbox.dart'; -import 'package:didvan/widgets/didvan/text.dart'; -import 'package:didvan/widgets/item_title.dart'; -import 'package:didvan/widgets/state_handlers/empty_result.dart'; -import 'package:didvan/widgets/state_handlers/sliver_state_handler.dart'; +import 'package:didvan/views/home/widgets/radar_overview.dart'; +import 'package:didvan/views/home/widgets/search_field.dart'; +import 'package:didvan/views/widgets/animated_visibility.dart'; +import 'package:didvan/views/widgets/didvan/checkbox.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/item_title.dart'; +import 'package:didvan/views/widgets/state_handlers/empty_result.dart'; +import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -113,7 +113,7 @@ class _RadarState extends State { ), ), SliverStateHandler( - onRetry: () => state.getRadarOverviewDatas(page: state.page), + onRetry: () => state.getRadars(page: state.page), state: state, itemPadding: const EdgeInsets.only( bottom: 20, @@ -128,6 +128,14 @@ class _RadarState extends State { ), placeholder: RadarOverview.placeholder, builder: (context, state, index) { + index += 2; + if (index % 15 == 0 && index / 15 >= state.page) { + state.getRadars(page: index ~/ 15 + 1); + } + index -= 2; + if (index >= state.radars.length) { + return RadarOverview.placeholder; + } final radar = state.radars[index]; return RadarOverview( radar: radar, @@ -145,7 +153,8 @@ class _RadarState extends State { ), ); }, - childCount: state.radars.length, + childCount: state.radars.length + + (state.lastPage == state.page ? 0 : 3), ), if (state.radars.length == 1) const SliverToBoxAdapter( @@ -155,7 +164,9 @@ class _RadarState extends State { ), if (state.appState != AppState.failed) CategoriesRow1(), if (state.appState != AppState.failed) CategoriesRow2(), - if (state.appState != AppState.failed && !state.searching) + if (state.appState != AppState.failed && + !state.searching && + !state.filtering) CategoriesList(), ], ), @@ -170,7 +181,7 @@ class _RadarState extends State { _timer?.cancel(); _timer = Timer(const Duration(seconds: 1), () { state.search = value; - state.getRadarOverviewDatas(page: 1); + state.getRadars(page: 1); }); } @@ -215,7 +226,7 @@ class _RadarState extends State { dismissTitle: 'حذف فیلتر', confrimTitle: 'نمایش نتایج', onDismissed: () => state.resetFilters(false), - onConfirmed: () => state.getRadarOverviewDatas(page: 1), + onConfirmed: () => state.getRadars(page: 1), content: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/pages/home/radar/radar_details/radar_details.dart b/lib/views/home/radar/radar_details/radar_details.dart similarity index 70% rename from lib/pages/home/radar/radar_details/radar_details.dart rename to lib/views/home/radar/radar_details/radar_details.dart index 6b27000..1c9eb11 100644 --- a/lib/pages/home/radar/radar_details/radar_details.dart +++ b/lib/views/home/radar/radar_details/radar_details.dart @@ -1,8 +1,8 @@ import 'package:didvan/models/requests/radar.dart'; -import 'package:didvan/pages/home/radar/radar_details/radar_details_state.dart'; -import 'package:didvan/widgets/didvan/page_view.dart'; -import 'package:didvan/pages/home/widgets/floating_navigation_bar.dart'; -import 'package:didvan/widgets/state_handlers/state_handler.dart'; +import 'package:didvan/views/home/radar/radar_details/radar_details_state.dart'; +import 'package:didvan/views/home/widgets/floating_navigation_bar.dart'; +import 'package:didvan/views/widgets/didvan/page_view.dart'; +import 'package:didvan/views/widgets/state_handlers/state_handler.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -20,6 +20,13 @@ class _RadarDetailsState extends State { @override void initState() { final state = context.read(); + _scrollController.addListener(() { + if (_scrollController.position.pixels > + _scrollController.position.maxScrollExtent - 200 && + !state.relatedQueue.contains(state.currentRadar.id)) { + state.getRelatedContents(); + } + }); state.args = widget.pageData['args'] ?? const RadarRequestArgs(page: 0); Future.delayed(Duration.zero, () { state.getRadarDetails(widget.pageData['id']); @@ -37,13 +44,16 @@ class _RadarDetailsState extends State { builder: (context, state) => Stack( children: [ if (state.radars.isNotEmpty) - DidvanPageView( - isRadar: true, - initialIndex: state.initialIndex, - onPageChanged: _onPageChanged, - scrollController: _scrollController, - items: state.radars, - currentIndex: state.currentIndex, + IgnorePointer( + ignoring: state.isFetchingNewItem, + child: DidvanPageView( + isRadar: true, + initialIndex: state.initialIndex, + onPageChanged: _onPageChanged, + scrollController: _scrollController, + items: state.radars, + currentIndex: state.currentIndex, + ), ), if (state.radars.isNotEmpty) Positioned( @@ -53,18 +63,14 @@ class _RadarDetailsState extends State { child: FloatingNavigationBar( hasUnmarkConfirmation: widget.pageData['hasUnmarkConfirmation'], - comments: state.currentRadar.comments, - id: state.currentRadar.id, isRadar: true, - marked: state.currentRadar.marked, - title: state.currentRadar.title, + scrollController: _scrollController, onMarkChanged: (value) => widget.pageData['onMarkChanged']?.call( state.currentRadar.id, value, ), - categories: state.currentRadar.categories, - scrollController: _scrollController, + item: state.currentRadar, onCommentsChanged: (count) { state.onCommentsChanged(count); widget.pageData['onCommentsChanged']?.call( diff --git a/lib/pages/home/radar/radar_details/radar_details_state.dart b/lib/views/home/radar/radar_details/radar_details_state.dart similarity index 76% rename from lib/pages/home/radar/radar_details/radar_details_state.dart rename to lib/views/home/radar/radar_details/radar_details_state.dart index 61f9ccf..1e6c4e8 100644 --- a/lib/pages/home/radar/radar_details/radar_details_state.dart +++ b/lib/views/home/radar/radar_details/radar_details_state.dart @@ -2,11 +2,13 @@ import 'dart:async'; import 'dart:math'; import 'package:didvan/models/enums.dart'; +import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/radar_details_data.dart'; import 'package:didvan/models/requests/radar.dart'; import 'package:didvan/providers/core_provider.dart'; import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request_helper.dart'; +import 'package:flutter/material.dart'; class RadarDetailsState extends CoreProvier { final List radars = []; @@ -14,6 +16,8 @@ class RadarDetailsState extends CoreProvier { int _trackingTimerCounter = 0; late final int initialIndex; late final RadarRequestArgs args; + bool isFetchingNewItem = false; + final List relatedQueue = []; int _currentIndex = 0; int get currentIndex => _currentIndex; @@ -29,6 +33,9 @@ class RadarDetailsState extends CoreProvier { Future getRadarDetails(int id, {bool? isForward}) async { if (isForward == null) { appState = AppState.busy; + } else { + isFetchingNewItem = true; + notifyListeners(); } final service = RequestService(RequestHelper.radarDetails(id, args)); await service.httpGet(); @@ -74,6 +81,7 @@ class RadarDetailsState extends CoreProvier { } _currentIndex--; } + isFetchingNewItem = false; appState = AppState.idle; return; } @@ -83,6 +91,28 @@ class RadarDetailsState extends CoreProvier { } } + Future getRelatedContents() async { + if (currentRadar.relatedContents.isNotEmpty) return; + relatedQueue.add(currentRadar.id); + final service = RequestService(RequestHelper.tag( + ids: currentRadar.tags.map((tag) => tag.id).toList(), + itemId: currentRadar.id, + type: 'radar', + )); + await service.httpGet(); + if (service.isSuccess) { + final relateds = service.result['contents']; + for (var i = 0; i < relateds.length; i++) { + radars + .where((element) => element != null) + .firstWhere((element) => element!.id == currentRadar.id)! + .relatedContents + .add(OverviewData.fromJson(relateds[i])); + } + notifyListeners(); + } + } + bool exists(RadarDetailsData? radar) => radars.any((r) => radar != null && r != null && r.id == radar.id); diff --git a/lib/pages/home/radar/radar_state.dart b/lib/views/home/radar/radar_state.dart similarity index 87% rename from lib/pages/home/radar/radar_state.dart rename to lib/views/home/radar/radar_state.dart index dd3fe11..75318ee 100644 --- a/lib/pages/home/radar/radar_state.dart +++ b/lib/views/home/radar/radar_state.dart @@ -1,8 +1,8 @@ import 'package:didvan/constants/assets.dart'; import 'package:didvan/models/enums.dart'; +import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/requests/radar.dart'; import 'package:didvan/models/view/radar_category.dart'; -import 'package:didvan/models/radar_overview.dart'; import 'package:didvan/providers/core_provider.dart'; import 'package:didvan/providers/user_provider.dart'; import 'package:didvan/services/network/request.dart'; @@ -14,11 +14,12 @@ class RadarState extends CoreProvier { String? startDate; String? endDate; int page = 1; + int lastPage = 1; bool isScrolled = false; bool shouldColapse = false; final List selectedCats = []; List categories = []; - final List radars = []; + final List radars = []; bool get filtering => selectedCats.length > 1 || startDate != null || endDate != null; @@ -38,19 +39,21 @@ class RadarState extends CoreProvier { lastSearch = ''; isScrolled = false; if (!isInit) { - notifyListeners(); + getRadars(page: 1); } } - Future getRadarOverviewDatas({ + Future getRadars({ required int page, }) async { + this.page = page; if (this.page == page) { radars.clear(); } - this.page = page; lastSearch = search; - appState = AppState.busy; + if (page == 1) { + appState = AppState.busy; + } final RequestService service = RequestService( RequestHelper.radarOverviews( args: RadarRequestArgs( @@ -64,8 +67,10 @@ class RadarState extends CoreProvier { ); await service.httpGet(); if (service.isSuccess) { - for (var i = 0; i < service.result['radars'].length; i++) { - radars.add(RadarOverviewData.fromJson(service.result['radars'][i])); + lastPage = service.result['lastPage']; + final radarsList = service.result['radars']; + for (var i = 0; i < radarsList.length; i++) { + radars.add(OverviewData.fromJson(radarsList[i])); } if (searching || filtering || isColapsed || isCategorySelected) { shouldColapse = true; @@ -91,7 +96,7 @@ class RadarState extends CoreProvier { void init() { resetFilters(true); Future.delayed(Duration.zero, () { - getRadarOverviewDatas(page: 1); + getRadars(page: 1); }); categories = [ RadarCategory( diff --git a/lib/pages/home/radar/widgets/categories_gird.dart b/lib/views/home/radar/widgets/categories_gird.dart similarity index 94% rename from lib/pages/home/radar/widgets/categories_gird.dart rename to lib/views/home/radar/widgets/categories_gird.dart index 9818b02..abcc0e2 100644 --- a/lib/pages/home/radar/widgets/categories_gird.dart +++ b/lib/views/home/radar/widgets/categories_gird.dart @@ -1,6 +1,6 @@ import 'package:didvan/config/design_config.dart'; -import 'package:didvan/pages/home/radar/radar_state.dart'; -import 'package:didvan/pages/home/radar/widgets/category_item.dart'; +import 'package:didvan/views/home/radar/radar_state.dart'; +import 'package:didvan/views/home/radar/widgets/category_item.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/lib/pages/home/radar/widgets/categories_list.dart b/lib/views/home/radar/widgets/categories_list.dart similarity index 95% rename from lib/pages/home/radar/widgets/categories_list.dart rename to lib/views/home/radar/widgets/categories_list.dart index 6546c61..bca0187 100644 --- a/lib/pages/home/radar/widgets/categories_list.dart +++ b/lib/views/home/radar/widgets/categories_list.dart @@ -1,9 +1,9 @@ import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/models/view/radar_category.dart'; -import 'package:didvan/pages/home/radar/radar_state.dart'; -import 'package:didvan/widgets/animated_visibility.dart'; -import 'package:didvan/widgets/didvan/text.dart'; +import 'package:didvan/views/home/radar/radar_state.dart'; +import 'package:didvan/views/widgets/animated_visibility.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -98,7 +98,7 @@ class _CategoriesListState extends State { duration: DesignConfig.lowAnimationDuration, curve: Curves.easeIn, ); - state.getRadarOverviewDatas(page: 1); + state.getRadars(page: 1); }, child: Container( margin: const EdgeInsets.only(left: 12), diff --git a/lib/pages/home/radar/widgets/category_item.dart b/lib/views/home/radar/widgets/category_item.dart similarity index 91% rename from lib/pages/home/radar/widgets/category_item.dart rename to lib/views/home/radar/widgets/category_item.dart index f545396..f8176a5 100644 --- a/lib/pages/home/radar/widgets/category_item.dart +++ b/lib/views/home/radar/widgets/category_item.dart @@ -1,9 +1,9 @@ import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/models/view/radar_category.dart'; -import 'package:didvan/pages/home/radar/radar_state.dart'; -import 'package:didvan/widgets/animated_visibility.dart'; -import 'package:didvan/widgets/didvan/text.dart'; +import 'package:didvan/views/home/radar/radar_state.dart'; +import 'package:didvan/views/widgets/animated_visibility.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:provider/provider.dart'; @@ -29,7 +29,7 @@ class CategoryItem extends StatelessWidget { if (category.id != 0) { state.selectedCats.add(category); } - state.getRadarOverviewDatas(page: 1); + state.getRadars(page: 1); }, child: AnimatedContainer( duration: DesignConfig.mediumAnimationDuration, diff --git a/lib/pages/home/settings/about_us/about_us.dart b/lib/views/home/settings/about_us/about_us.dart similarity index 95% rename from lib/pages/home/settings/about_us/about_us.dart rename to lib/views/home/settings/about_us/about_us.dart index 037057d..6cc17f9 100644 --- a/lib/pages/home/settings/about_us/about_us.dart +++ b/lib/views/home/settings/about_us/about_us.dart @@ -1,6 +1,6 @@ import 'package:didvan/models/view/app_bar_data.dart'; -import 'package:didvan/widgets/didvan/scaffold.dart'; -import 'package:didvan/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/didvan/scaffold.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; class AboutUs extends StatelessWidget { diff --git a/lib/pages/home/settings/bookmarks/bookmark_state.dart b/lib/views/home/settings/bookmarks/bookmark_state.dart similarity index 95% rename from lib/pages/home/settings/bookmarks/bookmark_state.dart rename to lib/views/home/settings/bookmarks/bookmark_state.dart index 5786b50..1807179 100644 --- a/lib/pages/home/settings/bookmarks/bookmark_state.dart +++ b/lib/views/home/settings/bookmarks/bookmark_state.dart @@ -1,5 +1,5 @@ import 'package:didvan/models/enums.dart'; -import 'package:didvan/models/item_overview.dart'; +import 'package:didvan/models/overview_data.dart'; import 'package:didvan/providers/core_provider.dart'; import 'package:didvan/providers/user_provider.dart'; import 'package:didvan/services/network/request.dart'; @@ -36,7 +36,7 @@ class BookmarksState extends CoreProvier { if (value) return; final type = bookmarks.firstWhere((element) => element.id == id).type; switch (type) { - case 'radars': + case 'radar': UserProvider.changeRadarMark(id, value); break; case 'news': diff --git a/lib/pages/home/settings/bookmarks/bookmarks.dart b/lib/views/home/settings/bookmarks/bookmarks.dart similarity index 82% rename from lib/pages/home/settings/bookmarks/bookmarks.dart rename to lib/views/home/settings/bookmarks/bookmarks.dart index 6d1474a..1cb736a 100644 --- a/lib/pages/home/settings/bookmarks/bookmarks.dart +++ b/lib/views/home/settings/bookmarks/bookmarks.dart @@ -3,19 +3,19 @@ import 'dart:async'; import 'package:didvan/config/design_config.dart'; import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/view/app_bar_data.dart'; -import 'package:didvan/pages/home/settings/bookmarks/bookmark_state.dart'; -import 'package:didvan/pages/home/widgets/menu_item.dart'; import 'package:didvan/routes/routes.dart'; -import 'package:didvan/widgets/animated_visibility.dart'; -import 'package:didvan/widgets/didvan/card.dart'; -import 'package:didvan/widgets/didvan/divider.dart'; -import 'package:didvan/widgets/didvan/scaffold.dart'; -import 'package:didvan/pages/home/widgets/multitype_overview.dart'; -import 'package:didvan/pages/home/widgets/search_field.dart'; -import 'package:didvan/widgets/item_title.dart'; -import 'package:didvan/widgets/state_handlers/empty_list.dart'; -import 'package:didvan/widgets/state_handlers/empty_result.dart'; -import 'package:didvan/widgets/state_handlers/sliver_state_handler.dart'; +import 'package:didvan/views/home/settings/bookmarks/bookmark_state.dart'; +import 'package:didvan/views/home/widgets/menu_item.dart'; +import 'package:didvan/views/home/widgets/multitype_overview.dart'; +import 'package:didvan/views/home/widgets/search_field.dart'; +import 'package:didvan/views/widgets/animated_visibility.dart'; +import 'package:didvan/views/widgets/didvan/card.dart'; +import 'package:didvan/views/widgets/didvan/divider.dart'; +import 'package:didvan/views/widgets/didvan/scaffold.dart'; +import 'package:didvan/views/widgets/item_title.dart'; +import 'package:didvan/views/widgets/state_handlers/empty_list.dart'; +import 'package:didvan/views/widgets/state_handlers/empty_result.dart'; +import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -60,7 +60,7 @@ class _BookmarksState extends State { child: Column( children: [ MenuItem( - onTap: () => _onCategorySelected('radars'), + onTap: () => _onCategorySelected('radar'), title: 'تحلیل‌های رادار', icon: DidvanIcons.radar_regular, iconSize: 24, @@ -122,7 +122,7 @@ class _BookmarksState extends State { } void _onCategorySelected(String type) { - if (type != 'radars' && type != 'news') return; + if (type != 'radar' && type != 'news') return; FocusScope.of(context).unfocus(); Navigator.of(context).pushNamed(Routes.filteredBookmarks, arguments: type); } diff --git a/lib/pages/home/settings/bookmarks/filtered_bookmark/filtered_bookmark.dart b/lib/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmark.dart similarity index 75% rename from lib/pages/home/settings/bookmarks/filtered_bookmark/filtered_bookmark.dart rename to lib/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmark.dart index ac5bee1..f9a5454 100644 --- a/lib/pages/home/settings/bookmarks/filtered_bookmark/filtered_bookmark.dart +++ b/lib/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmark.dart @@ -1,12 +1,10 @@ -import 'package:didvan/models/news_overview.dart'; -import 'package:didvan/models/radar_overview.dart'; import 'package:didvan/models/view/app_bar_data.dart'; -import 'package:didvan/pages/home/settings/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart'; -import 'package:didvan/pages/home/widgets/news_overview.dart'; -import 'package:didvan/pages/home/widgets/radar_overview.dart'; -import 'package:didvan/widgets/didvan/scaffold.dart'; -import 'package:didvan/widgets/state_handlers/empty_list.dart'; -import 'package:didvan/widgets/state_handlers/sliver_state_handler.dart'; +import 'package:didvan/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart'; +import 'package:didvan/views/home/widgets/news_overview.dart'; +import 'package:didvan/views/home/widgets/radar_overview.dart'; +import 'package:didvan/views/widgets/didvan/scaffold.dart'; +import 'package:didvan/views/widgets/state_handlers/empty_list.dart'; +import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -29,13 +27,13 @@ class _FilteredBookmarksState extends State { String get _appBarTitle { switch (context.read().type) { - case 'radars': + case 'radar': return 'تحلیل‌های رادار'; case 'news': return 'اخبار'; - case 'videos': + case 'video': return 'ویدئو‌ها'; - case 'podcasts': + case 'podcast': return 'پادکست‌ها'; default: return 'پادکست‌ها'; @@ -56,16 +54,16 @@ class _FilteredBookmarksState extends State { placeholder: RadarOverview.placeholder, emptyState: const EmptyList(), builder: (context, state, index) { - if (state.type == 'radars') { + if (state.type == 'radar') { return RadarOverview( - radar: state.bookmarks[index] as RadarOverviewData, + radar: state.bookmarks[index], onMarkChanged: _onBookmarkChanged, onCommentsChanged: state.onCommentsChanged, hasUnmarkConfirmation: true, ); } return NewsOverview( - news: state.bookmarks[index] as NewsOverviewData, + news: state.bookmarks[index], onMarkChanged: _onBookmarkChanged, hasUnmarkConfirmation: true, ); diff --git a/lib/pages/home/settings/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart b/lib/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart similarity index 66% rename from lib/pages/home/settings/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart rename to lib/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart index ff5d20b..fffd1dc 100644 --- a/lib/pages/home/settings/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart +++ b/lib/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart @@ -1,7 +1,5 @@ import 'package:didvan/models/enums.dart'; -import 'package:didvan/models/item_overview.dart'; -import 'package:didvan/models/news_overview.dart'; -import 'package:didvan/models/radar_overview.dart'; +import 'package:didvan/models/overview_data.dart'; import 'package:didvan/providers/core_provider.dart'; import 'package:didvan/providers/user_provider.dart'; import 'package:didvan/services/network/request.dart'; @@ -22,20 +20,14 @@ class FilteredBookmarksState extends CoreProvier { lastSearch = search; } appState = AppState.busy; - final service = RequestService( - RequestHelper.bookmarks(type: type == 'radars' ? 'radar' : type)); + final service = RequestService(RequestHelper.bookmarks(type: type)); await service.httpGet(); if (service.isSuccess) { - final marks = service.result[type]; + final marks = service.result[type != 'news' ? type + 's' : type]; bookmarks.clear(); for (var i = 0; i < marks.length; i++) { - if (type == 'radars') { - bookmarks.add(RadarOverviewData.fromJson(marks[i])); - } - if (type == 'news') { - bookmarks.add(NewsOverviewData.fromJson(marks[i])); - } + bookmarks.add(OverviewData.fromJson(marks[i])); } appState = AppState.idle; return; @@ -45,7 +37,7 @@ class FilteredBookmarksState extends CoreProvier { void onMarkChanged(int id, bool value) { switch (type) { - case 'radars': + case 'radar': UserProvider.changeRadarMark(id, value); break; case 'news': @@ -58,8 +50,7 @@ class FilteredBookmarksState extends CoreProvier { } void onCommentsChanged(int id, int value) { - (bookmarks.firstWhere((radar) => radar.id == id) as RadarOverviewData) - .comments = value; + bookmarks.firstWhere((radar) => radar.id == id).comments = value; notifyListeners(); } } diff --git a/lib/pages/home/settings/direct_list/direct_list.dart b/lib/views/home/settings/direct_list/direct_list.dart similarity index 84% rename from lib/pages/home/settings/direct_list/direct_list.dart rename to lib/views/home/settings/direct_list/direct_list.dart index 5a13fc4..7c8d264 100644 --- a/lib/pages/home/settings/direct_list/direct_list.dart +++ b/lib/views/home/settings/direct_list/direct_list.dart @@ -1,13 +1,13 @@ import 'package:didvan/constants/assets.dart'; import 'package:didvan/models/view/app_bar_data.dart'; -import 'package:didvan/pages/home/settings/direct_list/direct_list_state.dart'; -import 'package:didvan/pages/home/settings/direct_list/widgets/chat_room_item.dart'; -import 'package:didvan/widgets/didvan/badge.dart'; -import 'package:didvan/widgets/didvan/divider.dart'; -import 'package:didvan/widgets/didvan/scaffold.dart'; -import 'package:didvan/widgets/shimmer_placeholder.dart'; -import 'package:didvan/widgets/state_handlers/empty_state.dart'; -import 'package:didvan/widgets/state_handlers/sliver_state_handler.dart'; +import 'package:didvan/views/home/settings/direct_list/direct_list_state.dart'; +import 'package:didvan/views/home/settings/direct_list/widgets/direct_item.dart'; +import 'package:didvan/views/widgets/didvan/badge.dart'; +import 'package:didvan/views/widgets/didvan/divider.dart'; +import 'package:didvan/views/widgets/didvan/scaffold.dart'; +import 'package:didvan/views/widgets/shimmer_placeholder.dart'; +import 'package:didvan/views/widgets/state_handlers/empty_state.dart'; +import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/lib/pages/home/settings/direct_list/direct_list_state.dart b/lib/views/home/settings/direct_list/direct_list_state.dart similarity index 100% rename from lib/pages/home/settings/direct_list/direct_list_state.dart rename to lib/views/home/settings/direct_list/direct_list_state.dart diff --git a/lib/pages/home/settings/direct_list/widgets/chat_room_item.dart b/lib/views/home/settings/direct_list/widgets/direct_item.dart similarity index 56% rename from lib/pages/home/settings/direct_list/widgets/chat_room_item.dart rename to lib/views/home/settings/direct_list/widgets/direct_item.dart index 16f3215..6aa9a45 100644 --- a/lib/pages/home/settings/direct_list/widgets/chat_room_item.dart +++ b/lib/views/home/settings/direct_list/widgets/direct_item.dart @@ -2,9 +2,10 @@ import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/chat_room/chat_room.dart'; import 'package:didvan/routes/routes.dart'; -import 'package:didvan/widgets/didvan/badge.dart'; -import 'package:didvan/widgets/didvan/divider.dart'; -import 'package:didvan/widgets/didvan/text.dart'; +import 'package:didvan/utils/date_time.dart'; +import 'package:didvan/views/widgets/didvan/badge.dart'; +import 'package:didvan/views/widgets/didvan/divider.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; class ChatRoomItem extends StatelessWidget { @@ -14,10 +15,13 @@ class ChatRoomItem extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( - onTap: () => Navigator.of(context).pushNamed( - Routes.direct, - arguments: chatRoom.id, - ), + onTap: () { + Navigator.of(context).pushNamed( + Routes.direct, + arguments: {'type': chatRoom.type}, + ); + chatRoom.unread = 0; + }, child: Container( color: Colors.transparent, child: Column( @@ -35,15 +39,18 @@ class ChatRoomItem extends StatelessWidget { style: Theme.of(context).textTheme.bodyText1, ), ), - DidvanBadge(text: chatRoom.unread.toString()), + if (chatRoom.unread != 0) + DidvanBadge(text: chatRoom.unread.toString()), ], ), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(width: 40), - const Icon( - DidvanIcons.check_double_light, + Icon( + chatRoom.lastMessage.readed + ? DidvanIcons.check_double_light + : DidvanIcons.check_light, size: 16, ), const SizedBox(width: 4), @@ -51,12 +58,24 @@ class ChatRoomItem extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - DidvanText( - chatRoom.lastMessage.text ?? '', - maxLines: 1, + Row( + children: [ + if (chatRoom.lastMessage.text == null) + const Icon( + DidvanIcons.mic_light, + size: 18, + ), + Expanded( + child: DidvanText( + chatRoom.lastMessage.text ?? 'پیام صوتی', + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], ), DidvanText( - chatRoom.updatedAt, + DateTimeUtils.momentGenerator(chatRoom.updatedAt), style: Theme.of(context).textTheme.caption, color: Theme.of(context).colorScheme.caption, ) diff --git a/lib/pages/home/settings/general_settings/settings.dart b/lib/views/home/settings/general_settings/settings.dart similarity index 92% rename from lib/pages/home/settings/general_settings/settings.dart rename to lib/views/home/settings/general_settings/settings.dart index a02499b..c3cc3eb 100644 --- a/lib/pages/home/settings/general_settings/settings.dart +++ b/lib/views/home/settings/general_settings/settings.dart @@ -6,16 +6,16 @@ import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/constants/assets.dart'; import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/models/view/app_bar_data.dart'; -import 'package:didvan/pages/home/settings/general_settings/settings_state.dart'; -import 'package:didvan/pages/home/widgets/menu_item.dart'; import 'package:didvan/providers/theme_provider.dart'; import 'package:didvan/utils/action_sheet.dart'; -import 'package:didvan/widgets/didvan/card.dart'; -import 'package:didvan/widgets/didvan/divider.dart'; -import 'package:didvan/widgets/didvan/scaffold.dart'; -import 'package:didvan/widgets/didvan/text.dart'; -import 'package:didvan/widgets/item_title.dart'; -import 'package:didvan/widgets/state_handlers/state_handler.dart'; +import 'package:didvan/views/home/settings/general_settings/settings_state.dart'; +import 'package:didvan/views/home/widgets/menu_item.dart'; +import 'package:didvan/views/widgets/didvan/card.dart'; +import 'package:didvan/views/widgets/didvan/divider.dart'; +import 'package:didvan/views/widgets/didvan/scaffold.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/item_title.dart'; +import 'package:didvan/views/widgets/state_handlers/state_handler.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:provider/provider.dart'; diff --git a/lib/pages/home/settings/general_settings/settings_state.dart b/lib/views/home/settings/general_settings/settings_state.dart similarity index 100% rename from lib/pages/home/settings/general_settings/settings_state.dart rename to lib/views/home/settings/general_settings/settings_state.dart diff --git a/lib/pages/home/settings/profile/profile.dart b/lib/views/home/settings/profile/profile.dart similarity index 91% rename from lib/pages/home/settings/profile/profile.dart rename to lib/views/home/settings/profile/profile.dart index 0dfb2f3..071d6ac 100644 --- a/lib/pages/home/settings/profile/profile.dart +++ b/lib/views/home/settings/profile/profile.dart @@ -2,17 +2,17 @@ import 'dart:async'; import 'package:didvan/config/design_config.dart'; import 'package:didvan/models/view/app_bar_data.dart'; -import 'package:didvan/pages/home/settings/profile/widgets/profile_photo.dart'; -import 'package:didvan/pages/home/widgets/menu_item.dart'; import 'package:didvan/providers/user_provider.dart'; import 'package:didvan/routes/routes.dart'; -import 'package:didvan/widgets/animated_visibility.dart'; -import 'package:didvan/widgets/didvan/button.dart'; -import 'package:didvan/widgets/didvan/card.dart'; -import 'package:didvan/widgets/didvan/divider.dart'; -import 'package:didvan/widgets/didvan/scaffold.dart'; -import 'package:didvan/widgets/didvan/text.dart'; -import 'package:didvan/widgets/didvan/text_field.dart'; +import 'package:didvan/views/home/settings/profile/widgets/profile_photo.dart'; +import 'package:didvan/views/home/widgets/menu_item.dart'; +import 'package:didvan/views/widgets/animated_visibility.dart'; +import 'package:didvan/views/widgets/didvan/button.dart'; +import 'package:didvan/views/widgets/didvan/card.dart'; +import 'package:didvan/views/widgets/didvan/divider.dart'; +import 'package:didvan/views/widgets/didvan/scaffold.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/didvan/text_field.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/lib/pages/home/settings/profile/widgets/profile_photo.dart b/lib/views/home/settings/profile/widgets/profile_photo.dart similarity index 50% rename from lib/pages/home/settings/profile/widgets/profile_photo.dart rename to lib/views/home/settings/profile/widgets/profile_photo.dart index 3b307ee..62f1018 100644 --- a/lib/pages/home/settings/profile/widgets/profile_photo.dart +++ b/lib/views/home/settings/profile/widgets/profile_photo.dart @@ -1,19 +1,22 @@ +import 'dart:io'; + import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/enums.dart'; +import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/models/view/alert_data.dart'; -import 'package:didvan/pages/home/widgets/menu_item.dart'; import 'package:didvan/providers/user_provider.dart'; -import 'package:didvan/routes/routes.dart'; import 'package:didvan/services/media/media.dart'; import 'package:didvan/utils/action_sheet.dart'; -import 'package:didvan/widgets/didvan/card.dart'; -import 'package:didvan/widgets/didvan/divider.dart'; -import 'package:didvan/widgets/didvan/text.dart'; -import 'package:didvan/widgets/skeleton_image.dart'; +import 'package:didvan/views/home/widgets/menu_item.dart'; +import 'package:didvan/views/widgets/didvan/divider.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/skeleton_image.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:provider/provider.dart'; +import 'package:image_cropper/image_cropper.dart'; class ProfilePhoto extends StatefulWidget { const ProfilePhoto({Key? key}) : super(key: key); @@ -78,60 +81,88 @@ class _ProfilePhotoState extends State { Future _openImagePickerSheet() async { FocusScope.of(context).unfocus(); - await showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.transparent, - builder: (context) => DidvanCard( - enableBorder: false, - child: Column( + await ActionSheetUtils.showBottomSheet( + data: ActionSheetData( + content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const DidvanText('بارگذاری تصویر از:'), const SizedBox(height: 16), - MenuItem( - title: 'دوربین', - onTap: () => _setProfilePhoto(ImageSource.camera), - icon: DidvanIcons.camera_regular, + Padding( + padding: const EdgeInsets.only(right: 20), + child: Column( + children: [ + MenuItem( + title: 'دوربین', + onTap: () => _setProfilePhoto(ImageSource.camera), + icon: DidvanIcons.camera_regular, + ), + const DidvanDivider(), + MenuItem( + title: 'گالری', + onTap: () => _setProfilePhoto(ImageSource.gallery), + icon: DidvanIcons.gallery_file_regular, + ), + ], + ), ), const DidvanDivider(), MenuItem( - title: 'گالری', - onTap: () => _setProfilePhoto(ImageSource.gallery), - icon: DidvanIcons.gallery_file_regular, + title: 'حذف تصویر', + onTap: () => _setProfilePhoto(null), + icon: DidvanIcons.trash_solid, + color: Theme.of(context).colorScheme.secondary, ), ], ), + hasConfirmButton: false, + hasDismissButton: false, + title: 'بارگذاری تصویر از:', ), ); } - Future _setProfilePhoto(ImageSource source) async { + Future _setProfilePhoto(ImageSource? source) async { ActionSheetUtils.pop(); - final pickedFile = await MediaService.pickImage(source: source); - if (pickedFile != null) { - ActionSheetUtils.showLogoLoadingIndicator(); - final bytes = await pickedFile.readAsBytes(); - ActionSheetUtils.pop(); - Navigator.of(context).pushNamed( - Routes.imageCropper, - arguments: { - 'bytes': bytes, - 'onCropped': () async { - final state = context.read(); - final result = await state.setProfilePhoto(pickedFile); - ActionSheetUtils.showAlert( - AlertData( - message: result - ? 'تصویر پروفایل بارگذاری شد.' - : 'بارگذاری تصویر موفقیت آمیز نبود.', - aLertType: result ? ALertType.success : ALertType.error, - ), - ); - } - }, + final state = context.read(); + if (source == null) { + final result = await state.deleteProfilePhoto(); + ActionSheetUtils.showAlert( + AlertData( + message: + result ? 'تصویر پروفایل حذف شد.' : 'حذف تصویر موفقیت آمیز نبود.', + aLertType: result ? ALertType.success : ALertType.error, + ), ); + return; } + final pickedFile = await MediaService.pickImage(source: source); + File? file; + if (pickedFile != null && !kIsWeb) { + file = await ImageCropper().cropImage( + sourcePath: pickedFile.path, + aspectRatio: const CropAspectRatio(ratioX: 1, ratioY: 1), + iosUiSettings: const IOSUiSettings( + title: 'برش تصویر', + doneButtonTitle: 'تایید', + cancelButtonTitle: 'بازگشت', + ), + androidUiSettings: const AndroidUiSettings(toolbarTitle: 'برش تصویر'), + compressQuality: 70, + ); + if (file == null) return; + } + if (pickedFile == null) return; + final uploadFile = kIsWeb ? pickedFile : file; + final result = await state.setProfilePhoto(uploadFile); + ActionSheetUtils.showAlert( + AlertData( + message: result + ? 'تصویر پروفایل بارگذاری شد.' + : 'بارگذاری تصویر موفقیت آمیز نبود.', + aLertType: result ? ALertType.success : ALertType.error, + ), + ); } } diff --git a/lib/pages/home/settings/settings.dart b/lib/views/home/settings/settings.dart similarity index 92% rename from lib/pages/home/settings/settings.dart rename to lib/views/home/settings/settings.dart index 5a4a048..7753d26 100644 --- a/lib/pages/home/settings/settings.dart +++ b/lib/views/home/settings/settings.dart @@ -1,14 +1,14 @@ import 'package:didvan/constants/app_icons.dart'; -import 'package:didvan/pages/home/widgets/menu_item.dart'; -import 'package:didvan/pages/home/widgets/logo_app_bar.dart'; import 'package:didvan/providers/theme_provider.dart'; import 'package:didvan/providers/user_provider.dart'; import 'package:didvan/routes/routes.dart'; import 'package:didvan/services/storage/storage.dart'; -import 'package:didvan/widgets/didvan/card.dart'; -import 'package:didvan/widgets/didvan/divider.dart'; -import 'package:didvan/widgets/didvan/text.dart'; -import 'package:didvan/widgets/item_title.dart'; +import 'package:didvan/views/home/widgets/logo_app_bar.dart'; +import 'package:didvan/views/home/widgets/menu_item.dart'; +import 'package:didvan/views/widgets/didvan/card.dart'; +import 'package:didvan/views/widgets/didvan/divider.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/item_title.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; diff --git a/lib/pages/home/statistics/statistics.dart b/lib/views/home/statistics/statistics.dart similarity index 83% rename from lib/pages/home/statistics/statistics.dart rename to lib/views/home/statistics/statistics.dart index e727b5a..eef8150 100644 --- a/lib/pages/home/statistics/statistics.dart +++ b/lib/views/home/statistics/statistics.dart @@ -1,7 +1,7 @@ import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/assets.dart'; -import 'package:didvan/pages/home/widgets/logo_app_bar.dart'; -import 'package:didvan/widgets/state_handlers/empty_state.dart'; +import 'package:didvan/views/home/widgets/logo_app_bar.dart'; +import 'package:didvan/views/widgets/state_handlers/empty_state.dart'; import 'package:flutter/material.dart'; class Statictics extends StatelessWidget { diff --git a/lib/views/home/studio/studio.dart b/lib/views/home/studio/studio.dart new file mode 100644 index 0000000..a6f675a --- /dev/null +++ b/lib/views/home/studio/studio.dart @@ -0,0 +1,57 @@ +import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/views/home/studio/widgets/slider.dart'; +import 'package:didvan/views/home/studio/widgets/tab_bar.dart'; +import 'package:didvan/views/home/widgets/logo_app_bar.dart'; +import 'package:didvan/views/home/widgets/search_field.dart'; +import 'package:didvan/views/widgets/didvan/icon_button.dart'; +import 'package:flutter/material.dart'; + +class Studio extends StatefulWidget { + const Studio({Key? key}) : super(key: key); + + @override + State createState() => _StudioState(); +} + +class _StudioState extends State { + final FocusNode _focusNode = FocusNode(); + + @override + Widget build(BuildContext context) { + return CustomScrollView( + slivers: [ + SliverToBoxAdapter( + child: Row( + children: [ + const Expanded(child: LogoAppBar(type: 'studio')), + Padding( + padding: + EdgeInsets.only(top: MediaQuery.of(context).padding.top), + child: DidvanIconButton( + icon: DidvanIcons.bookmark_regular, + onPressed: () {}, + ), + ), + ], + ), + ), + const SliverToBoxAdapter( + child: StudioTabBar(), + ), + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: SearchField( + title: 'جستجو در استودیو', + onChanged: (value) {}, + focusNode: _focusNode, + ), + ), + ), + const SliverToBoxAdapter( + child: StudioSlider(), + ), + ], + ); + } +} diff --git a/lib/views/home/studio/studio_state.dart b/lib/views/home/studio/studio_state.dart new file mode 100644 index 0000000..ffa0282 --- /dev/null +++ b/lib/views/home/studio/studio_state.dart @@ -0,0 +1,5 @@ +import 'package:didvan/providers/core_provider.dart'; + +class StudioState extends CoreProvier { + bool videosSelected = true; +} diff --git a/lib/views/home/studio/widgets/slider.dart b/lib/views/home/studio/widgets/slider.dart new file mode 100644 index 0000000..d312b2b --- /dev/null +++ b/lib/views/home/studio/widgets/slider.dart @@ -0,0 +1,51 @@ +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:didvan/config/theme_data.dart'; +import 'package:flutter/material.dart'; + +class StudioSlider extends StatelessWidget { + const StudioSlider({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + CarouselSlider( + items: [ + Image.network('https://wallpapercave.com/wp/wp10731650.jpg'), + Image.network('https://wallpapercave.com/wp/wp10731650.jpg'), + Image.network('https://wallpapercave.com/wp/wp10731650.jpg'), + Image.network('https://wallpapercave.com/wp/wp10731650.jpg'), + ], + options: CarouselOptions( + viewportFraction: 0.94, + aspectRatio: 16 / 9, + autoPlay: true, + ), + ), + Row(), + ], + ); + } +} + +class _SliderIndicator extends StatelessWidget { + final bool isCurrentIndex; + const _SliderIndicator({Key? key, required this.isCurrentIndex}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + height: 8, + width: 8, + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).colorScheme.focusedBorder, + ), + shape: BoxShape.circle, + color: + isCurrentIndex ? Theme.of(context).colorScheme.focusedBorder : null, + ), + ); + } +} diff --git a/lib/views/home/studio/widgets/tab_bar.dart b/lib/views/home/studio/widgets/tab_bar.dart new file mode 100644 index 0000000..c5caacb --- /dev/null +++ b/lib/views/home/studio/widgets/tab_bar.dart @@ -0,0 +1,100 @@ +import 'package:didvan/config/design_config.dart'; +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:flutter/material.dart'; + +class StudioTabBar extends StatelessWidget { + const StudioTabBar({ + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + border: Border.all(color: Theme.of(context).colorScheme.border), + borderRadius: DesignConfig.lowBorderRadius, + ), + child: Row( + children: [ + Expanded( + child: _StudioTypeButton( + icon: DidvanIcons.video_solid, + selectedColor: Theme.of(context).colorScheme.secondary, + title: 'ویدئو', + onTap: () {}, + isSelected: true, + ), + ), + Container( + width: 1, + height: 32, + color: Theme.of(context).colorScheme.border, + ), + Expanded( + child: _StudioTypeButton( + icon: DidvanIcons.podcast_solid, + selectedColor: Theme.of(context).colorScheme.focusedBorder, + title: 'پادکست', + onTap: () {}, + isSelected: true, + ), + ), + ], + ), + ); + } +} + +class _StudioTypeButton extends StatelessWidget { + final IconData icon; + final String title; + final VoidCallback onTap; + final bool isSelected; + final Color selectedColor; + const _StudioTypeButton({ + Key? key, + required this.icon, + required this.selectedColor, + required this.title, + required this.onTap, + required this.isSelected, + }) : super(key: key); + + Color? get _color => isSelected ? selectedColor : null; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + color: Colors.transparent, + child: Column( + children: [ + Icon( + icon, + size: 32, + color: _color, + ), + if (!isSelected) const SizedBox(height: 18), + if (isSelected) + Container( + width: 88, + height: 1, + color: _color, + ), + if (isSelected) + DidvanText( + title, + style: Theme.of(context).textTheme.overline, + color: _color, + ) + ], + ), + ), + ); + } +} diff --git a/lib/views/home/widgets/audio_slider.dart b/lib/views/home/widgets/audio_slider.dart new file mode 100644 index 0000000..b9edf70 --- /dev/null +++ b/lib/views/home/widgets/audio_slider.dart @@ -0,0 +1,39 @@ +import 'package:audio_video_progress_bar/audio_video_progress_bar.dart'; +import 'package:didvan/services/media/media.dart'; +import 'package:flutter/material.dart'; + +class AudioSlider extends StatelessWidget { + final String tag; + const AudioSlider({Key? key, required this.tag}) : super(key: key); + + bool get _isPlaying => MediaService.audioPlayerTag == tag; + + @override + Widget build(BuildContext context) { + return IgnorePointer( + ignoring: MediaService.audioPlayerTag != tag, + child: Directionality( + textDirection: TextDirection.ltr, + child: StreamBuilder( + stream: _isPlaying ? MediaService.audioPlayer.positionStream : null, + builder: (context, snapshot) { + return ProgressBar( + total: MediaService.audioPlayer.duration ?? Duration.zero, + progress: snapshot.data ?? Duration.zero, + buffered: + _isPlaying ? MediaService.audioPlayer.bufferedPosition : null, + thumbRadius: 6, + barHeight: 3, + timeLabelTextStyle: const TextStyle(fontSize: 0), + onSeek: (value) => _onSeek(value.inMilliseconds), + ); + }, + ), + ), + ); + } + + void _onSeek(int value) { + MediaService.audioPlayer.seek(Duration(milliseconds: value)); + } +} diff --git a/lib/views/home/widgets/audio_visualizer.dart b/lib/views/home/widgets/audio_visualizer.dart new file mode 100644 index 0000000..c39ca3d --- /dev/null +++ b/lib/views/home/widgets/audio_visualizer.dart @@ -0,0 +1,314 @@ +// import 'dart:io'; +// import 'dart:math'; + +// import 'package:didvan/config/design_config.dart'; +// import 'package:didvan/config/theme_data.dart'; +// import 'package:didvan/constants/app_icons.dart'; +// import 'package:didvan/constants/assets.dart'; +// import 'package:didvan/pages/home/direct/direct_state.dart'; +// import 'package:didvan/services/media/media.dart'; +// import 'package:didvan/services/storage/storage.dart'; +// import 'package:didvan/utils/date_time.dart'; +// import 'package:didvan/widgets/didvan/icon_button.dart'; +// import 'package:didvan/widgets/didvan/text.dart'; +// import 'package:flutter/foundation.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_svg/flutter_svg.dart'; +// import 'package:just_waveform/just_waveform.dart'; +// import 'package:provider/provider.dart'; + +// class AudioVisualizer extends StatefulWidget { +// final File? audioFile; +// final Waveform? waveform; +// final String? audioUrl; +// final int? duration; +// final Color? backgroundColor; + +// const AudioVisualizer({ +// Key? key, +// this.audioFile, +// this.waveform, +// this.audioUrl, +// this.duration, +// this.backgroundColor, +// }) : super(key: key); + +// @override +// State createState() => _AudioVisualizerState(); +// } + +// class _AudioVisualizerState extends State { +// Stream? waveDataStream; + +// @override +// void initState() { +// if (!kIsWeb && widget.audioFile != null) { +// waveDataStream = JustWaveform.extract( +// audioInFile: widget.audioFile!, +// waveOutFile: File(StorageService.appTempsDir + '/rec-wave.wave'), +// zoom: const WaveformZoom.pixelsPerSecond(100), +// ); +// } +// super.initState(); +// } + +// bool get _nowPlaying => +// MediaService.lastAudioPath == widget.audioFile || +// MediaService.lastAudioPath == widget.audioUrl; + +// @override +// Widget build(BuildContext context) { +// return Container( +// decoration: BoxDecoration( +// color: widget.backgroundColor ?? +// (DesignConfig.isDark +// ? Theme.of(context).colorScheme.black +// : Theme.of(context).colorScheme.background), +// borderRadius: DesignConfig.mediumBorderRadius, +// ), +// child: Row( +// children: [ +// const SizedBox(width: 12), +// StreamBuilder( +// stream: +// _nowPlaying ? MediaService.audioPlayer.positionStream : null, +// builder: (context, snapshot) { +// String text = ''; +// if (MediaService.audioPlayer.duration == null) { +// Future.delayed(Duration.zero, () { +// if (mounted) { +// setState(() {}); +// } +// }); +// } +// if (snapshot.data == null || snapshot.data == Duration.zero) { +// text = DateTimeUtils.normalizeTimeDuration( +// MediaService.audioPlayer.duration ?? +// widget.waveform?.duration ?? +// Duration.zero); +// } else { +// text = DateTimeUtils.normalizeTimeDuration(snapshot.data!); +// } +// return DidvanText( +// text, +// color: Theme.of(context).colorScheme.focusedBorder, +// isEnglishFont: true, +// ); +// }, +// ), +// const SizedBox(width: 12), +// Expanded( +// child: Builder( +// builder: (context) { +// if (kIsWeb) { +// return SvgPicture.asset(Assets.record); +// } +// if (widget.audioFile != null) { +// return StreamBuilder( +// stream: waveDataStream, +// builder: (context, snapshot) { +// if (snapshot.data == null || +// snapshot.data!.waveform == null) { +// return const SizedBox(); +// } +// final waveform = snapshot.data!.waveform!; +// context.read().waveform = waveform; +// return _waveWidget(waveform); +// }, +// ); +// } +// if (widget.waveform == null && waveDataStream == null) { +// return SvgPicture.asset(Assets.record); +// } +// return _waveWidget(widget.waveform!); +// }, +// ), +// ), +// StreamBuilder( +// stream: _nowPlaying ? MediaService.audioPlayer.playingStream : null, +// builder: (context, snapshot) { +// return DidvanIconButton( +// icon: snapshot.data == true +// ? DidvanIcons.pause_circle_solid +// : DidvanIcons.play_circle_solid, +// color: Theme.of(context).colorScheme.focusedBorder, +// onPressed: () { +// MediaService.handleAudioPlayback( +// audioSource: widget.audioFile ?? widget.audioUrl, +// isNetworkAudio: widget.audioFile == null, +// ); +// setState(() {}); +// }, +// ); +// }, +// ), +// ], +// ), +// ); +// } + +// Widget _waveWidget(Waveform waveform) => IgnorePointer( +// ignoring: !_nowPlaying, +// child: GestureDetector( +// onHorizontalDragUpdate: _changePosition, +// onTapDown: _changePosition, +// child: SizedBox( +// height: double.infinity, +// width: double.infinity, +// child: _AudioWaveformWidget( +// waveform: waveform, +// start: Duration.zero, +// scale: 2, +// strokeWidth: 3, +// nowPlaying: _nowPlaying, +// duration: waveform.duration, +// waveColor: Theme.of(context).colorScheme.focusedBorder, +// ), +// ), +// ), +// ); + +// void _changePosition(details) { +// if (MediaService.audioPlayer.audioSource == null) return; +// double posper = +// details.localPosition.dx / (MediaQuery.of(context).size.width - 200); +// if (posper >= 1 || posper < 0) return; +// final position = MediaService.audioPlayer.duration!.inMilliseconds; +// MediaService.audioPlayer.seek( +// Duration(milliseconds: (posper * position).toInt()), +// ); +// } +// } + +// class _AudioWaveformWidget extends StatelessWidget { +// final Color waveColor; +// final double scale; +// final double strokeWidth; +// final double pixelsPerStep; +// final Waveform waveform; +// final Duration start; +// final bool nowPlaying; +// final Duration duration; + +// const _AudioWaveformWidget({ +// Key? key, +// required this.waveform, +// required this.start, +// required this.duration, +// required this.nowPlaying, +// this.waveColor = Colors.blue, +// this.scale = 1.0, +// this.strokeWidth = 5.0, +// this.pixelsPerStep = 8.0, +// }) : super(key: key); + +// @override +// Widget build(BuildContext context) { +// return ClipRect( +// child: StreamBuilder( +// stream: nowPlaying ? MediaService.audioPlayer.positionStream : null, +// builder: (context, snapshot) { +// double progress = 0; +// if (snapshot.data == null || +// MediaService.audioPlayer.duration == null) { +// progress = 0; +// } else { +// progress = snapshot.data!.inMilliseconds / +// MediaService.audioPlayer.duration!.inMilliseconds * +// 100; +// } +// if (progress >= 100) { +// progress = 0; +// MediaService.audioPlayer.stop(); +// MediaService.audioPlayer.seek(Duration.zero); +// } +// return CustomPaint( +// painter: _AudioWaveformPainter( +// waveColor: waveColor, +// waveform: waveform, +// start: start, +// duration: duration, +// scale: scale, +// strokeWidth: strokeWidth, +// pixelsPerStep: pixelsPerStep, +// progressPercentage: progress, +// progressColor: Theme.of(context).colorScheme.focusedBorder, +// color: Theme.of(context).colorScheme.border, +// ), +// ); +// }), +// ); +// } +// } + +// class _AudioWaveformPainter extends CustomPainter { +// final double scale; +// final double strokeWidth; +// final double pixelsPerStep; +// final Waveform waveform; +// final Duration start; +// final Duration duration; +// final double progressPercentage; +// final Color progressColor; +// final Color color; + +// _AudioWaveformPainter({ +// required this.waveform, +// required this.start, +// required this.duration, +// required this.progressPercentage, +// required this.color, +// required this.progressColor, +// Color waveColor = Colors.blue, +// this.scale = 1.0, +// this.strokeWidth = 5.0, +// this.pixelsPerStep = 8.0, +// }); + +// @override +// void paint(Canvas canvas, Size size) { +// if (duration == Duration.zero) return; +// double width = size.width; +// double height = size.height; + +// final waveformPixelsPerWindow = waveform.positionToPixel(duration).toInt(); +// final waveformPixelsPerDevicePixel = waveformPixelsPerWindow / width; +// final waveformPixelsPerStep = waveformPixelsPerDevicePixel * pixelsPerStep; +// final sampleOffset = waveform.positionToPixel(start); +// final sampleStart = -sampleOffset % waveformPixelsPerStep; +// final totalLength = waveformPixelsPerWindow; +// final wavePaintB = Paint() +// ..style = PaintingStyle.stroke +// ..strokeWidth = strokeWidth +// ..strokeCap = StrokeCap.round +// ..color = progressColor; +// final wavePaintA = Paint() +// ..style = PaintingStyle.stroke +// ..strokeWidth = strokeWidth +// ..strokeCap = StrokeCap.round +// ..color = color; +// for (var i = sampleStart.toDouble(); +// i <= waveformPixelsPerWindow + 1.0; +// i += waveformPixelsPerStep) { +// final sampleIdx = (sampleOffset + i).toInt(); +// final x = i / waveformPixelsPerDevicePixel; +// final minY = normalise(waveform.getPixelMin(sampleIdx), height); +// final maxY = normalise(waveform.getPixelMax(sampleIdx), height); +// canvas.drawLine( +// Offset(x + strokeWidth / 2, max(strokeWidth * 0.75, minY)), +// Offset(x + strokeWidth / 2, min(height - strokeWidth * 0.75, maxY)), +// i / totalLength < progressPercentage / 100 ? wavePaintB : wavePaintA, +// ); +// } +// } + +// @override +// bool shouldRepaint(covariant _AudioWaveformPainter oldDelegate) { +// return oldDelegate.progressPercentage != progressPercentage; +// } + +// double normalise(int s, double height) { +// final y = 32768 + (scale * s).clamp(-32768.0, 32767.0).toDouble(); +// return height - 1 - y * height / 65536; +// } +// } diff --git a/lib/pages/home/widgets/bnb.dart b/lib/views/home/widgets/bnb.dart similarity index 98% rename from lib/pages/home/widgets/bnb.dart rename to lib/views/home/widgets/bnb.dart index 01e2927..42d8ad9 100644 --- a/lib/pages/home/widgets/bnb.dart +++ b/lib/views/home/widgets/bnb.dart @@ -1,7 +1,7 @@ import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; -import 'package:didvan/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; class DidvanBNB extends StatelessWidget { diff --git a/lib/pages/home/widgets/bookmark_button.dart b/lib/views/home/widgets/bookmark_button.dart similarity index 94% rename from lib/pages/home/widgets/bookmark_button.dart rename to lib/views/home/widgets/bookmark_button.dart index eb4aa46..4de2621 100644 --- a/lib/pages/home/widgets/bookmark_button.dart +++ b/lib/views/home/widgets/bookmark_button.dart @@ -1,8 +1,8 @@ import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/utils/action_sheet.dart'; -import 'package:didvan/widgets/didvan/icon_button.dart'; -import 'package:didvan/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/didvan/icon_button.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; class BookmarkButton extends StatefulWidget { diff --git a/lib/pages/home/widgets/date_picker_button.dart b/lib/views/home/widgets/date_picker_button.dart similarity index 97% rename from lib/pages/home/widgets/date_picker_button.dart rename to lib/views/home/widgets/date_picker_button.dart index a2ac7f7..ac5164c 100644 --- a/lib/pages/home/widgets/date_picker_button.dart +++ b/lib/views/home/widgets/date_picker_button.dart @@ -1,7 +1,7 @@ import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/utils/date_time.dart'; -import 'package:didvan/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; import 'package:persian_number_utility/persian_number_utility.dart'; diff --git a/lib/pages/home/widgets/floating_navigation_bar.dart b/lib/views/home/widgets/floating_navigation_bar.dart similarity index 81% rename from lib/pages/home/widgets/floating_navigation_bar.dart rename to lib/views/home/widgets/floating_navigation_bar.dart index 25bf4ef..3cc0323 100644 --- a/lib/pages/home/widgets/floating_navigation_bar.dart +++ b/lib/views/home/widgets/floating_navigation_bar.dart @@ -1,29 +1,25 @@ import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; -import 'package:didvan/models/category.dart'; +import 'package:didvan/models/message_data/radar_attachment.dart'; import 'package:didvan/models/view/action_sheet_data.dart'; -import 'package:didvan/pages/home/widgets/menu_item.dart'; import 'package:didvan/routes/routes.dart'; import 'package:didvan/utils/action_sheet.dart'; -import 'package:didvan/pages/home/widgets/bookmark_button.dart'; -import 'package:didvan/widgets/didvan/chip.dart'; -import 'package:didvan/widgets/didvan/divider.dart'; -import 'package:didvan/widgets/didvan/icon_button.dart'; -import 'package:didvan/widgets/didvan/text.dart'; -import 'package:didvan/widgets/item_title.dart'; +import 'package:didvan/views/home/widgets/bookmark_button.dart'; +import 'package:didvan/views/home/widgets/menu_item.dart'; +import 'package:didvan/views/widgets/didvan/chip.dart'; +import 'package:didvan/views/widgets/didvan/divider.dart'; +import 'package:didvan/views/widgets/didvan/icon_button.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/item_title.dart'; import 'package:flutter/material.dart'; class FloatingNavigationBar extends StatefulWidget { final ScrollController scrollController; + final dynamic item; final bool hasUnmarkConfirmation; final void Function(int count) onCommentsChanged; final bool isRadar; - final bool marked; - final int comments; - final int id; - final String title; - final List? categories; final void Function(bool value) onMarkChanged; const FloatingNavigationBar({ @@ -32,11 +28,7 @@ class FloatingNavigationBar extends StatefulWidget { required this.onCommentsChanged, required this.onMarkChanged, required this.isRadar, - required this.marked, - required this.comments, - required this.id, - required this.title, - this.categories, + required this.item, this.hasUnmarkConfirmation = false, }) : super(key: key); @@ -50,7 +42,9 @@ class _FloatingNavigationBarState extends State { @override void didUpdateWidget(covariant FloatingNavigationBar oldWidget) { - _comments = widget.comments; + _comments = widget.item.comments; + _isScrolled = false; + _handleScroll(); super.didUpdateWidget(oldWidget); } @@ -58,7 +52,7 @@ class _FloatingNavigationBarState extends State { void initState() { _handleScroll(); _isScrolled = false; - _comments = widget.comments; + _comments = widget.item.comments; super.initState(); } @@ -111,7 +105,7 @@ class _FloatingNavigationBarState extends State { if (widget.isRadar) BookmarkButton( askForConfirmation: widget.hasUnmarkConfirmation, - value: widget.marked, + value: widget.item.marked, onMarkChanged: (value) { widget.onMarkChanged(value); if (widget.hasUnmarkConfirmation && !value) { @@ -135,9 +129,9 @@ class _FloatingNavigationBarState extends State { onPressed: () => Navigator.of(context).pushNamed( Routes.comments, arguments: { - 'id': widget.id, + 'id': widget.item.id, 'isRadar': widget.isRadar, - 'title': widget.title, + 'title': widget.item.title, 'onCommentsChanged': widget.onCommentsChanged, }, ), @@ -150,7 +144,7 @@ class _FloatingNavigationBarState extends State { if (!widget.isRadar) BookmarkButton( askForConfirmation: widget.hasUnmarkConfirmation, - value: widget.marked, + value: widget.item.marked, onMarkChanged: (value) { widget.onMarkChanged(value); if (widget.hasUnmarkConfirmation && !value) { @@ -189,7 +183,7 @@ class _FloatingNavigationBarState extends State { } void _showMoreOptions() { - final categories = widget.categories!; + final categories = widget.item.categories!; ActionSheetUtils.showBottomSheet( data: ActionSheetData( content: Column( @@ -209,7 +203,19 @@ class _FloatingNavigationBarState extends State { Navigator.of(context).pop(); Navigator.of(context).pushNamed( Routes.direct, - arguments: categories[i].id, + arguments: { + 'radarAttachment': RadarAttachment( + id: widget.item.id, + title: widget.item.title, + description: widget.item.contents.first.text, + timeToRead: widget.item.timeToRead, + image: widget.item.image, + forManagers: widget.item.forManagers, + categories: widget.item.categories, + createdAt: widget.item.createdAt, + ), + 'type': categories[i].label, + }, ); }, ), @@ -227,7 +233,7 @@ class _FloatingNavigationBarState extends State { Navigator.of(context).pop(); Navigator.of(context).pushNamed( Routes.direct, - arguments: 0, + arguments: {}, ); }, icon: DidvanIcons.description_regular, diff --git a/lib/pages/home/widgets/logo_app_bar.dart b/lib/views/home/widgets/logo_app_bar.dart similarity index 55% rename from lib/pages/home/widgets/logo_app_bar.dart rename to lib/views/home/widgets/logo_app_bar.dart index b8f2803..50f0bfc 100644 --- a/lib/pages/home/widgets/logo_app_bar.dart +++ b/lib/views/home/widgets/logo_app_bar.dart @@ -1,23 +1,26 @@ -import 'package:didvan/widgets/logos/didvan_vertical_logo.dart'; +import 'package:didvan/views/widgets/logos/didvan_vertical_logo.dart'; import 'package:flutter/material.dart'; class LogoAppBar extends StatelessWidget { + final String? type; final bool hasExtraPadding; - const LogoAppBar({Key? key, this.hasExtraPadding = true}) : super(key: key); + const LogoAppBar({Key? key, this.hasExtraPadding = true, this.type}) + : super(key: key); @override Widget build(BuildContext context) { final MediaQueryData d = MediaQuery.of(context); final double extraPadding = hasExtraPadding ? 0 : 16; return Container( + margin: EdgeInsets.only(top: d.padding.top), padding: EdgeInsets.only( - left: 140 - extraPadding, - top: d.padding.top + 16 - extraPadding, + left: (type == null ? 140 : 0) - extraPadding, + top: 16 - extraPadding, bottom: 16 - extraPadding, right: 16 - extraPadding, ), alignment: Alignment.centerRight, - child: const DidvanHorizontalLogo(), + child: DidvanHorizontalLogo(type: type), ); } } diff --git a/lib/pages/home/widgets/menu_item.dart b/lib/views/home/widgets/menu_item.dart similarity index 97% rename from lib/pages/home/widgets/menu_item.dart rename to lib/views/home/widgets/menu_item.dart index bd65501..d0612c7 100644 --- a/lib/pages/home/widgets/menu_item.dart +++ b/lib/views/home/widgets/menu_item.dart @@ -1,6 +1,6 @@ import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; -import 'package:didvan/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; class MenuItem extends StatelessWidget { diff --git a/lib/pages/home/widgets/multitype_overview.dart b/lib/views/home/widgets/multitype_overview.dart similarity index 91% rename from lib/pages/home/widgets/multitype_overview.dart rename to lib/views/home/widgets/multitype_overview.dart index ef0a944..0076a05 100644 --- a/lib/pages/home/widgets/multitype_overview.dart +++ b/lib/views/home/widgets/multitype_overview.dart @@ -1,13 +1,13 @@ import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; -import 'package:didvan/models/item_overview.dart'; +import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/requests/news.dart'; import 'package:didvan/models/requests/radar.dart'; import 'package:didvan/routes/routes.dart'; -import 'package:didvan/widgets/didvan/card.dart'; -import 'package:didvan/widgets/didvan/text.dart'; -import 'package:didvan/widgets/shimmer_placeholder.dart'; -import 'package:didvan/widgets/skeleton_image.dart'; +import 'package:didvan/views/widgets/didvan/card.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/shimmer_placeholder.dart'; +import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:flutter/material.dart'; import 'package:persian_number_utility/persian_number_utility.dart'; @@ -72,6 +72,8 @@ class MultitypeOverview extends StatelessWidget { DidvanText( item.title, style: Theme.of(context).textTheme.bodyText1, + maxLines: 2, + overflow: TextOverflow.ellipsis, ), Row( children: [ diff --git a/lib/pages/home/widgets/news_overview.dart b/lib/views/home/widgets/news_overview.dart similarity index 89% rename from lib/pages/home/widgets/news_overview.dart rename to lib/views/home/widgets/news_overview.dart index 0c625de..e998acf 100644 --- a/lib/pages/home/widgets/news_overview.dart +++ b/lib/views/home/widgets/news_overview.dart @@ -1,17 +1,17 @@ -import 'package:didvan/models/news_overview.dart'; +import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/requests/news.dart'; import 'package:didvan/routes/routes.dart'; import 'package:didvan/utils/date_time.dart'; -import 'package:didvan/pages/home/widgets/bookmark_button.dart'; -import 'package:didvan/widgets/didvan/card.dart'; -import 'package:didvan/widgets/didvan/divider.dart'; -import 'package:didvan/widgets/didvan/text.dart'; -import 'package:didvan/widgets/shimmer_placeholder.dart'; -import 'package:didvan/widgets/skeleton_image.dart'; +import 'package:didvan/views/home/widgets/bookmark_button.dart'; +import 'package:didvan/views/widgets/didvan/card.dart'; +import 'package:didvan/views/widgets/didvan/divider.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/shimmer_placeholder.dart'; +import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:flutter/material.dart'; class NewsOverview extends StatelessWidget { - final NewsOverviewData news; + final OverviewData news; final NewsRequestArgs? newsRequestArgs; final void Function(int id, bool value) onMarkChanged; final bool hasUnmarkConfirmation; @@ -69,7 +69,7 @@ class NewsOverview extends StatelessWidget { Row( children: [ DidvanText( - news.reference, + news.reference!, style: Theme.of(context).textTheme.caption, ), DidvanText( diff --git a/lib/views/home/widgets/player_controller_button.dart b/lib/views/home/widgets/player_controller_button.dart new file mode 100644 index 0000000..7e2cc14 --- /dev/null +++ b/lib/views/home/widgets/player_controller_button.dart @@ -0,0 +1,34 @@ +import 'dart:io'; + +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/services/media/media.dart'; +import 'package:didvan/views/widgets/didvan/icon_button.dart'; +import 'package:flutter/material.dart'; + +class AudioControllerButton extends StatelessWidget { + final String? audioUrl; + final File? audioFile; + + const AudioControllerButton({Key? key, this.audioUrl, this.audioFile}) + : super(key: key); + + bool get _nowPlaying => + MediaService.audioPlayerTag == audioUrl || + audioFile != null && MediaService.audioPlayerTag == audioFile!.path; + + @override + Widget build(BuildContext context) { + return DidvanIconButton( + icon: MediaService.audioPlayer.playing == true && _nowPlaying + ? DidvanIcons.pause_circle_solid + : DidvanIcons.play_circle_solid, + color: Theme.of(context).colorScheme.focusedBorder, + onPressed: () { + MediaService.handleAudioPlayback( + audioSource: audioFile ?? audioUrl, + ); + }, + ); + } +} diff --git a/lib/pages/home/widgets/radar_overview.dart b/lib/views/home/widgets/radar_overview.dart similarity index 89% rename from lib/pages/home/widgets/radar_overview.dart rename to lib/views/home/widgets/radar_overview.dart index 35be2a4..5616994 100644 --- a/lib/pages/home/widgets/radar_overview.dart +++ b/lib/views/home/widgets/radar_overview.dart @@ -1,20 +1,20 @@ import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; -import 'package:didvan/models/radar_overview.dart'; +import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/requests/radar.dart'; import 'package:didvan/routes/routes.dart'; import 'package:didvan/utils/date_time.dart'; -import 'package:didvan/pages/home/widgets/bookmark_button.dart'; -import 'package:didvan/widgets/didvan/card.dart'; -import 'package:didvan/widgets/didvan/divider.dart'; -import 'package:didvan/widgets/didvan/icon_button.dart'; -import 'package:didvan/widgets/didvan/text.dart'; -import 'package:didvan/widgets/shimmer_placeholder.dart'; -import 'package:didvan/widgets/skeleton_image.dart'; +import 'package:didvan/views/home/widgets/bookmark_button.dart'; +import 'package:didvan/views/widgets/didvan/card.dart'; +import 'package:didvan/views/widgets/didvan/divider.dart'; +import 'package:didvan/views/widgets/didvan/icon_button.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/shimmer_placeholder.dart'; +import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:flutter/material.dart'; class RadarOverview extends StatelessWidget { - final RadarOverviewData radar; + final OverviewData radar; final void Function(int id, int count) onCommentsChanged; final void Function(int id, bool value) onMarkChanged; final bool hasUnmarkConfirmation; @@ -44,7 +44,6 @@ class RadarOverview extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox(height: 8), DidvanText( radar.title, fontWeight: FontWeight.w600, @@ -82,13 +81,13 @@ class RadarOverview extends StatelessWidget { Row( children: [ DidvanText( - radar.categories.first.label, + radar.categories!.first.label, style: Theme.of(context).textTheme.overline, color: Theme.of(context).colorScheme.caption, ), const Spacer(), DidvanText( - '${DateTimeUtils.momentGenerator(radar.createdAt)} | خواندن ${radar.timeToRead} دقیقه', + '${DateTimeUtils.momentGenerator(radar.createdAt)} | خواندن در ${radar.timeToRead} دقیقه', style: Theme.of(context).textTheme.overline, color: Theme.of(context).colorScheme.caption, ), @@ -139,11 +138,13 @@ class RadarOverview extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + const ShimmerPlaceholder(height: 16), + const SizedBox(height: 8), const ShimmerPlaceholder( width: 200, height: 16, ), - const SizedBox(height: 8), + const SizedBox(height: 16), const AspectRatio(aspectRatio: 16 / 9, child: ShimmerPlaceholder()), const SizedBox(height: 8), Row( diff --git a/lib/pages/home/widgets/search_field.dart b/lib/views/home/widgets/search_field.dart similarity index 98% rename from lib/pages/home/widgets/search_field.dart rename to lib/views/home/widgets/search_field.dart index 1dd684c..dea0aa1 100644 --- a/lib/pages/home/widgets/search_field.dart +++ b/lib/views/home/widgets/search_field.dart @@ -1,6 +1,6 @@ import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; -import 'package:didvan/widgets/didvan/icon_button.dart'; +import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:flutter/material.dart'; class SearchField extends StatefulWidget { diff --git a/lib/views/home/widgets/tag_item.dart b/lib/views/home/widgets/tag_item.dart new file mode 100644 index 0000000..1265361 --- /dev/null +++ b/lib/views/home/widgets/tag_item.dart @@ -0,0 +1,52 @@ +import 'package:didvan/config/design_config.dart'; +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/models/tag.dart'; +import 'package:didvan/routes/routes.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/ink_wrapper.dart'; +import 'package:flutter/material.dart'; + +class TagItem extends StatelessWidget { + final Tag tag; + + const TagItem({ + Key? key, + required this.tag, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return InkWrapper( + borderRadius: DesignConfig.lowBorderRadius, + onPressed: () => + Navigator.of(context).pushNamed(Routes.hashtag, arguments: tag), + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 4, + horizontal: 8, + ), + decoration: BoxDecoration( + borderRadius: DesignConfig.lowBorderRadius, + border: Border.all( + color: Theme.of(context).colorScheme.focusedBorder, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + DidvanIcons.hashtag_regular, + color: Theme.of(context).colorScheme.focusedBorder, + ), + DidvanText( + tag.label, + color: Theme.of(context).colorScheme.focusedBorder, + style: Theme.of(context).textTheme.bodyText1, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/splash/splash.dart b/lib/views/splash/splash.dart similarity index 93% rename from lib/pages/splash/splash.dart rename to lib/views/splash/splash.dart index 4486bec..a66e8d1 100644 --- a/lib/pages/splash/splash.dart +++ b/lib/views/splash/splash.dart @@ -10,8 +10,8 @@ import 'package:didvan/services/app_initalizer.dart'; import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/storage/storage.dart'; import 'package:didvan/utils/action_sheet.dart'; -import 'package:didvan/widgets/didvan/button.dart'; -import 'package:didvan/widgets/logos/didvan_horizontal_logo.dart'; +import 'package:didvan/views/widgets/didvan/button.dart'; +import 'package:didvan/views/widgets/logos/didvan_horizontal_logo.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -64,7 +64,7 @@ class _SplashState extends State { SpinKitSpinningLines( color: Theme.of(context).colorScheme.primary, ), - if (_errorOccured) const SizedBox(height: 38), + if (_errorOccured) const SizedBox(height: 30), if (_errorOccured) DidvanButton( width: 120, @@ -108,7 +108,7 @@ class _SplashState extends State { log(token); RequestService.token = token; await userProvider.getUserInfo(); - await context.read().getData(); + await ServerDataProvider.getData(); } Navigator.of(context).pushReplacementNamed( token == null ? Routes.authenticaion : Routes.home, diff --git a/lib/widgets/animated_visibility.dart b/lib/views/widgets/animated_visibility.dart similarity index 100% rename from lib/widgets/animated_visibility.dart rename to lib/views/widgets/animated_visibility.dart diff --git a/lib/widgets/didvan/app_bar.dart b/lib/views/widgets/didvan/app_bar.dart similarity index 68% rename from lib/widgets/didvan/app_bar.dart rename to lib/views/widgets/didvan/app_bar.dart index 764e588..1de49fe 100644 --- a/lib/widgets/didvan/app_bar.dart +++ b/lib/views/widgets/didvan/app_bar.dart @@ -1,23 +1,44 @@ import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/view/app_bar_data.dart'; -import 'package:didvan/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; class DidvanAppBar extends StatelessWidget { final AppBarData appBarData; - const DidvanAppBar({Key? key, required this.appBarData}) : super(key: key); + final bool hasBorder; + final Color? backgroundColor; + const DidvanAppBar({ + Key? key, + required this.appBarData, + this.hasBorder = false, + this.backgroundColor = Colors.transparent, + }) : super(key: key); @override Widget build(BuildContext context) { - return Padding( + return Container( + height: kToolbarHeight + MediaQuery.of(context).padding.top, + width: MediaQuery.of(context).size.width, padding: const EdgeInsets.only(right: 4, left: 20), + decoration: BoxDecoration( + color: backgroundColor, + border: hasBorder + ? Border( + bottom: BorderSide( + color: Theme.of(context).colorScheme.cardBorder, + ), + ) + : null, + ), child: Row( children: [ IconButton( onPressed: () => Navigator.of(context).pop(), color: Theme.of(context).colorScheme.title, - icon: const Icon(DidvanIcons.back_regular), + icon: const Icon( + DidvanIcons.back_regular, + ), ), const SizedBox(width: 16), Expanded( diff --git a/lib/widgets/didvan/badge.dart b/lib/views/widgets/didvan/badge.dart similarity index 93% rename from lib/widgets/didvan/badge.dart rename to lib/views/widgets/didvan/badge.dart index 5223d0a..c88c0fd 100644 --- a/lib/widgets/didvan/badge.dart +++ b/lib/views/widgets/didvan/badge.dart @@ -1,5 +1,5 @@ import 'package:didvan/config/theme_data.dart'; -import 'package:didvan/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; class DidvanBadge extends StatelessWidget { diff --git a/lib/widgets/didvan/button.dart b/lib/views/widgets/didvan/button.dart similarity index 97% rename from lib/widgets/didvan/button.dart rename to lib/views/widgets/didvan/button.dart index 9160709..de7498b 100644 --- a/lib/widgets/didvan/button.dart +++ b/lib/views/widgets/didvan/button.dart @@ -1,7 +1,7 @@ import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/models/enums.dart'; -import 'package:didvan/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; class DidvanButton extends StatelessWidget { @@ -75,5 +75,6 @@ class DidvanButton extends StatelessWidget { color: enabled ? color : Theme.of(context).colorScheme.disabledText, ); } + return null; } } diff --git a/lib/widgets/didvan/card.dart b/lib/views/widgets/didvan/card.dart similarity index 100% rename from lib/widgets/didvan/card.dart rename to lib/views/widgets/didvan/card.dart diff --git a/lib/widgets/didvan/checkbox.dart b/lib/views/widgets/didvan/checkbox.dart similarity index 95% rename from lib/widgets/didvan/checkbox.dart rename to lib/views/widgets/didvan/checkbox.dart index 588234c..dfeb454 100644 --- a/lib/widgets/didvan/checkbox.dart +++ b/lib/views/widgets/didvan/checkbox.dart @@ -1,4 +1,4 @@ -import 'package:didvan/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; class DidvanCheckbox extends StatefulWidget { diff --git a/lib/widgets/didvan/chip.dart b/lib/views/widgets/didvan/chip.dart similarity index 89% rename from lib/widgets/didvan/chip.dart rename to lib/views/widgets/didvan/chip.dart index 358bcef..a1a7e73 100644 --- a/lib/widgets/didvan/chip.dart +++ b/lib/views/widgets/didvan/chip.dart @@ -1,7 +1,7 @@ import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; -import 'package:didvan/widgets/didvan/text.dart'; -import 'package:didvan/widgets/ink_wrapper.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/ink_wrapper.dart'; import 'package:flutter/material.dart'; class DidvanChip extends StatelessWidget { diff --git a/lib/widgets/didvan/divider.dart b/lib/views/widgets/didvan/divider.dart similarity index 100% rename from lib/widgets/didvan/divider.dart rename to lib/views/widgets/didvan/divider.dart diff --git a/lib/widgets/didvan/icon_button.dart b/lib/views/widgets/didvan/icon_button.dart similarity index 74% rename from lib/widgets/didvan/icon_button.dart rename to lib/views/widgets/didvan/icon_button.dart index 6f12245..3046757 100644 --- a/lib/widgets/didvan/icon_button.dart +++ b/lib/views/widgets/didvan/icon_button.dart @@ -1,9 +1,10 @@ -import 'package:didvan/widgets/ink_wrapper.dart'; +import 'package:didvan/views/widgets/ink_wrapper.dart'; import 'package:flutter/material.dart'; class DidvanIconButton extends StatelessWidget { final IconData icon; final Color? color; + final Color? backgroundColor; final double? size; final double? gestureSize; final VoidCallback onPressed; @@ -14,6 +15,7 @@ class DidvanIconButton extends StatelessWidget { this.color, this.size, this.gestureSize, + this.backgroundColor, }) : super(key: key); @override @@ -21,7 +23,11 @@ class DidvanIconButton extends StatelessWidget { return InkWrapper( onPressed: onPressed, borderRadius: BorderRadius.circular(200), - child: SizedBox( + child: Container( + decoration: BoxDecoration( + color: backgroundColor, + shape: BoxShape.circle, + ), height: gestureSize ?? 48, width: gestureSize ?? 48, child: Icon( diff --git a/lib/views/widgets/didvan/page_view.dart b/lib/views/widgets/didvan/page_view.dart new file mode 100644 index 0000000..e42744c --- /dev/null +++ b/lib/views/widgets/didvan/page_view.dart @@ -0,0 +1,343 @@ +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:didvan/config/design_config.dart'; +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/utils/date_time.dart'; +import 'package:didvan/views/home/widgets/multitype_overview.dart'; +import 'package:didvan/views/home/widgets/tag_item.dart'; +import 'package:didvan/views/widgets/animated_visibility.dart'; +import 'package:didvan/views/widgets/didvan/card.dart'; +import 'package:didvan/views/widgets/didvan/divider.dart'; +import 'package:didvan/views/widgets/didvan/icon_button.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/ink_wrapper.dart'; +import 'package:didvan/views/widgets/item_title.dart'; +import 'package:didvan/views/widgets/skeleton_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; + +class DidvanPageView extends StatefulWidget { + final List items; + final int initialIndex; + final int currentIndex; + final bool isRadar; + final ScrollController scrollController; + + final void Function(int index) onPageChanged; + const DidvanPageView({ + Key? key, + required this.initialIndex, + required this.items, + required this.scrollController, + required this.onPageChanged, + required this.isRadar, + required this.currentIndex, + }) : super(key: key); + + @override + State createState() => _DidvanPageViewState(); +} + +class _DidvanPageViewState extends State { + @override + Widget build(BuildContext context) { + final double deviceTopPadding = MediaQuery.of(context).padding.top; + return Stack( + children: [ + Directionality( + textDirection: TextDirection.ltr, + child: CarouselSlider.builder( + itemCount: widget.items.length, + options: CarouselOptions( + onPageChanged: (index, reason) => widget.onPageChanged(index), + height: double.infinity, + initialPage: widget.initialIndex, + viewportFraction: 0.94, + enableInfiniteScroll: false, + ), + itemBuilder: (context, index, realIndex) => Directionality( + textDirection: TextDirection.rtl, + child: SizedBox( + height: MediaQuery.of(context).size.height, + child: SingleChildScrollView( + controller: index == widget.currentIndex + ? widget.scrollController + : null, + physics: const BouncingScrollPhysics(), + padding: EdgeInsets.only( + left: 4, + right: 4, + top: 16 + deviceTopPadding, + bottom: 92, + ), + child: DidvanCard( + padding: EdgeInsets.zero, + enableBorder: false, + child: Builder( + builder: (context) { + final item = widget.items[index]; + if (item == null) { + return const SizedBox(); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SkeletonImage( + imageUrl: item.image, + aspectRatio: 16 / 9, + ), + const SizedBox(height: 20), + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 16), + child: DidvanText( + item.title, + style: Theme.of(context).textTheme.subtitle1, + ), + ), + const SizedBox(height: 8), + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 16), + child: _subtitle(item), + ), + for (var i = 0; i < item.contents.length; i++) + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 4, + ), + child: _contentBuilder(item, i), + ), + if (item.tags.isNotEmpty) + const SizedBox(height: 20), + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 16), + child: Wrap( + spacing: 8, + runSpacing: 8, + children: [ + for (var i = 0; i < item.tags.length; i++) + TagItem(tag: item.tags[i]), + ], + ), + ), + const Padding( + padding: EdgeInsets.only( + left: 16, + right: 16, + top: 16, + ), + child: DidvanDivider(verticalPadding: 0), + ), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 16.0), + child: ItemTitle(title: 'مطالب مشابه'), + ), + if (item.relatedContents.isEmpty) + for (var i = 0; i < 3; i++) + Padding( + padding: const EdgeInsets.only( + bottom: 8, + left: 16, + right: 16, + ), + child: MultitypeOverview.placeholder, + ), + for (var i = 0; + i < item.relatedContents.length; + i++) + Padding( + padding: const EdgeInsets.only( + bottom: 8, + left: 16, + right: 16, + ), + child: MultitypeOverview( + item: item.relatedContents[i], + onMarkChanged: (id, value) {}, + ), + ), + const SizedBox(height: 8), + ], + ); + }, + ), + ), + ), + ), + ), + ), + ), + Positioned( + child: _BackButton(scrollController: widget.scrollController), + right: 24, + top: 24 + deviceTopPadding, + ), + ], + ); + } + + Widget _contentBuilder(dynamic item, int index) { + final content = item.contents[index]; + if (content.text != null) { + return Html( + data: content.text, + style: { + '*': Style( + direction: TextDirection.rtl, + lineHeight: LineHeight.percent(135), + margin: EdgeInsets.zero, + padding: EdgeInsets.zero, + ), + 'span': Style( + textAlign: TextAlign.center, + fontSize: FontSize.small, + alignment: Alignment.center, + ), + }, + ); + } + if (content.image != null) { + return GestureDetector( + onTap: () { + showDialog( + context: context, + builder: (context) => Stack( + children: [ + Positioned.fill( + child: InteractiveViewer( + child: Center( + child: SkeletonImage( + width: MediaQuery.of(context).size.width, + height: 200, + imageUrl: content.image, + ), + ), + ), + ), + Positioned( + right: 16, + child: DidvanIconButton( + backgroundColor: Theme.of(context).colorScheme.primary, + icon: DidvanIcons.back_regular, + onPressed: Navigator.of(context).pop, + ), + ), + ], + ), + ); + }, + child: SkeletonImage( + imageUrl: content.image!, + aspectRatio: 16 / 9, + ), + ); + } + return const SizedBox(); + } + + Widget _subtitle(dynamic item) { + if (widget.isRadar) { + return Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + DidvanText( + 'رادار ', + style: Theme.of(context).textTheme.caption, + ), + for (var i = 0; i < item.categories.length; i++) + DidvanText( + item.categories[i].label + + '${i != item.categories.length - 1 ? '،' : ''} ', + style: Theme.of(context).textTheme.caption, + ), + DidvanText( + ' - ' + DateTimeUtils.momentGenerator(item.createdAt), + style: Theme.of(context).textTheme.caption, + ), + ], + ); + } else { + return Row( + children: [ + DidvanText( + item.reference, + style: Theme.of(context).textTheme.caption, + ), + DidvanText( + ' - ' + DateTimeUtils.momentGenerator(item.createdAt), + style: Theme.of(context).textTheme.caption, + ), + ], + ); + } + } +} + +class _BackButton extends StatefulWidget { + final ScrollController scrollController; + const _BackButton({Key? key, required this.scrollController}) + : super(key: key); + + @override + __BackButtonState createState() => __BackButtonState(); +} + +class __BackButtonState extends State<_BackButton> { + bool _isVisible = false; + + @override + void didUpdateWidget(covariant _BackButton oldWidget) { + _isVisible = false; + _handleScroll(); + super.didUpdateWidget(oldWidget); + } + + @override + void initState() { + _handleScroll(); + super.initState(); + } + + void _handleScroll() { + widget.scrollController.addListener(() { + final position = widget.scrollController.position.pixels; + final size = MediaQuery.of(context).size.height * 0.3; + if (position > size && _isVisible == false) { + setState(() { + _isVisible = true; + }); + } + if (position < size && _isVisible == true) { + setState(() { + _isVisible = false; + }); + } + }); + } + + @override + Widget build(BuildContext context) { + return AnimatedVisibility( + duration: DesignConfig.lowAnimationDuration, + isVisible: _isVisible, + child: InkWrapper( + borderRadius: DesignConfig.lowBorderRadius, + onPressed: Navigator.of(context).pop, + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.splash, + border: Border.all(color: Theme.of(context).colorScheme.border), + borderRadius: DesignConfig.lowBorderRadius, + ), + child: const Icon( + DidvanIcons.back_regular, + size: 32, + ), + ), + ), + ); + } +} diff --git a/lib/views/widgets/didvan/scaffold.dart b/lib/views/widgets/didvan/scaffold.dart new file mode 100644 index 0000000..db75ac9 --- /dev/null +++ b/lib/views/widgets/didvan/scaffold.dart @@ -0,0 +1,136 @@ +import 'package:didvan/models/view/app_bar_data.dart'; +import 'package:didvan/views/widgets/didvan/app_bar.dart'; +import 'package:flutter/material.dart'; + +class DidvanScaffold extends StatefulWidget { + final List? slivers; + final List? children; + final AppBarData appBarData; + final EdgeInsets padding; + final Color? backgroundColor; + final bool reverse; + + const DidvanScaffold({ + Key? key, + this.slivers, + required this.appBarData, + this.children, + this.padding = const EdgeInsets.symmetric(horizontal: 16), + this.backgroundColor, + this.reverse = false, + }) : super(key: key); + + @override + State createState() => _DidvanScaffoldState(); +} + +class _DidvanScaffoldState extends State { + final _scrollController = ScrollController(); + + @override + Widget build(BuildContext context) { + final double statusBarHeight = MediaQuery.of(context).padding.top; + return Scaffold( + backgroundColor: widget.backgroundColor, + body: Padding( + padding: EdgeInsets.only(top: statusBarHeight), + child: Stack( + children: [ + CustomScrollView( + controller: _scrollController, + reverse: widget.reverse, + slivers: [ + if (!widget.reverse) + SliverAppBar( + toolbarHeight: kToolbarHeight, + backgroundColor: widget.backgroundColor ?? + Theme.of(context).colorScheme.background, + automaticallyImplyLeading: false, + pinned: true, + flexibleSpace: DidvanAppBar(appBarData: widget.appBarData), + ), + if (!widget.reverse) + const SliverToBoxAdapter( + child: SizedBox(height: 16), + ), + if (widget.children != null) + SliverPadding( + padding: widget.padding, + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => widget.children![index], + childCount: widget.children!.length, + ), + ), + ), + if (widget.slivers != null) + for (var i = 0; i < widget.slivers!.length; i++) + SliverPadding( + padding: widget.padding, + sliver: widget.slivers![i], + ), + if (widget.reverse) + SliverToBoxAdapter( + child: SizedBox( + height: kToolbarHeight + + MediaQuery.of(context).padding.top + + 12, + ), + ), + ], + ), + if (widget.reverse) + _AppBar( + appBarData: widget.appBarData, + scrollController: _scrollController, + ), + ], + ), + ), + ); + } +} + +class _AppBar extends StatefulWidget { + final AppBarData appBarData; + final ScrollController scrollController; + const _AppBar({ + Key? key, + required this.appBarData, + required this.scrollController, + }) : super(key: key); + + @override + __AppBarState createState() => __AppBarState(); +} + +class __AppBarState extends State<_AppBar> { + bool _isScrolled = false; + + @override + void initState() { + widget.scrollController.addListener(() { + final position = widget.scrollController.position.pixels; + if (position > 10 && _isScrolled == false) { + setState(() { + _isScrolled = true; + }); + } + if (position < 10 && _isScrolled == true) { + setState(() { + _isScrolled = false; + }); + } + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return DidvanAppBar( + backgroundColor: Theme.of(context).colorScheme.surface, + appBarData: widget.appBarData, + hasBorder: _isScrolled, + ); + } +} diff --git a/lib/widgets/didvan/switch.dart b/lib/views/widgets/didvan/switch.dart similarity index 95% rename from lib/widgets/didvan/switch.dart rename to lib/views/widgets/didvan/switch.dart index 6b6841d..558a2f7 100644 --- a/lib/widgets/didvan/switch.dart +++ b/lib/views/widgets/didvan/switch.dart @@ -1,4 +1,4 @@ -import 'package:didvan/pages/home/widgets/menu_item.dart'; +import 'package:didvan/views/home/widgets/menu_item.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/didvan/text.dart b/lib/views/widgets/didvan/text.dart similarity index 100% rename from lib/widgets/didvan/text.dart rename to lib/views/widgets/didvan/text.dart diff --git a/lib/widgets/didvan/text_field.dart b/lib/views/widgets/didvan/text_field.dart similarity index 97% rename from lib/widgets/didvan/text_field.dart rename to lib/views/widgets/didvan/text_field.dart index e8fbeb4..bf01ab4 100644 --- a/lib/widgets/didvan/text_field.dart +++ b/lib/views/widgets/didvan/text_field.dart @@ -1,8 +1,8 @@ import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; -import 'package:didvan/widgets/animated_visibility.dart'; -import 'package:didvan/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/animated_visibility.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:persian_number_utility/persian_number_utility.dart'; @@ -152,6 +152,7 @@ class _DidvanTextFieldState extends State { if (_error != null) { return Theme.of(context).colorScheme.error; } + return null; } Color _fillColor() { @@ -185,6 +186,7 @@ class _DidvanTextFieldState extends State { ), ); } + return null; } void _onChanged(String value) { @@ -209,6 +211,7 @@ class _DidvanTextFieldState extends State { }); } } + return null; } @override diff --git a/lib/widgets/ink_wrapper.dart b/lib/views/widgets/ink_wrapper.dart similarity index 100% rename from lib/widgets/ink_wrapper.dart rename to lib/views/widgets/ink_wrapper.dart diff --git a/lib/widgets/item_title.dart b/lib/views/widgets/item_title.dart similarity index 94% rename from lib/widgets/item_title.dart rename to lib/views/widgets/item_title.dart index 1c0a89d..0477d87 100644 --- a/lib/widgets/item_title.dart +++ b/lib/views/widgets/item_title.dart @@ -1,5 +1,5 @@ import 'package:didvan/config/theme_data.dart'; -import 'package:didvan/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; class ItemTitle extends StatelessWidget { diff --git a/lib/widgets/logos/didvan_horizontal_logo.dart b/lib/views/widgets/logos/didvan_horizontal_logo.dart similarity index 100% rename from lib/widgets/logos/didvan_horizontal_logo.dart rename to lib/views/widgets/logos/didvan_horizontal_logo.dart diff --git a/lib/views/widgets/logos/didvan_vertical_logo.dart b/lib/views/widgets/logos/didvan_vertical_logo.dart new file mode 100644 index 0000000..75cf9d5 --- /dev/null +++ b/lib/views/widgets/logos/didvan_vertical_logo.dart @@ -0,0 +1,21 @@ +import 'package:didvan/constants/assets.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +class DidvanHorizontalLogo extends StatelessWidget { + final String? type; + const DidvanHorizontalLogo({Key? key, this.type}) : super(key: key); + + @override + Widget build(BuildContext context) { + return SvgPicture.asset(_asset); + } + + String get _asset { + if (type == 'studio') { + return Assets.studioLogo; + } else { + return Assets.horizontalLogoWithText; + } + } +} diff --git a/lib/widgets/process_indicators.dart b/lib/views/widgets/process_indicators.dart similarity index 100% rename from lib/widgets/process_indicators.dart rename to lib/views/widgets/process_indicators.dart diff --git a/lib/widgets/shimmer_placeholder.dart b/lib/views/widgets/shimmer_placeholder.dart similarity index 100% rename from lib/widgets/shimmer_placeholder.dart rename to lib/views/widgets/shimmer_placeholder.dart diff --git a/lib/views/widgets/skeleton_image.dart b/lib/views/widgets/skeleton_image.dart new file mode 100644 index 0000000..bf7db1c --- /dev/null +++ b/lib/views/widgets/skeleton_image.dart @@ -0,0 +1,57 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:didvan/config/design_config.dart'; +import 'package:didvan/services/network/request.dart'; +import 'package:didvan/services/network/request_helper.dart'; +import 'package:didvan/views/widgets/shimmer_placeholder.dart'; +import 'package:flutter/material.dart'; +import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart'; + +class SkeletonImage extends StatelessWidget { + final String imageUrl; + final double width; + final double height; + final BorderRadius? borderRadius; + final double? aspectRatio; + const SkeletonImage({ + Key? key, + required this.imageUrl, + this.width = 300, + this.height = 140, + this.borderRadius = DesignConfig.lowBorderRadius, + this.aspectRatio, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return _aspectRatioGenerator( + child: CachedNetworkImage( + imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet, + httpHeaders: {'Authorization': 'Bearer ${RequestService.token}'}, + width: width, + height: height, + imageUrl: RequestHelper.baseUrl + imageUrl, + imageBuilder: (context, imageProvider) => Container( + decoration: BoxDecoration( + borderRadius: borderRadius ?? DesignConfig.lowBorderRadius, + image: DecorationImage( + image: imageProvider, + fit: BoxFit.cover, + ), + ), + ), + progressIndicatorBuilder: (context, url, progress) => + ShimmerPlaceholder( + borderRadius: borderRadius, + ), + ), + ); + } + + Widget _aspectRatioGenerator({required Widget child}) => aspectRatio == null + ? SizedBox(key: ValueKey(imageUrl), child: child) + : AspectRatio( + key: ValueKey(imageUrl), + aspectRatio: aspectRatio!, + child: child, + ); +} diff --git a/lib/widgets/state_handlers/empty_connection.dart b/lib/views/widgets/state_handlers/empty_connection.dart similarity index 87% rename from lib/widgets/state_handlers/empty_connection.dart rename to lib/views/widgets/state_handlers/empty_connection.dart index 53e87a6..c6543bb 100644 --- a/lib/widgets/state_handlers/empty_connection.dart +++ b/lib/views/widgets/state_handlers/empty_connection.dart @@ -1,5 +1,5 @@ import 'package:didvan/constants/assets.dart'; -import 'package:didvan/widgets/state_handlers/empty_state.dart'; +import 'package:didvan/views/widgets/state_handlers/empty_state.dart'; import 'package:flutter/material.dart'; class EmptyConnection extends StatelessWidget { diff --git a/lib/widgets/state_handlers/empty_list.dart b/lib/views/widgets/state_handlers/empty_list.dart similarity index 82% rename from lib/widgets/state_handlers/empty_list.dart rename to lib/views/widgets/state_handlers/empty_list.dart index da00ac7..9be66b6 100644 --- a/lib/widgets/state_handlers/empty_list.dart +++ b/lib/views/widgets/state_handlers/empty_list.dart @@ -1,5 +1,5 @@ import 'package:didvan/constants/assets.dart'; -import 'package:didvan/widgets/state_handlers/empty_state.dart'; +import 'package:didvan/views/widgets/state_handlers/empty_state.dart'; import 'package:flutter/material.dart'; class EmptyList extends StatelessWidget { diff --git a/lib/widgets/state_handlers/empty_result.dart b/lib/views/widgets/state_handlers/empty_result.dart similarity index 87% rename from lib/widgets/state_handlers/empty_result.dart rename to lib/views/widgets/state_handlers/empty_result.dart index bbb5fbb..1ae6b8f 100644 --- a/lib/widgets/state_handlers/empty_result.dart +++ b/lib/views/widgets/state_handlers/empty_result.dart @@ -1,5 +1,5 @@ import 'package:didvan/constants/assets.dart'; -import 'package:didvan/widgets/state_handlers/empty_state.dart'; +import 'package:didvan/views/widgets/state_handlers/empty_state.dart'; import 'package:flutter/material.dart'; class EmptyResult extends StatelessWidget { diff --git a/lib/widgets/state_handlers/empty_state.dart b/lib/views/widgets/state_handlers/empty_state.dart similarity index 92% rename from lib/widgets/state_handlers/empty_state.dart rename to lib/views/widgets/state_handlers/empty_state.dart index 6d4a883..abfe420 100644 --- a/lib/widgets/state_handlers/empty_state.dart +++ b/lib/views/widgets/state_handlers/empty_state.dart @@ -1,6 +1,6 @@ import 'package:didvan/config/theme_data.dart'; -import 'package:didvan/widgets/didvan/button.dart'; -import 'package:didvan/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/didvan/button.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; diff --git a/lib/widgets/state_handlers/sliver_state_handler.dart b/lib/views/widgets/state_handlers/sliver_state_handler.dart similarity index 96% rename from lib/widgets/state_handlers/sliver_state_handler.dart rename to lib/views/widgets/state_handlers/sliver_state_handler.dart index e160f70..cb762f5 100644 --- a/lib/widgets/state_handlers/sliver_state_handler.dart +++ b/lib/views/widgets/state_handlers/sliver_state_handler.dart @@ -1,6 +1,6 @@ import 'package:didvan/models/enums.dart'; import 'package:didvan/providers/core_provider.dart'; -import 'package:didvan/widgets/state_handlers/empty_connection.dart'; +import 'package:didvan/views/widgets/state_handlers/empty_connection.dart'; import 'package:flutter/material.dart'; class SliverStateHandler extends SliverList { diff --git a/lib/widgets/state_handlers/state_handler.dart b/lib/views/widgets/state_handlers/state_handler.dart similarity index 95% rename from lib/widgets/state_handlers/state_handler.dart rename to lib/views/widgets/state_handlers/state_handler.dart index 107db8e..513ac3f 100644 --- a/lib/widgets/state_handlers/state_handler.dart +++ b/lib/views/widgets/state_handlers/state_handler.dart @@ -1,6 +1,6 @@ import 'package:didvan/models/enums.dart'; import 'package:didvan/providers/core_provider.dart'; -import 'package:didvan/widgets/state_handlers/empty_connection.dart'; +import 'package:didvan/views/widgets/state_handlers/empty_connection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; diff --git a/lib/widgets/didvan/page_view.dart b/lib/widgets/didvan/page_view.dart deleted file mode 100644 index efbe19b..0000000 --- a/lib/widgets/didvan/page_view.dart +++ /dev/null @@ -1,188 +0,0 @@ -import 'package:carousel_slider/carousel_slider.dart'; -import 'package:didvan/utils/date_time.dart'; -import 'package:didvan/widgets/didvan/card.dart'; -import 'package:didvan/widgets/didvan/divider.dart'; -import 'package:didvan/widgets/didvan/text.dart'; -import 'package:didvan/widgets/skeleton_image.dart'; -import 'package:didvan/pages/home/widgets/tag_item.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_html/flutter_html.dart'; - -class DidvanPageView extends StatefulWidget { - final List items; - final int initialIndex; - final int currentIndex; - final bool isRadar; - final ScrollController scrollController; - - final void Function(int index) onPageChanged; - const DidvanPageView({ - Key? key, - required this.initialIndex, - required this.items, - required this.scrollController, - required this.onPageChanged, - required this.isRadar, - required this.currentIndex, - }) : super(key: key); - - @override - State createState() => _DidvanPageViewState(); -} - -class _DidvanPageViewState extends State { - @override - Widget build(BuildContext context) { - final double deviceTopPadding = MediaQuery.of(context).padding.top; - return Directionality( - textDirection: TextDirection.ltr, - child: CarouselSlider.builder( - itemCount: widget.items.length, - options: CarouselOptions( - onPageChanged: (index, reason) => widget.onPageChanged(index), - height: double.infinity, - initialPage: widget.initialIndex, - viewportFraction: 0.94, - enableInfiniteScroll: false, - ), - itemBuilder: (context, index, realIndex) => Directionality( - textDirection: TextDirection.rtl, - child: SizedBox( - height: MediaQuery.of(context).size.height, - child: SingleChildScrollView( - controller: - index == widget.currentIndex ? widget.scrollController : null, - physics: const BouncingScrollPhysics(), - padding: EdgeInsets.only( - left: 4, - right: 4, - top: 16 + deviceTopPadding, - bottom: 92, - ), - child: DidvanCard( - padding: EdgeInsets.zero, - enableBorder: false, - child: Builder( - builder: (context) { - final item = widget.items[index]; - if (item == null) { - return const SizedBox(); - } - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SkeletonImage( - imageUrl: item.image, - aspectRatio: 16 / 9, - ), - const SizedBox(height: 20), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: DidvanText( - item.title, - style: Theme.of(context).textTheme.subtitle1, - ), - ), - const SizedBox(height: 8), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: _subtitle(item), - ), - for (var i = 0; i < item.contents.length; i++) - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 4, - ), - child: _contentBuilder(item, i), - ), - if (item.tags.isNotEmpty) const SizedBox(height: 20), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Wrap( - spacing: 8, - runSpacing: 8, - children: [ - for (var i = 0; i < item.tags.length; i++) - TagItem(label: item.tags[i].label), - ], - ), - ), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 16), - child: DidvanDivider(), - ), - ], - ); - }, - ), - ), - ), - ), - ), - ), - ); - } - - Widget _contentBuilder(dynamic item, int index) { - final content = item.contents[index]; - if (content.text != null) { - return Html( - data: content.text, - style: { - '*': Style( - direction: TextDirection.rtl, - lineHeight: LineHeight.percent(135), - // textAlign: TextAlign.justify, - margin: EdgeInsets.zero, - padding: EdgeInsets.zero, - ), - }, - ); - } - if (content.image != null) { - return SkeletonImage( - imageUrl: content.image!, - aspectRatio: 16 / 9, - ); - } - return const SizedBox(); - } - - Widget _subtitle(dynamic item) { - if (widget.isRadar) { - return Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - DidvanText( - 'رادار ', - style: Theme.of(context).textTheme.caption, - ), - for (var i = 0; i < item.categories.length; i++) - DidvanText( - item.categories[i].label + - '${i != item.categories.length - 1 ? '،' : ''} ', - style: Theme.of(context).textTheme.caption, - ), - DidvanText( - ' - ' + DateTimeUtils.momentGenerator(item.createdAt), - style: Theme.of(context).textTheme.caption, - ), - ], - ); - } else { - return Row( - children: [ - DidvanText( - item.reference, - style: Theme.of(context).textTheme.caption, - ), - DidvanText( - ' - ' + DateTimeUtils.momentGenerator(item.createdAt), - style: Theme.of(context).textTheme.caption, - ), - ], - ); - } - } -} diff --git a/lib/widgets/didvan/scaffold.dart b/lib/widgets/didvan/scaffold.dart deleted file mode 100644 index 6e00a7a..0000000 --- a/lib/widgets/didvan/scaffold.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'package:didvan/models/view/app_bar_data.dart'; -import 'package:didvan/widgets/didvan/app_bar.dart'; -import 'package:flutter/material.dart'; - -class DidvanScaffold extends StatelessWidget { - final List? slivers; - final List? children; - final AppBarData appBarData; - final EdgeInsets padding; - final Color? backgroundColor; - const DidvanScaffold({ - Key? key, - this.slivers, - required this.appBarData, - this.children, - this.padding = const EdgeInsets.symmetric(horizontal: 16), - this.backgroundColor, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final double statusBarHeight = MediaQuery.of(context).padding.top; - return Scaffold( - backgroundColor: backgroundColor, - body: Padding( - padding: EdgeInsets.only(top: statusBarHeight), - child: CustomScrollView( - slivers: [ - SliverAppBar( - toolbarHeight: kToolbarHeight, - backgroundColor: - backgroundColor ?? Theme.of(context).colorScheme.background, - automaticallyImplyLeading: false, - pinned: true, - flexibleSpace: DidvanAppBar(appBarData: appBarData), - ), - const SliverToBoxAdapter( - child: SizedBox(height: 16), - ), - if (children != null) - SliverPadding( - padding: padding, - sliver: SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) => children![index], - childCount: children!.length, - ), - ), - ), - if (slivers != null) - for (var i = 0; i < slivers!.length; i++) - SliverPadding( - padding: padding, - sliver: slivers![i], - ), - ], - ), - ), - ); - } -} diff --git a/lib/widgets/image_cropper.dart b/lib/widgets/image_cropper.dart deleted file mode 100644 index fd67e93..0000000 --- a/lib/widgets/image_cropper.dart +++ /dev/null @@ -1,201 +0,0 @@ -import 'package:crop/crop.dart'; -import 'package:didvan/config/theme_data.dart'; -import 'package:didvan/constants/app_icons.dart'; -import 'package:flutter/material.dart'; - -class ImageCropper extends StatefulWidget { - final Map data; - - const ImageCropper({Key? key, required this.data}) : super(key: key); - - @override - State createState() => _ImageCropperState(); -} - -class _ImageCropperState extends State { - double _rotation = 0; - BoxShape shape = BoxShape.rectangle; - - final _controller = CropController(); - - get _bytes => widget.data['bytes']; - get _onCropped => widget.data['onCropped']; - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return Scaffold( - appBar: AppBar( - title: const Text('برش عکس'), - centerTitle: true, - actions: [ - IconButton( - onPressed: _cropImage, - tooltip: 'Crop', - icon: const Icon(Icons.crop), - ) - ], - ), - body: Column( - children: [ - Expanded( - child: Container( - color: Colors.black, - padding: const EdgeInsets.all(8), - child: Crop( - onChanged: (decomposition) { - if (_rotation != decomposition.rotation) { - setState(() { - _rotation = ((decomposition.rotation + 180) % 360) - 180; - }); - } - }, - controller: _controller, - shape: shape, - child: Image.memory( - _bytes, - fit: BoxFit.cover, - ), - helper: shape == BoxShape.rectangle - ? Container( - decoration: BoxDecoration( - border: Border.all(color: Colors.white, width: 2), - ), - ) - : null, - ), - ), - ), - Row( - children: [ - IconButton( - icon: const Icon(Icons.redo), - tooltip: 'برگرداندن', - onPressed: () { - _controller.rotation = 0; - _controller.scale = 1; - _controller.offset = Offset.zero; - setState(() { - _rotation = 0; - }); - }, - ), - Expanded( - child: SliderTheme( - data: theme.sliderTheme.copyWith( - trackShape: const RectangularSliderTrackShape(), - ), - child: Slider( - thumbColor: Theme.of(context).colorScheme.title, - divisions: 360, - value: _rotation, - min: -180, - max: 180, - label: '$_rotation°', - onChanged: (n) { - setState(() { - _rotation = n.roundToDouble(); - _controller.rotation = _rotation; - }); - }, - ), - ), - ), - // PopupMenuButton( - // icon: const Icon(Icons.crop_free), - // itemBuilder: (context) => [ - // const PopupMenuItem( - // child: Text("Box"), - // value: BoxShape.rectangle, - // ), - // const PopupMenuItem( - // child: Text("Oval"), - // value: BoxShape.circle, - // ), - // ], - // tooltip: 'Crop Shape', - // onSelected: (x) { - // setState(() { - // shape = x; - // }); - // }, - // ), - // PopupMenuButton( - // icon: const Icon(Icons.aspect_ratio), - // itemBuilder: (context) => [ - // const PopupMenuItem( - // child: Text("Original"), - // value: 1000 / 667.0, - // ), - // const PopupMenuDivider(), - // const PopupMenuItem( - // child: Text("16:9"), - // value: 16.0 / 9.0, - // ), - // const PopupMenuItem( - // child: Text("4:3"), - // value: 4.0 / 3.0, - // ), - // const PopupMenuItem( - // child: Text("1:1"), - // value: 1, - // ), - // const PopupMenuItem( - // child: Text("3:4"), - // value: 3.0 / 4.0, - // ), - // const PopupMenuItem( - // child: Text("9:16"), - // value: 9.0 / 16.0, - // ), - // ], - // tooltip: 'Aspect Ratio', - // onSelected: (x) { - // _controller.aspectRatio = x; - // setState(() {}); - // }, - // ), - ], - ), - ], - ), - ); - } - - void _cropImage() async { - final pixelRatio = MediaQuery.of(context).devicePixelRatio; - final cropped = await _controller.crop(pixelRatio: pixelRatio); - - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => Scaffold( - appBar: AppBar( - title: const Text('تایید برش'), - centerTitle: true, - actions: [ - Builder( - builder: (context) => IconButton( - icon: const Icon( - DidvanIcons.check_circle_solid, - size: 32, - ), - onPressed: () { - Navigator.of(context).pop(); - Navigator.of(context).pop(); - _onCropped(); - }, - ), - ), - ], - ), - body: Center( - child: RawImage( - image: cropped, - ), - ), - ), - fullscreenDialog: true, - ), - ); - } -} diff --git a/lib/widgets/logos/didvan_vertical_logo.dart b/lib/widgets/logos/didvan_vertical_logo.dart deleted file mode 100644 index 4f5637a..0000000 --- a/lib/widgets/logos/didvan_vertical_logo.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:didvan/constants/assets.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; - -class DidvanHorizontalLogo extends StatelessWidget { - const DidvanHorizontalLogo({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return SvgPicture.asset(Assets.horizontalLogoWithText); - } -} diff --git a/lib/widgets/skeleton_image.dart b/lib/widgets/skeleton_image.dart deleted file mode 100644 index ed1f93d..0000000 --- a/lib/widgets/skeleton_image.dart +++ /dev/null @@ -1,108 +0,0 @@ -import 'dart:typed_data'; - -import 'package:didvan/widgets/shimmer_placeholder.dart'; -import 'package:http/http.dart' as http; -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:didvan/config/design_config.dart'; -import 'package:didvan/services/network/request.dart'; -import 'package:didvan/services/network/request_helper.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - -class SkeletonImage extends StatefulWidget { - final String imageUrl; - final double width; - final double height; - final BorderRadius? borderRadius; - final double? aspectRatio; - const SkeletonImage({ - Key? key, - required this.imageUrl, - this.width = 300, - this.height = 140, - this.borderRadius = DesignConfig.lowBorderRadius, - this.aspectRatio, - }) : super(key: key); - - @override - State createState() => _SkeletonImageState(); -} - -class _SkeletonImageState extends State { - late Uint8List _bytes; - bool _isLoading = true; - @override - void initState() { - if (kIsWeb) _getImage(); - super.initState(); - } - - Future _getImage() async { - final url = RequestHelper.baseUrl + widget.imageUrl; - _bytes = (await http.get( - Uri.parse(url), - headers: {'Authorization': 'Bearer ${RequestService.token}'}, - )) - .bodyBytes; - if (mounted) { - setState(() { - _isLoading = false; - }); - } - } - - @override - Widget build(BuildContext context) { - if (kIsWeb) { - if (_isLoading) { - return _aspectRatioGenerator( - child: ShimmerPlaceholder( - borderRadius: widget.borderRadius, - width: widget.aspectRatio == null ? widget.width : null, - height: widget.aspectRatio == null ? widget.height : null, - ), - ); - } - return _aspectRatioGenerator( - child: ClipRRect( - borderRadius: widget.borderRadius, - child: Image.memory( - _bytes, - fit: BoxFit.cover, - width: widget.width, - height: widget.height, - ), - ), - ); - } - return _aspectRatioGenerator( - child: CachedNetworkImage( - httpHeaders: {'Authorization': 'Bearer ${RequestService.token}'}, - width: widget.width, - height: widget.height, - imageUrl: RequestHelper.baseUrl + widget.imageUrl, - imageBuilder: (context, imageProvider) => Container( - decoration: BoxDecoration( - borderRadius: widget.borderRadius ?? DesignConfig.lowBorderRadius, - image: DecorationImage( - image: imageProvider, - fit: BoxFit.cover, - ), - ), - ), - progressIndicatorBuilder: (context, url, progress) => - ShimmerPlaceholder( - borderRadius: widget.borderRadius, - ), - ), - ); - } - - Widget _aspectRatioGenerator({required Widget child}) => - widget.aspectRatio == null - ? SizedBox(child: child) - : AspectRatio( - aspectRatio: widget.aspectRatio!, - child: child, - ); -} diff --git a/pubspec.lock b/pubspec.lock index fce966d..6942b4d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -15,6 +15,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.6+1" + audio_video_progress_bar: + dependency: "direct main" + description: + name: audio_video_progress_bar + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.0" boolean_selector: dependency: transitive description: @@ -85,20 +92,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.15.0" - collision: - dependency: transitive - description: - name: collision - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.3" - crop: - dependency: "direct main" - description: - name: crop - url: "https://pub.dartlang.org" - source: hosted - version: "0.5.2" cross_file: dependency: transitive description: @@ -155,48 +148,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.1.2" - firebase_core: - dependency: "direct main" - description: - name: firebase_core - url: "https://pub.dartlang.org" - source: hosted - version: "1.12.0" - firebase_core_platform_interface: - dependency: transitive - description: - name: firebase_core_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "4.2.4" - firebase_core_web: - dependency: transitive - description: - name: firebase_core_web - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.4" - firebase_messaging: - dependency: "direct main" - description: - name: firebase_messaging - url: "https://pub.dartlang.org" - source: hosted - version: "11.2.6" - firebase_messaging_platform_interface: - dependency: transitive - description: - name: firebase_messaging_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.6" - firebase_messaging_web: - dependency: transitive - description: - name: firebase_messaging_web - url: "https://pub.dartlang.org" - source: hosted - version: "2.2.7" flutter: dependency: "direct main" description: flutter @@ -343,6 +294,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.0.0" + image_cropper: + dependency: "direct main" + description: + name: image_cropper + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.0" image_picker: dependency: "direct main" description: @@ -399,13 +357,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.2" - just_waveform: - dependency: "direct main" - description: - name: just_waveform - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.1" lints: dependency: transitive description: @@ -420,6 +371,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.12.11" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" meta: dependency: transitive description: @@ -690,7 +648,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.3" + version: "0.4.8" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 90f4ef2..119a748 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -52,17 +52,15 @@ dependencies: record: ^3.0.2 just_audio: ^0.9.18 record_web: ^0.2.1 - just_waveform: ^0.0.1 persian_datetime_picker: ^2.4.0 persian_number_utility: ^1.1.1 bot_toast: ^4.0.1 flutter_secure_storage: ^5.0.2 flutter_html: ^3.0.0-alpha.2 - crop: ^0.5.2 url_launcher: ^6.0.18 - firebase_messaging: ^11.2.6 - firebase_core: ^1.12.0 - + audio_video_progress_bar: ^0.10.0 + image_cropper: ^1.5.0 + dev_dependencies: flutter_test: @@ -92,6 +90,8 @@ flutter: - lib/assets/images/logos/logo-vertical-light.svg - lib/assets/images/logos/logo-horizontal-dark.svg - lib/assets/images/logos/logo-horizontal-light.svg + - lib/assets/images/logos/studio-dark.svg + - lib/assets/images/logos/studio-light.svg - lib/assets/images/categories/business-light.svg - lib/assets/images/categories/economic-light.svg - lib/assets/images/categories/enviromental-light.svg @@ -122,6 +122,7 @@ flutter: - lib/assets/images/empty_states/studio-dark.svg - lib/assets/animations/indicator-light.riv - lib/assets/animations/indicator-dark.riv + - lib/assets/loading.gif diff --git a/web/index.html b/web/index.html index 96d3c9c..a2dfb65 100644 --- a/web/index.html +++ b/web/index.html @@ -46,7 +46,7 @@
- +