diff --git a/analysis_options.yaml b/analysis_options.yaml index 674be66a..8716f747 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -19,6 +19,11 @@ analyzer: unnecessary_null_checks: error unnecessary_brace_in_string_interps: error unnecessary_final: error + exclude: + - build/** + - android/** + - ios/** + - web/** linter: diff --git a/android/app/build.gradle b/android/app/build.gradle index 992e2a29..12b3cb96 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -18,7 +18,7 @@ if (keystorePropertiesFile.exists()) { android { namespace 'hmg.cloudSolutions.mohem' - compileSdk 36 + compileSdk 37 ndkVersion '28.2.13676358' @@ -34,7 +34,7 @@ android { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.cloudsolutions.alhabibmohemm" minSdkVersion 30 - targetSdk 35 + targetSdk 37 targetSdk = flutter.targetSdkVersion versionCode flutter.versionCode versionName flutter.versionName @@ -60,12 +60,14 @@ android { buildTypes { debug { signingConfig signingConfigs.debug + minifyEnabled false + shrinkResources false } release { signingConfig signingConfigs.release minifyEnabled true shrinkResources true - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + proguardFiles 'proguard-rules.pro' } } } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8b84ab9d..d22fc9e5 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -30,11 +30,19 @@ + + + + + + + + + + if (project.name == "huawei_push") { + project.android { + compileSdk 36 + } + } + } +} rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" diff --git a/android/gradle.properties b/android/gradle.properties index 8df3fe98..05625607 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,7 +1,12 @@ -org.gradle.jvmargs=-Xmx4096m +#org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=1024m -XX:MaxMetaspaceSize=1024m +org.gradle.jvmargs=-XX:MaxMetaspaceSize=1024m -XX:+HeapDumpOnOutOfMemoryError -Xmx4096m -Dfile.encoding=UTF-8 kotlin.daemon.jvmargs=-Xmx4096m android.useAndroidX=true android.enableJetifier=true apmsInstrumentationEnabled=false -#org.gradle.java.home=/Users/amirs/Library/Java/JavaVirtualMachines/jbr-17.0.12/Contents/Home/ \ No newline at end of file +#org.gradle.java.home=/Users/amirs/Library/Java/JavaVirtualMachines/jbr-17.0.12/Contents/Home/ +# This builtInKotlin flag was added automatically by Flutter migrator +android.builtInKotlin=false +# This newDsl flag was added automatically by Flutter migrator +android.newDsl=false diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 941ceb11..54ce42ec 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Tue Jul 08 11:08:34 AST 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/android/settings.gradle b/android/settings.gradle index 10a4a8da..1f8397e2 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -30,8 +30,10 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version '8.11.0' apply false - id("org.jetbrains.kotlin.android") version "2.2.0" apply false +// id "com.android.application" version '8.11.0' apply false +// id("org.jetbrains.kotlin.android") version "2.2.0" apply false + id "com.android.application" version '9.0.1' apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false id("com.google.gms.google-services") version "4.4.3" apply false id("com.google.firebase.crashlytics") version "3.0.4" apply false id('org.gradle.toolchains.foojay-resolver-convention') version '0.9.0' apply false diff --git a/assets/icons/chatbot.svg b/assets/icons/chatbot.svg new file mode 100644 index 00000000..59be2870 --- /dev/null +++ b/assets/icons/chatbot.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/microphone.svg b/assets/icons/microphone.svg new file mode 100644 index 00000000..0cbd4aaf --- /dev/null +++ b/assets/icons/microphone.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/send.svg b/assets/icons/send.svg new file mode 100644 index 00000000..37f97e2b --- /dev/null +++ b/assets/icons/send.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 1903f912..140b7721 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -676,6 +676,16 @@ "costCenter": "مركز التكلفة", "priceSAR": "السعر (ريال سعودي)", "amountSAR": "المبلغ (ريال سعودي)", - "amuLastThreeMonths": "AMU (آخر 3 أشهر)" + "amuLastThreeMonths": "AMU (آخر 3 أشهر)", + "buyerDetails": "تفاصيل المشتري", + "chatBot": "المساعد الذكي", + "helloAskMe": "مرحباً، اسألني", + "anything": "عن أي شيء...", + "thinking": "جاري التفكير", + "waitingForResponse": "في انتظار الرد...", + "typeAMessage": "اكتب رسالة...", + "initializingChatPleaseWait": "يرجى الانتظار، جاري تهيئة المحادثة...", + "am": "ص", + "pm": "م" } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index fea158b1..225edd2c 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -676,5 +676,15 @@ "costCenter": "Cost Center", "priceSAR": "Price (SAR)", "amountSAR": "Amount (SAR)", - "amuLastThreeMonths": "AMU (Last 3 months)" + "amuLastThreeMonths": "AMU (Last 3 months)", + "buyerDetails": "Buyer Details", + "chatBot": "ChatBot", + "helloAskMe": "Hello, ask me", + "anything": "anything...", + "thinking": "Thinking", + "waitingForResponse": "Waiting for response...", + "typeAMessage": "Type a message..", + "initializingChatPleaseWait": "Please wait, initializing chat...", + "am": "AM", + "pm": "PM" } \ No newline at end of file diff --git a/check.sh b/check.sh new file mode 100755 index 00000000..d8db1291 --- /dev/null +++ b/check.sh @@ -0,0 +1,699 @@ +#!/bin/bash + +# Enhanced ELF Alignment Checker for Android 16KB Page Size Compatibility +# This script checks if your app's native libraries are compatible with 16KB page size devices +# as required by Google Play starting November 1st, 2025 for apps targeting Android 15+ + +progname="${0##*/}" +progname="${progname%.sh}" + +# Color codes and formatting - detect if colors are supported +if [[ -t 1 ]] && command -v tput >/dev/null 2>&1 && tput colors >/dev/null 2>&1 && [[ $(tput colors) -ge 8 ]]; then + RED="\033[31m" + GREEN="\033[32m" + YELLOW="\033[33m" + BLUE="\033[34m" + CYAN="\033[36m" + PURPLE="\033[35m" + BOLD="\033[1m" + DIM="\033[2m" + ENDCOLOR="\033[0m" + USE_COLORS=true +else + # No color support - use plain text + RED="" + GREEN="" + YELLOW="" + BLUE="" + CYAN="" + PURPLE="" + BOLD="" + DIM="" + ENDCOLOR="" + USE_COLORS=false +fi + +# Unicode symbols with fallbacks +if [[ $USE_COLORS == true ]]; then + CHECK_MARK="✓" + CROSS_MARK="✗" + WARNING_MARK="⚠" + INFO_MARK="ℹ" + ROCKET="🚀" + GEAR="⚙" + PACKAGE="📦" + DOCUMENT="📄" + ALERT="🚨" + PARTY="🎉" + TOOLS="🔧" + BOOKS="📚" + ARROW="▶" +else + CHECK_MARK="[OK]" + CROSS_MARK="[X]" + WARNING_MARK="[!]" + INFO_MARK="[i]" + ROCKET="[>]" + GEAR="[*]" + PACKAGE="[P]" + DOCUMENT="[D]" + ALERT="[!]" + PARTY="[*]" + TOOLS="[T]" + BOOKS="[B]" + ARROW=">" +fi + +# Enhanced formatting functions +print_banner() { + local title="$1" + local subtitle="$2" + local width=88 + + echo + printf "%*s\n" $width | tr ' ' '═' + + # Main title + local title_len=${#title} + local title_padding=$(( (width - title_len - 4) / 2 )) + printf "║${BOLD}${BLUE}%*s %s %*s${ENDCOLOR}║\n" \ + $title_padding "" "$title" $title_padding "" + + # Subtitle if provided + if [[ -n "$subtitle" ]]; then + local subtitle_len=${#subtitle} + local subtitle_padding=$(( (width - subtitle_len - 4) / 2 )) + printf "║${DIM}%*s %s %*s${ENDCOLOR}║\n" \ + $subtitle_padding "" "$subtitle" $subtitle_padding "" + fi + + printf "%*s\n" $width | tr ' ' '═' + echo +} + +print_section() { + local icon="$1" + local title="$2" + local description="$3" + + echo + echo -e "${BOLD}${BLUE}$icon $title${ENDCOLOR}" + if [[ -n "$description" ]]; then + echo -e "${DIM}$description${ENDCOLOR}" + fi + printf "${BLUE}%*s${ENDCOLOR}\n" 80 | tr ' ' '─' +} + +print_status() { + local status="$1" + local message="$2" + local detail="$3" + + case $status in + "success") + printf " ${GREEN}${CHECK_MARK}${ENDCOLOR} %s" "$message" + ;; + "error") + printf " ${RED}${CROSS_MARK}${ENDCOLOR} %s" "$message" + ;; + "warning") + printf " ${YELLOW}${WARNING_MARK}${ENDCOLOR} %s" "$message" + ;; + "info") + printf " ${CYAN}${INFO_MARK}${ENDCOLOR} %s" "$message" + ;; + "processing") + printf " ${PURPLE}${GEAR}${ENDCOLOR} %s" "$message" + ;; + esac + + if [[ -n "$detail" ]]; then + printf "\n ${DIM}%s${ENDCOLOR}" "$detail" + fi + echo +} + +print_subsection() { + local title="$1" + echo + echo -e " ${BOLD}${PURPLE}${ARROW} $title${ENDCOLOR}" + printf " ${PURPLE}%*s${ENDCOLOR}\n" 50 | tr ' ' '┈' +} + +print_table_header() { + echo + local line1="┌$(printf '─%.0s' {1..29})┬$(printf '─%.0s' {1..18})┬$(printf '─%.0s' {1..15})┬$(printf '─%.0s' {1..13})┐" + local line2="│ $(printf '%-27s' 'Library') │ $(printf '%-16s' 'Architecture') │ $(printf '%-13s' 'Alignment') │ $(printf '%-11s' 'Status') │" + local line3="├$(printf '─%.0s' {1..29})┼$(printf '─%.0s' {1..18})┼$(printf '─%.0s' {1..15})┼$(printf '─%.0s' {1..13})┤" + + echo -e "${CYAN}$line1${ENDCOLOR}" + echo -e "${BOLD}${CYAN}$line2${ENDCOLOR}" + echo -e "${CYAN}$line3${ENDCOLOR}" +} + +print_table_row() { + local lib="$1" + local arch="$2" + local alignment="$3" + local status="$4" + local is_critical="$5" + + local status_symbol + local status_color + local status_text + + if [[ $status == "ALIGNED" ]]; then + status_symbol="$CHECK_MARK" + status_color="$GREEN" + status_text="PASS" + else + status_symbol="$CROSS_MARK" + if [[ $is_critical == "true" ]]; then + status_color="$RED" + status_text="FAIL" + else + status_color="$YELLOW" + status_text="WARN" + fi + fi + + # Truncate library name if too long + local display_lib="$lib" + if [[ ${#lib} -gt 27 ]]; then + display_lib="${lib:0:24}..." + fi + + printf "${CYAN}│${ENDCOLOR} %-27s ${CYAN}│${ENDCOLOR} %-16s ${CYAN}│${ENDCOLOR} %-13s ${CYAN}│${ENDCOLOR} ${status_color}%s %-7s${ENDCOLOR} ${CYAN}│${ENDCOLOR}\n" \ + "$display_lib" "$arch" "$alignment" "$status_symbol" "$status_text" + + if [[ $is_critical == "true" && $status == "UNALIGNED" ]]; then + printf "${CYAN}│${ENDCOLOR} ${RED}%-75s${ENDCOLOR} ${CYAN}│${ENDCOLOR}\n" " ${ALERT} CRITICAL: Required for Google Play compliance!" + fi +} + +print_table_footer() { + local line="└$(printf '─%.0s' {1..29})┴$(printf '─%.0s' {1..18})┴$(printf '─%.0s' {1..15})┴$(printf '─%.0s' {1..13})┘" + echo -e "${CYAN}$line${ENDCOLOR}" + echo +} + +print_summary_box() { + local title="$1" + local status="$2" # success, warning, error + shift 2 + local lines=("$@") + + local box_color + local title_color + case $status in + "success") box_color="$GREEN"; title_color="$GREEN" ;; + "warning") box_color="$YELLOW"; title_color="$YELLOW" ;; + "error") box_color="$RED"; title_color="$RED" ;; + *) box_color="$BLUE"; title_color="$BLUE" ;; + esac + + local width=78 + echo + printf "${box_color}┌%*s┐${ENDCOLOR}\n" $((width-2)) | tr ' ' '─' + + # Title + local title_len=${#title} + local title_padding=$(( (width - title_len - 4) / 2 )) + printf "${box_color}│${ENDCOLOR}${BOLD}${title_color}%*s %s %*s${ENDCOLOR}${box_color}│${ENDCOLOR}\n" \ + $title_padding "" "$title" $title_padding "" + + printf "${box_color}├%*s┤${ENDCOLOR}\n" $((width-2)) | tr ' ' '─' + + # Content lines + for line in "${lines[@]}"; do + printf "${box_color}│${ENDCOLOR} %-*s ${box_color}│${ENDCOLOR}\n" $((width-4)) "$line" + done + + printf "${box_color}└%*s┘${ENDCOLOR}\n" $((width-2)) | tr ' ' '─' + echo +} + +cleanup_trap() { + if [ -n "${tmp}" -a -d "${tmp}" ]; then + print_status "processing" "Cleaning up temporary files..." + rm -rf "${tmp}" + fi + exit $1 +} + +usage() { + print_banner "Android 16KB Page Size Compatibility Checker" "Google Play Compliance Tool" + + echo -e "${BOLD}DESCRIPTION:${ENDCOLOR}" + echo " This tool verifies that your Android app's native libraries are compatible" + echo " with 16KB page size devices, as required by Google Play starting November 1st, 2025." + echo + + echo -e "${BOLD}USAGE:${ENDCOLOR}" + echo -e " ${GREEN}$progname${ENDCOLOR} ${CYAN}[input-path|input-APK|input-APEX]${ENDCOLOR}" + echo + + echo -e "${BOLD}EXAMPLES:${ENDCOLOR}" + echo -e " ${DIM}# Check an APK file${ENDCOLOR}" + echo -e " $progname ${CYAN}app/build/outputs/apk/release/app-release.apk${ENDCOLOR}" + echo + echo -e " ${DIM}# Check a directory of native libraries${ENDCOLOR}" + echo -e " $progname ${CYAN}/path/to/native/libs/${ENDCOLOR}" + echo + + echo -e "${BOLD}WHAT THIS TOOL CHECKS:${ENDCOLOR}" + echo -e " ${CHECK_MARK} APK zip-alignment for 16KB boundaries (requires build-tools 35.0.0+)" + echo -e " ${CHECK_MARK} ELF segment alignment in native libraries (arm64-v8a and x86_64)" + echo -e " ${CHECK_MARK} Compliance with Android 16KB page size requirements" + echo + + echo -e "${BOLD}RESULT MEANINGS:${ENDCOLOR}" + echo -e " ${GREEN}${CHECK_MARK} PASS${ENDCOLOR} - Library is compatible with 16KB page sizes (2**14 or higher)" + echo -e " ${RED}${CROSS_MARK} FAIL${ENDCOLOR} - Library needs recompilation with 16KB ELF alignment" + echo -e " ${YELLOW}${WARNING_MARK} WARN${ENDCOLOR} - Non-critical architecture but should be fixed" + echo +} + +# Enhanced dependency checking +check_dependencies() { + print_section "$GEAR" "Dependency Check" "Verifying required tools are available" + + local missing_tools=() + local available_tools=() + + # Check each tool + local tools=("objdump" "unzip" "file") + for tool in "${tools[@]}"; do + if command -v "$tool" >/dev/null 2>&1; then + available_tools+=("$tool") + print_status "success" "$tool" "$(which "$tool")" + else + missing_tools+=("$tool") + print_status "error" "$tool not found" + fi + done + + if [ ${#missing_tools[@]} -gt 0 ]; then + echo + print_subsection "Installation Instructions" + for tool in "${missing_tools[@]}"; do + case $tool in + objdump) + print_status "info" "Install objdump:" "macOS: Xcode Command Line Tools or Android NDK's llvm-objdump" + print_status "info" "" "Linux: sudo apt-get install binutils (Ubuntu/Debian)" + ;; + unzip) + print_status "info" "Install unzip:" "Usually pre-installed on most systems" + print_status "info" "" "Linux: sudo apt-get install unzip" + ;; + file) + print_status "info" "Install file:" "Usually pre-installed on most systems" + print_status "info" "" "Linux: sudo apt-get install file" + ;; + esac + echo + done + exit 1 + fi + + print_status "success" "All dependencies satisfied" "${#available_tools[@]} tools available" +} + +# Validate input arguments +if [ ${#} -ne 1 ]; then + usage + exit 1 +fi + +case ${1} in + --help | -h | -\?) + usage + exit 0 + ;; + *) + dir="${1}" + ;; +esac + +# Validate input file/directory +if ! [ -f "${dir}" -o -d "${dir}" ]; then + print_status "error" "Invalid input: ${dir}" + echo " Please provide a valid APK file, APEX file, or directory containing native libraries." + exit 1 +fi + +# Check dependencies before proceeding +check_dependencies + +print_banner "ANALYSIS IN PROGRESS" "Checking 16KB Page Size Compatibility" + +print_status "processing" "Target: $(basename "${dir}")" +print_status "info" "Compliance Deadline: November 1st, 2025" +print_status "info" "Requirement: Apps targeting Android 15+ must support 16KB pages" + +# APK Processing +if [[ "${dir}" == *.apk ]]; then + trap 'cleanup_trap' EXIT + + print_section "$PACKAGE" "APK Analysis" "Processing Android Package file" + + # Enhanced zipalign check + if command -v zipalign >/dev/null 2>&1; then + if { zipalign --help 2>&1 | grep -q "\-P "; }; then + print_status "processing" "Checking APK zip-alignment for 16KB boundaries..." + + zip_result=$(zipalign -v -c -P 16 4 "${dir}" 2>&1) + zip_exit_code=$? + + if [ $zip_exit_code -eq 0 ]; then + print_status "success" "APK zip-alignment verification passed" + else + print_status "error" "APK zip-alignment verification failed" + echo " ${DIM}Details:${ENDCOLOR}" + echo "$zip_result" | grep -E 'lib/arm64-v8a|lib/x86_64|Verification|would be' | sed 's/^/ /' || echo " $zip_result" + fi + else + print_status "warning" "zipalign version doesn't support 16KB alignment checks" + print_status "info" "Solution: Update to Android SDK build-tools 35.0.0+" + echo " ${DIM}Update via Android Studio → SDK Manager → SDK Tools${ENDCOLOR}" + echo " ${DIM}Or run: sdkmanager \"build-tools;35.0.0\"${ENDCOLOR}" + fi + else + print_status "warning" "zipalign not found in PATH" + print_status "info" "Ensure Android SDK build-tools are installed and in PATH" + fi + + # Extract APK + dir_filename=$(basename "${dir}") + tmp=$(mktemp -d -t "${dir_filename%.apk}_out_XXXXX") + + if [ ! -d "${tmp}" ]; then + print_status "error" "Failed to create temporary directory" + exit 1 + fi + + print_status "processing" "Extracting native libraries from APK..." + if ! unzip -q "${dir}" "lib/*" -d "${tmp}" 2>/dev/null; then + print_summary_box "NO NATIVE LIBRARIES FOUND" "success" \ + "${PARTY} Your app contains only Java/Kotlin code!" \ + "" \ + "${CHECK_MARK} Apps without native libraries automatically support 16KB devices" \ + "${CHECK_MARK} No additional changes required for Google Play compliance" \ + "${CHECK_MARK} You're all set for the November 1st, 2025 deadline!" + cleanup_trap 0 + fi + + dir="${tmp}" +fi + +# APEX Processing +if [[ "${dir}" == *.apex ]]; then + trap 'cleanup_trap' EXIT + + print_section "$DOCUMENT" "APEX Analysis" "Processing Android Pony EXpress file" + + if ! command -v deapexer >/dev/null 2>&1; then + print_status "error" "deapexer tool not found" + echo " Please ensure Android SDK tools are properly installed and in your PATH." + exit 1 + fi + + dir_filename=$(basename "${dir}") + tmp=$(mktemp -d -t "${dir_filename%.apex}_out_XXXXX") + + if [ ! -d "${tmp}" ]; then + print_status "error" "Failed to create temporary directory" + exit 1 + fi + + print_status "processing" "Extracting APEX contents..." + if ! deapexer extract "${dir}" "${tmp}"; then + print_status "error" "Failed to extract APEX file" + cleanup_trap 1 + fi + + dir="${tmp}" +fi + +# Track libraries for enhanced summary +unaligned_libs=() +aligned_libs=() +critical_unaligned_libs=() +non_critical_unaligned_libs=() +total_libs=0 +critical_libs=0 + +print_section "$GEAR" "ELF Segment Analysis" "Scanning native libraries for 16KB alignment compliance" + +# Find all native libraries +matches="$(find "${dir}" -type f -name '*.so' 2>/dev/null)" +if [ -z "$matches" ]; then + # Also check for ELF files without .so extension + matches="$(find "${dir}" -type f -exec file {} \; 2>/dev/null | grep 'ELF' | cut -d: -f1)" +fi + +if [ -z "$matches" ]; then + print_summary_box "NO NATIVE LIBRARIES DETECTED" "success" \ + "${PARTY} Your app uses only Java/Kotlin code!" \ + "" \ + "${CHECK_MARK} No native library alignment issues to worry about" \ + "${CHECK_MARK} Already compatible with 16KB page size devices" \ + "${CHECK_MARK} Ready for Google Play's November 1st, 2025 requirement!" + cleanup_trap 0 +fi + +print_status "info" "Found native libraries - analyzing ELF segment alignment..." + +print_table_header + +IFS=$'\n' +for match in $matches; do + # Skip non-ELF files and nested packages + if [[ "${match}" == *".apk" ]]; then + continue + fi + if [[ "${match}" == *".apex" ]]; then + continue + fi + + # Verify it's actually an ELF file + if ! [[ $(file "${match}" 2>/dev/null) == *"ELF"* ]]; then + continue + fi + + total_libs=$((total_libs + 1)) + + # Determine architecture and criticality + arch="unknown" + is_critical="false" + if [[ "${match}" == *"arm64-v8a"* ]]; then + arch="arm64-v8a" + is_critical="true" + critical_libs=$((critical_libs + 1)) + elif [[ "${match}" == *"x86_64"* ]]; then + arch="x86_64" + is_critical="true" + critical_libs=$((critical_libs + 1)) + elif [[ "${match}" == *"armeabi-v7a"* ]]; then + arch="armeabi-v7a" + elif [[ "${match}" == *"x86"* ]]; then + arch="x86" + fi + + # Check ELF segment alignment + res="$(objdump -p "${match}" 2>/dev/null | grep LOAD | awk '{ print $NF }' | head -1)" + + if [ -z "$res" ]; then + print_table_row "$(basename "${match}")" "$arch" "UNKNOWN" "FAILED" "$is_critical" + continue + fi + + # Check if alignment meets 16KB requirement (2**14 or higher) + if [[ $res =~ 2\*\*(1[4-9]|[2-9][0-9]|[1-9][0-9]{2,}) ]]; then + print_table_row "$(basename "${match}")" "$arch" "$res" "ALIGNED" "$is_critical" + aligned_libs+=("${match}") + else + print_table_row "$(basename "${match}")" "$arch" "$res" "UNALIGNED" "$is_critical" + unaligned_libs+=("${match}") + + if [[ "$is_critical" == "true" ]]; then + critical_unaligned_libs+=("${match}") + else + non_critical_unaligned_libs+=("${match}") + fi + fi +done + +print_table_footer + +# Enhanced Summary Section +if [ ${#unaligned_libs[@]} -gt 0 ]; then + # Failure case - unaligned libraries found + summary_lines=( + "${ALERT} ACTION REQUIRED: Unaligned native libraries detected!" + "" + "Analysis Results:" + " • Total libraries scanned: $total_libs" + " • Critical architectures (64-bit): $critical_libs" + " • Libraries aligned: ${#aligned_libs[@]}" + " • Libraries UNALIGNED: ${#unaligned_libs[@]}" + ) + + if [ ${#critical_unaligned_libs[@]} -gt 0 ]; then + summary_lines+=(" • Critical failures: ${#critical_unaligned_libs[@]} (MUST FIX)") + fi + + if [ ${#non_critical_unaligned_libs[@]} -gt 0 ]; then + summary_lines+=(" • Non-critical warnings: ${#non_critical_unaligned_libs[@]} (SHOULD FIX)") + fi + + summary_lines+=("") + summary_lines+=("Google Play Compliance: FAILED") + summary_lines+=("Deadline: November 1st, 2025") + + print_summary_box "COMPATIBILITY CHECK FAILED" "error" "${summary_lines[@]}" + + # Detailed library breakdown + if [ ${#critical_unaligned_libs[@]} -gt 0 ]; then + print_subsection "Critical Libraries Requiring Immediate Attention" + for lib in "${critical_unaligned_libs[@]}"; do + arch_type="64-bit" + [[ "${lib}" == *"arm64-v8a"* ]] && arch_type="ARM64" + [[ "${lib}" == *"x86_64"* ]] && arch_type="x86_64" + print_status "error" "$(basename "$lib")" "$arch_type architecture - Required for Google Play" + done + fi + + if [ ${#non_critical_unaligned_libs[@]} -gt 0 ]; then + print_subsection "Non-Critical Libraries (Recommended to Fix)" + for lib in "${non_critical_unaligned_libs[@]}"; do + arch_type="32-bit" + [[ "${lib}" == *"armeabi-v7a"* ]] && arch_type="ARMv7" + [[ "${lib}" == *"x86"* ]] && arch_type="x86" + print_status "warning" "$(basename "$lib")" "$arch_type architecture" + done + fi + + # Comprehensive fix instructions + print_section "$TOOLS" "How to Fix Unaligned Libraries" "Step-by-step guide to achieve 16KB compatibility" + + print_subsection "Step 1: Update Your Build Environment" + print_status "info" "Android Gradle Plugin (AGP)" "Upgrade to version 8.5.1 or higher" + print_status "info" "Android NDK" "Update to NDK r27 or higher (r28+ recommended)" + print_status "info" "Build Tools" "Ensure Android SDK build-tools 35.0.0+ for zipalign" + + print_subsection "Step 2: Configure 16KB ELF Alignment" + echo + echo -e "${BOLD}For NDK r28 and newer:${ENDCOLOR}" + print_status "success" "Automatic Support" "16KB alignment enabled by default - no changes needed!" + + echo + echo -e "${BOLD}For NDK r27:${ENDCOLOR}" + print_status "info" "Gradle Configuration" "Add to your app/build.gradle:" + echo -e "${DIM} android {" + echo -e " defaultConfig {" + echo -e " externalNativeBuild {" + echo -e " cmake {" + echo -e " arguments '-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON'" + echo -e " }" + echo -e " }" + echo -e " }" + echo -e " }${ENDCOLOR}" + + print_status "info" "NDK-Build Configuration" "Add to Application.mk:" + echo -e "${DIM} APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true${ENDCOLOR}" + + echo + echo -e "${BOLD}For NDK r26 and older:${ENDCOLOR}" + print_status "info" "Manual Linker Flags" "Add to your native build configuration:" + echo -e "${DIM} # For Android.mk:" + echo -e " LOCAL_LDFLAGS += \"-Wl,-z,max-page-size=16384\"" + echo + echo -e " # For CMakeLists.txt:" + echo -e " target_link_options(\${CMAKE_PROJECT_NAME} PRIVATE \"-Wl,-z,max-page-size=16384\")${ENDCOLOR}" + + print_subsection "Step 3: Update Library Packaging" + print_status "info" "AGP 8.5.1+" "Uncompressed libraries used by default (no changes needed)" + print_status "info" "Older AGP versions" "Add to app/build.gradle:" + echo -e "${DIM} android {" + echo -e " packagingOptions {" + echo -e " jniLibs {" + echo -e " useLegacyPackaging = true" + echo -e " }" + echo -e " }" + echo -e " }${ENDCOLOR}" + + print_subsection "Step 4: Build and Verify" + print_status "processing" "Clean Build" "Run ./gradlew clean" + print_status "processing" "Rebuild Project" "Run ./gradlew assembleRelease" + print_status "processing" "Re-run This Script" "Verify all libraries are now aligned" + print_status "processing" "Test on Device" "Use Android 15 emulator with 16KB system image" + + print_section "$BOOKS" "Additional Resources" "Learn more about 16KB page size support" + print_status "info" "Official Guide" "https://developer.android.com/guide/practices/page-sizes" + print_status "info" "NDK Documentation" "https://developer.android.com/ndk/guides/" + print_status "info" "Testing Guide" "Use 'adb shell getconf PAGE_SIZE' (should return 16384)" + print_status "info" "Emulator Setup" "Android 15 system images with 16KB page size" + + final_message="" + if [ ${#critical_unaligned_libs[@]} -gt 0 ]; then + final_message="CRITICAL: Fix required for Google Play compliance by November 1st, 2025!" + else + final_message="Warning: Non-critical issues found - recommended to fix for complete compatibility" + fi + + print_summary_box "NEXT STEPS" "error" \ + "$final_message" \ + "" \ + "1. Update your build tools and NDK version" \ + "2. Apply the configuration changes above" \ + "3. Clean and rebuild your project" \ + "4. Run this script again to verify fixes" \ + "5. Test thoroughly on 16KB devices/emulator" + + cleanup_trap 1 +else + # Success case - all libraries aligned + summary_lines=( + "${PARTY} All native libraries are properly aligned!" + "" + "Analysis Results:" + " • Total libraries scanned: $total_libs" + " • Critical architectures: $critical_libs" + " • All libraries: ${#aligned_libs[@]}/${total_libs} ALIGNED" + "" + "Google Play Compliance: PASSED ${CHECK_MARK}" + "Ready for November 1st, 2025 deadline!" + ) + + print_summary_box "COMPATIBILITY CHECK PASSED" "success" "${summary_lines[@]}" + + print_section "$ROCKET" "Next Steps" "Ensure complete 16KB compatibility" + + print_status "success" "Library Alignment" "All ELF segments properly aligned for 16KB pages" + print_status "info" "Runtime Testing" "Test on Android 15 emulator with 16KB system image" + print_status "info" "Code Review" "Check for hardcoded PAGE_SIZE dependencies in your code" + print_status "info" "Third-party SDKs" "Verify all dependencies are 16KB compatible" + print_status "info" "Zipalign Verification" "Run: zipalign -c -P 16 -v 4 your-app.apk" + + print_subsection "Testing Commands" + echo -e "${DIM} # Verify page size on device/emulator${ENDCOLOR}" + echo -e "${DIM} adb shell getconf PAGE_SIZE # Should return: 16384${ENDCOLOR}" + echo + echo -e "${DIM} # Verify APK alignment${ENDCOLOR}" + echo -e "${DIM} zipalign -c -P 16 -v 4 app-release.apk${ENDCOLOR}" + + print_summary_box "CONGRATULATIONS!" "success" \ + "${PARTY} Your app is 16KB page size compatible!" \ + "" \ + "${CHECK_MARK} All native libraries meet Google Play requirements" \ + "${CHECK_MARK} Ready for devices with 16KB page sizes" \ + "${CHECK_MARK} Compliant with November 1st, 2025 deadline" \ + "" \ + "Recommendation: Test thoroughly on 16KB environment to ensure" \ + "no runtime issues exist in your application code." +fi + +echo +print_status "info" "Analysis completed at $(date)" +echo diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 7c569640..391a902b 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -20,7 +20,5 @@ ???? CFBundleVersion 1.0 - MinimumOSVersion - 12.0 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 3df58f13..4afd8f7c 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ @@ -12,6 +12,7 @@ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 76AE113C28E078EA00513112 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 76AE113B28E078EA00513112 /* GoogleService-Info.plist */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 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 */; }; @@ -39,6 +40,7 @@ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7627313328E45A2400F354F7 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; 76AE113B28E078EA00513112 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; @@ -57,6 +59,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, 1CCF895CFB3C95DEBB84A05F /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -75,6 +78,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -144,13 +148,16 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, C4CFBC4C5CAC00182015ACD5 /* [CP] Embed Pods Frameworks */, - EF29CE25BB57F5AA48CE1136 /* [CP] Copy Pods Resources */, + D9FFC0F21D4146458615A992 /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -179,6 +186,9 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; @@ -242,23 +252,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 6C6E76E4FB19FAF11C24005D /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -291,6 +284,23 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; + D9FFC0F21D4146458615A992 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -367,7 +377,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -454,7 +464,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -504,7 +514,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -600,6 +610,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 9c12df59..5db441f5 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -5,6 +5,24 @@ + + + + + + + + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + FlutterSceneDelegate + UISceneConfigurationName + flutter + UISceneStoryboardFile + Main + + + + NSAppTransportSecurity NSAllowsArbitraryLoads @@ -53,6 +74,8 @@ This App requires access to your location to mark your attendance. NSPhotoLibraryUsageDescription This app requires photo library access to select image as document & upload it. + NSSpeechRecognitionUsageDescription + This app requires speech recognition to convert your voice to text. NSMicrophoneUsageDescription This app requires microphone access to for call. NSSpeechRecognitionUsageDescription diff --git a/lib/api/dashboard_api_client.dart b/lib/api/dashboard_api_client.dart index 61aef69d..05e4d325 100644 --- a/lib/api/dashboard_api_client.dart +++ b/lib/api/dashboard_api_client.dart @@ -6,7 +6,6 @@ import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:http/http.dart' as http; import 'package:device_info_plus/device_info_plus.dart'; -import 'package:mobile_device_identifier/mobile_device_identifier.dart'; import 'package:mohem_flutter_app/api/api_client.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/classes/consts.dart'; @@ -37,7 +36,7 @@ class DashboardApiClient { Future getAttendanceTracking() async { String url = "${ApiConsts.erpRest}GET_Attendance_Tracking"; - Map postParams = {}; + Map postParams = {}; postParams.addAll(AppState().postParamsJson); return await ApiClient().postJsonForObject( (json) { @@ -51,7 +50,7 @@ class DashboardApiClient { Future getOpenNotifications() async { String url = "${ApiConsts.erpRest}GET_OPEN_NOTIFICATIONS"; - Map postParams = {}; + Map postParams = {}; postParams.addAll(AppState().postParamsJson); return await ApiClient().postJsonForObject( (json) { @@ -65,7 +64,7 @@ class DashboardApiClient { Future getCOCNotifications() async { String url = "${ApiConsts.cocRest}Mohemm_ITG_ReviewerAdmin_Pending_Tasks"; - Map postParams = {"Date": DateUtil.getISODateFormat(DateTime.now()), "EmployeeNumber": AppState().memberInformationList?.eMPLOYEENUMBER}; + Map postParams = {"Date": DateUtil.getISODateFormat(DateTime.now()), "EmployeeNumber": AppState().memberInformationList?.eMPLOYEENUMBER}; postParams.addAll(AppState().postParamsJson); return await ApiClient().postJsonForObject( (json) { @@ -79,7 +78,7 @@ class DashboardApiClient { Future getItgFormsPendingTask() async { String url = "${ApiConsts.cocRest}ITGFormsPendingTasks"; - Map postParams = {}; + Map postParams = {}; postParams.addAll(AppState().postParamsJson); return await ApiClient().postJsonForObject( (json) { @@ -93,13 +92,13 @@ class DashboardApiClient { Future> getAccrualBalances(String effectiveDate, {String? empID}) async { String url = "${ApiConsts.erpRest}GET_ACCRUAL_BALANCES"; - Map postParams = {"P_EFFECTIVE_DATE": effectiveDate}; + Map postParams = {"P_EFFECTIVE_DATE": effectiveDate}; postParams.addAll(AppState().postParamsJson); if (empID != null) postParams["P_SELECTED_EMPLOYEE_NUMBER"] = empID; return await ApiClient().postJsonForObject( (json) { GenericResponseModel responseData = GenericResponseModel.fromJson(json); - return responseData.getAccrualBalancesList ?? []; + return responseData.getAccrualBalancesList ?? []; }, url, postParams, @@ -108,13 +107,13 @@ class DashboardApiClient { Future> getTicketAccuralBalance(String effectiveDate, {String? empID}) async { String url = "${ApiConsts.erpRest}GET_TICKET_ACCRUAL_BALANCES"; - Map postParams = {"P_EFFECTIVE_DATE": effectiveDate}; + Map postParams = {"P_EFFECTIVE_DATE": effectiveDate}; postParams.addAll(AppState().postParamsJson); if (empID != null) postParams["P_SELECTED_EMPLOYEE_NUMBER"] = empID; return await ApiClient().postJsonForObject( (json) { GenericResponseModel responseData = GenericResponseModel.fromJson(json); - return responseData.getAccrualBalancesTicketList ?? []; + return responseData.getAccrualBalancesTicketList ?? []; }, url, postParams, @@ -123,7 +122,7 @@ class DashboardApiClient { Future getOpenMissingSwipes() async { String url = "${ApiConsts.erpRest}GET_OPEN_MISSING_SWIPES"; - Map postParams = {}; + Map postParams = {}; postParams.addAll(AppState().postParamsJson); return await ApiClient().postJsonForObject( (json) { @@ -138,7 +137,7 @@ class DashboardApiClient { //Menus List Future> getListMenu() async { String url = "${ApiConsts.erpRest}GET_MENU"; - Map postParams = {}; + Map postParams = {}; postParams.addAll(AppState().postParamsJson); return await ApiClient().postJsonForObject( (json) { @@ -276,7 +275,7 @@ class DashboardApiClient { String QRValue = "", String payrollCode = "", }) async { - var _mobileDeviceIdentifierPlugin = MobileDeviceIdentifier(); + Codec stringToBase64 = utf8.fuse(base64); String url = "${ApiConsts.swpRest}AuthenticateAndSwipeUserSupportNFC"; Uuid uuid = Uuid(); @@ -284,7 +283,7 @@ class DashboardApiClient { // Generate a v4 (random) id if (Platform.isAndroid) { AndroidDeviceInfo androidInfo = await DeviceInfoPlugin().androidInfo; - deviceId = await _mobileDeviceIdentifierPlugin.getDeviceId() ?? androidInfo.id; + deviceId = await androidInfo.id; deviceId = stringToBase64.encode(deviceId); deviceId = deviceId.length >= 50 ? deviceId.substring(0, 48) : deviceId; } else if (Platform.isIOS) { @@ -502,4 +501,32 @@ class DashboardApiClient { postParams, ); } + + Future getChatBotSession() async { + String url = "${ApiConsts.erpRest}Atlas_InitiateChat"; + Map postParams = {}; + postParams.addAll(AppState().postParamsJson); + return await ApiClient().postJsonForObject( + (json) { + GenericResponseModel responseData = GenericResponseModel.fromJson(json); + return responseData; + }, + url, + postParams, + ); + } + + Future sendChatBotMessage({required AtlasInitiateChatResponse atlasChatTokenResponse, required String atlasChatText}) async { + String url = "${ApiConsts.erpRest}Atlas_ContinueChat"; + Map postParams = {"Atlas_AccessToken": atlasChatTokenResponse.atlasAccessToken, "Atlas_ChatSessionID": atlasChatTokenResponse.atlasChatSessionId, "Atlas_ChatText": atlasChatText}; + postParams.addAll(AppState().postParamsJson); + return await ApiClient().postJsonForObject( + (json) { + GenericResponseModel responseData = GenericResponseModel.fromJson(json); + return responseData; + }, + url, + postParams, + ); + } } diff --git a/lib/classes/colors.dart b/lib/classes/colors.dart index 1b0dd069..4c88da98 100644 --- a/lib/classes/colors.dart +++ b/lib/classes/colors.dart @@ -67,4 +67,5 @@ class MyColors { static const Color lightGreyIconColor = Color(0xff919191); static const Color selectedBorderColor = Color(0xff37A4BE); static const Color mazayaRedColor = Color(0xffED1C2B); + static const Color hintTextColor = Color(0xff9AA4B2); } diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index 5e980a90..79e04300 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -12,13 +12,13 @@ class ApiConsts { // static String baseUrl = "https://mohemm.hmg.com"; // New Live server static String ssoBaseUrl = "https://sso.hmg.com"; // New Live server // - // static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver + static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver + // static String baseUrl = "http://10.201.214.101:2027/"; // static String baseUrl = "http://10.20.200.111:1010/"; - static String baseUrl = "https://mohemm.hmg.com"; // New Live server + // static String baseUrl = "https://mohemm.hmg.com"; // New Live server //static String eLearningBaseUrl = "https://elearning.hmg.com/moodle_dev/api/"; // Elearning server UAT static String eLearningBaseUrl = "https://elearning.hmg.com/moodle/api/"; // Elearning server live static String enSATLiveURL = "https://ensat.hmg.com/auth/autologin"; // ENSAT server live - // static String baseUrl = "http://10.20.200.111:1010/"; // static String baseUrl = "https://webservices.hmg.com"; // PreProd @@ -34,8 +34,7 @@ class ApiConsts { static String user = baseUrlServices + "api/User/"; static String cocRest = baseUrlServices + "COCWS.svc/REST/"; - static String ssoAuthRedirection = ssoBaseUrl + "/api/auth/connect"; - + static String ssoAuthRedirection = "https://sso-uat.hmg.com/api/auth/connect"; //Chat static String chatServerBaseUrl = "https://apiderichat.hmg.com/"; @@ -63,7 +62,6 @@ class ApiConsts { static String marathonBaseUrlUAT = "https://marathoon.com/uatservice/api/"; static String marathonBaseUrl = marathonBaseUrlLive; - // static String marathonBaseUrl = marathonBaseUrlUAT; static String marathonBaseUrlServices = "https://marathoon.com/service/"; static String marathonParticipantLoginUrl = marathonBaseUrl + "auth/participantlogin"; diff --git a/lib/classes/date_uitl.dart b/lib/classes/date_uitl.dart index 1f4396e3..f7b807b6 100644 --- a/lib/classes/date_uitl.dart +++ b/lib/classes/date_uitl.dart @@ -22,7 +22,7 @@ class DateUtil { return DateTime.now(); } - static DateTime convertStringToDate(String date) { + static DateTime convertStringToDate(String? date) { // /Date(1585774800000+0300)/ if (date != null) { const start = "/Date("; @@ -34,8 +34,9 @@ class DateUtil { date.substring(startIndex + start.length, endIndex), ), ); - } else + } else { return DateTime.now(); + } } static DateTime convertSimpleStringDateToDate(String date, {bool isITG = false}) { diff --git a/lib/classes/utils.dart b/lib/classes/utils.dart index 2ae4fe3b..ea12d8ff 100644 --- a/lib/classes/utils.dart +++ b/lib/classes/utils.dart @@ -20,7 +20,9 @@ import 'package:mohem_flutter_app/provider/chat_provider_model.dart'; import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart'; import 'package:mohem_flutter_app/widgets/loading_dialog.dart'; import 'package:nfc_manager/nfc_manager.dart'; -import 'package:nfc_manager/platform_tags.dart'; +import 'package:nfc_manager/nfc_manager_android.dart'; +import 'package:nfc_manager/nfc_manager_ios.dart'; +// import 'package:nfc_manager/platform_tags.dart'; import 'package:shared_preferences/shared_preferences.dart'; // ignore_for_file: avoid_annotating_with_dynamic @@ -349,15 +351,34 @@ class Utils { NfcManager.instance .startSession( onDiscovered: (NfcTag tag) async { - MifareUltralight f; + // MifareUltralight f; + // if (Platform.isAndroid) { + // print(tag); + // f = MifareUltralight(tag: tag, identifier: tag.data["nfca"]["identifier"], type: 2, maxTransceiveLength: 252, timeout: 22); + // } else { + // f = MifareUltralight(tag: tag, identifier: tag.data["mifare"]["identifier"], type: 2, maxTransceiveLength: 252, timeout: 22); + // } + + Uint8List? id; + if (Platform.isAndroid) { - print(tag); - f = MifareUltralight(tag: tag, identifier: tag.data["nfca"]["identifier"], type: 2, maxTransceiveLength: 252, timeout: 22); - } else { - f = MifareUltralight(tag: tag, identifier: tag.data["mifare"]["identifier"], type: 2, maxTransceiveLength: 252, timeout: 22); + id = NfcTagAndroid.from(tag)?.id; + } else if (Platform.isIOS) { + id = MiFareIos.from(tag)?.identifier; + } + + if (id == null) { + await NfcManager.instance.stopSession(); + return; } - String identifier = f.identifier.map((e) => e.toRadixString(16).padLeft(2, '0')).join(''); - NfcManager.instance.stopSession(); + + String identifier = + id.map((e) => e.toRadixString(16).padLeft(2, '0')).join(''); + print(identifier); + + await NfcManager.instance.stopSession(); + // String identifier = f.identifier.map((e) => e.toRadixString(16).padLeft(2, '0')).join(''); + // NfcManager.instance.stopSession(); onRead(identifier); }, pollingOptions: {NfcPollingOption.iso14443}, diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 9bae8df3..c3376e9a 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -13,6 +13,7 @@ import 'package:mohem_flutter_app/ui/chat/manage_group.dart'; import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_create_request.dart'; import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_ovr_home.dart'; import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_request_detailed.dart'; +import 'package:mohem_flutter_app/ui/chatbot/chatbot_screen.dart'; import 'package:mohem_flutter_app/ui/landing/dashboard_screen.dart'; import 'package:mohem_flutter_app/ui/landing/itg/change_itg_ad_password_screen.dart'; import 'package:mohem_flutter_app/ui/landing/itg/its_add_screen_video_image.dart'; @@ -44,6 +45,7 @@ import 'package:mohem_flutter_app/ui/my_team/subordinate_leave.dart'; import 'package:mohem_flutter_app/ui/my_team/team_members.dart'; import 'package:mohem_flutter_app/ui/my_team/view_attendance.dart'; import 'package:mohem_flutter_app/ui/parking_qr/parking_qr.dart'; +import 'package:mohem_flutter_app/ui/parking_qr/parking_qr.dart'; import 'package:mohem_flutter_app/ui/payslip/monthly_pay_slip_screen.dart'; import 'package:mohem_flutter_app/ui/performance/performance_appraisal_screen.dart'; import 'package:mohem_flutter_app/ui/profile/add_update_family_member.dart'; @@ -59,6 +61,7 @@ import 'package:mohem_flutter_app/ui/screens/announcements/announcement_details. import 'package:mohem_flutter_app/ui/screens/announcements/announcements.dart'; import 'package:mohem_flutter_app/ui/screens/child_education/child_education_assistance.dart'; import 'package:mohem_flutter_app/ui/screens/courses/courses_screen.dart'; + // import 'package:mohem_flutter_app/ui/my_attendance/work_from_home_screen.dart'; import 'package:mohem_flutter_app/ui/screens/eit/add_eit.dart'; import 'package:mohem_flutter_app/ui/screens/event_activity/event_activity.dart'; @@ -199,6 +202,7 @@ class AppRoutes { static const String chat = "/chat"; static const String chatDetailed = "/chatDetailed"; static const String chatFavoriteUsers = "/chatFavoriteUsers"; + static const String chatBotHome = "/chatBotHome"; //Group Chat static const String manageGroup = "/manageGroup"; @@ -238,8 +242,8 @@ class AppRoutes { newPassword: (BuildContext context) => NewPasswordScreen(), forgotPassword: (BuildContext context) => ForgotPasswordScreen(), todayAttendance: (BuildContext context) => TodayAttendanceScreen2(), - //eit + //eit addEitScreen: (BuildContext context) => AddEITScreen(), //Work List @@ -250,7 +254,6 @@ class AppRoutes { worklistSettings: (BuildContext context) => WorklistSettings(), // Leave Balance - leaveBalance: (BuildContext context) => LeaveBalance(), addLeaveBalance: (BuildContext context) => AddLeaveBalanceScreen(), @@ -330,6 +333,7 @@ class AppRoutes { chat: (BuildContext context) => ChatHome(), chatDetailed: (BuildContext context) => ChatDetailScreen(), chatFavoriteUsers: (BuildContext context) => ChatFavoriteUsersScreen(), + chatBotHome: (BuildContext context) => ChatBotScreen(), //Group Chat manageGroup: (BuildContext context) => ManageGroupScreen(), @@ -357,5 +361,6 @@ class AppRoutes { //Parking QR parkingQr: (BuildContext context) => const ParkingQrScreen(), + activityScreen: (BuildContext context) => const EventActivityScreen(), }; } diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart index 516fe03a..4c96539f 100644 --- a/lib/generated/codegen_loader.g.dart +++ b/lib/generated/codegen_loader.g.dart @@ -669,7 +669,6 @@ class CodegenLoader extends AssetLoader{ "noCoursesAvailable": "لا توجد دورات متاحة", "pullToRefresh": "اسحب للتحديث", "courseUrlNotAvailable": "رابط الدورة غير متوفر", - "buyerDetails": "تفاصيل المشتري", "etqanOVR": "الإبلاغ عن حادثة", "etqanRequest": "طلبات الحوادث", "selectProject": "حدد المشروع", @@ -690,7 +689,17 @@ class CodegenLoader extends AssetLoader{ "costCenter": "مركز التكلفة", "priceSAR": "السعر (ريال سعودي)", "amountSAR": "المبلغ (ريال سعودي)", - "amuLastThreeMonths": "AMU (آخر 3 أشهر)" + "amuLastThreeMonths": "AMU (آخر 3 أشهر)", + "buyerDetails": "تفاصيل المشتري", + "chatBot": "المساعد الذكي", + "helloAskMe": "مرحباً، اسألني", + "anything": "عن أي شيء...", + "thinking": "جاري التفكير", + "waitingForResponse": "في انتظار الرد...", + "typeAMessage": "اكتب رسالة...", + "initializingChatPleaseWait": "يرجى الانتظار، جاري تهيئة المحادثة...", + "am": "ص", + "pm": "م" }; static const Map _en_US = { "mohemm": "Mohemm", @@ -1347,7 +1356,6 @@ static const Map _en_US = { "noCoursesAvailable": "No Courses Available", "pullToRefresh": "Pull to refresh", "courseUrlNotAvailable": "Course URL not available", - "buyerDetails": "Buyer Details", "etqanOVR": "Report An Incident", "etqanRequest": "Incident Requests", "selectProject": "Select Project", @@ -1368,7 +1376,17 @@ static const Map _en_US = { "costCenter": "Cost Center", "priceSAR": "Price (SAR)", "amountSAR": "Amount (SAR)", - "amuLastThreeMonths": "AMU (Last 3 months)" + "amuLastThreeMonths": "AMU (Last 3 months)", + "buyerDetails": "Buyer Details", + "chatBot": "ChatBot", + "helloAskMe": "Hello, ask me", + "anything": "anything...", + "thinking": "Thinking", + "waitingForResponse": "Waiting for response...", + "typeAMessage": "Type a message..", + "initializingChatPleaseWait": "Please wait, initializing chat...", + "am": "AM", + "pm": "PM" }; static const Map> mapLocales = {"ar_SA": _ar_SA, "en_US": _en_US}; } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 9abe224b..f2659017 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -665,5 +665,14 @@ abstract class LocaleKeys { static const priceSAR = 'priceSAR'; static const amountSAR = 'amountSAR'; static const amuLastThreeMonths = 'amuLastThreeMonths'; + static const chatBot = 'chatBot'; + static const helloAskMe = 'helloAskMe'; + static const anything = 'anything'; + static const thinking = 'thinking'; + static const waitingForResponse = 'waitingForResponse'; + static const typeAMessage = 'typeAMessage'; + static const initializingChatPleaseWait = 'initializingChatPleaseWait'; + static const am = 'am'; + static const pm = 'pm'; } diff --git a/lib/generated_plugin_registrant.dart b/lib/generated_plugin_registrant.dart index 8fe66629..7d54d7ba 100644 --- a/lib/generated_plugin_registrant.dart +++ b/lib/generated_plugin_registrant.dart @@ -8,7 +8,6 @@ import 'package:audio_session/audio_session_web.dart'; import 'package:camera_web/camera_web.dart'; -import 'package:file_picker/_internal/file_picker_web.dart'; import 'package:firebase_core_web/firebase_core_web.dart'; import 'package:firebase_messaging_web/firebase_messaging_web.dart'; import 'package:fluttertoast/fluttertoast_web.dart'; @@ -27,7 +26,6 @@ import 'package:flutter_web_plugins/flutter_web_plugins.dart'; void registerPlugins(Registrar registrar) { AudioSessionWeb.registerWith(registrar); CameraPlugin.registerWith(registrar); - FilePickerWeb.registerWith(registrar); FirebaseCoreWeb.registerWith(registrar); FirebaseMessagingWeb.registerWith(registrar); FluttertoastWebPlugin.registerWith(registrar); diff --git a/lib/main.dart b/lib/main.dart index 0105672f..91706023 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -19,6 +19,8 @@ import 'package:month_year_picker/month_year_picker.dart'; import 'package:provider/provider.dart'; import 'package:provider/single_child_widget.dart'; import 'package:sizer/sizer.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb; // test uat account // username 199067 @@ -77,32 +79,33 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return Sizer( - builder: (BuildContext context, Orientation orientation, DeviceType deviceType) { + builder: (BuildContext context, Orientation orientation, ScreenType deviceType){ PostParamsModel? obj = AppState().postParamsObject; - obj?.languageID = EasyLocalization.of(context)?.locale.languageCode == "ar" ? 1 : 2; - AppState().setPostParamsModel(obj!); - List> delegates = context.localizationDelegates; - // delegates.add(GlobalMaterialLocalizations.delegate); - delegates.add(MonthYearPickerLocalizations.delegate); - return SafeArea( + obj?.languageID = EasyLocalization.of(context)?.locale.languageCode == "ar" ? 1 : 2; + AppState().setPostParamsModel(obj!); + List> delegates = context.localizationDelegates; + // delegates.add(GlobalMaterialLocalizations.delegate); + delegates.add(MonthYearPickerLocalizations.delegate); + return SafeArea( top: false, bottom: Platform.isAndroid ? true : false, - child: MaterialApp( - navigatorKey: AppRoutes.navigatorKey, - builder: (BuildContext context, Widget? child) { - return MediaQuery(data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0), child: child!); - }, - theme: AppTheme.getTheme(EasyLocalization.of(context)?.locale.languageCode == "ar"), - debugShowCheckedModeBanner: false, - localizationsDelegates: delegates, - supportedLocales: context.supportedLocales, - locale: context.locale, - initialRoute: AppRoutes.initialRoute, - routes: AppRoutes.routes, - ), - ); - }, + child: MaterialApp( + navigatorKey: AppRoutes.navigatorKey, + builder: (BuildContext context, Widget? child) { + return MediaQuery(data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0), child: child!); + }, + theme: AppTheme.getTheme(EasyLocalization.of(context)?.locale.languageCode == "ar"), + debugShowCheckedModeBanner: false, + localizationsDelegates: delegates, + supportedLocales: context.supportedLocales, + locale: context.locale, + initialRoute: AppRoutes.initialRoute, + routes: AppRoutes.routes, + ), + ); + } ); + } } diff --git a/lib/models/generic_response_model.dart b/lib/models/generic_response_model.dart index 01e3056d..1c1662b5 100644 --- a/lib/models/generic_response_model.dart +++ b/lib/models/generic_response_model.dart @@ -399,6 +399,9 @@ class GenericResponseModel { bool? isSMSSent; PortalDirectionData? portalDirectionData; String? Mohemm_ENSAT_GetItgToken_Response; + AtlasInitiateChatResponse? atlasInitiateChatResponse; + AtlasContinueChatResponseDetails? atlasContinueChatResponseDetails; + GenericResponseModel({ this.date, @@ -672,6 +675,9 @@ class GenericResponseModel { this.isSMSSent, this.getFADisposalNtfDetails, this.Mohemm_ENSAT_GetItgToken_Response, + this.atlasInitiateChatResponse, + this.atlasContinueChatResponseDetails, + }); GenericResponseModel.fromJson(Map json) { @@ -1465,6 +1471,10 @@ class GenericResponseModel { ePharmacyGetItemOnHandList = json['ePharmacy_GetItemOnHandList']; isActiveCode = json['isActiveCode']; isSMSSent = json['isSMSSent']; + atlasInitiateChatResponse = json["Atlas_InitiateChat_Response"] == null ? null : AtlasInitiateChatResponse.fromJson(json["Atlas_InitiateChat_Response"]); + atlasContinueChatResponseDetails = json["Atlas_ContinueChat_ResponseDetails"] == null ? null : AtlasContinueChatResponseDetails.fromJson(json["Atlas_ContinueChat_ResponseDetails"]); + + Mohemm_ENSAT_GetItgToken_Response = json['Mohemm_ENSAT_GetItgToken_Response']; } @@ -1951,6 +1961,9 @@ class GenericResponseModel { data['ePharmacy_GetItemOnHandList'] = this.ePharmacyGetItemOnHandList; data['isActiveCode'] = this.isActiveCode; data['isSMSSent'] = this.isSMSSent; + data["Atlas_InitiateChat_Response"] = atlasInitiateChatResponse?.toJson(); + data["Atlas_ContinueChat_ResponseDetails"] = atlasContinueChatResponseDetails?.toJson(); + data['Mohemm_ENSAT_GetItgToken_Response'] = this.Mohemm_ENSAT_GetItgToken_Response; return data; } @@ -1977,3 +1990,138 @@ class TicketBookingResult { TicketBookingResult(this.success, this.clientId); } + +class AtlasInitiateChatResponse { + String? atlasAccessToken; + String? atlasChatSessionId; + String? atlasTokenType; + + AtlasInitiateChatResponse({this.atlasAccessToken, this.atlasChatSessionId, this.atlasTokenType}); + + factory AtlasInitiateChatResponse.fromRawJson(String str) => AtlasInitiateChatResponse.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AtlasInitiateChatResponse.fromJson(Map json) => + AtlasInitiateChatResponse(atlasAccessToken: json["Atlas_AccessToken"], atlasChatSessionId: json["Atlas_ChatSessionID"], atlasTokenType: json["Atlas_TokenType"]); + + Map toJson() => {"Atlas_AccessToken": atlasAccessToken, "Atlas_ChatSessionID": atlasChatSessionId, "Atlas_TokenType": atlasTokenType}; +} + + + +class AtlasContinueChatResponseDetails { + AiResponse? aiResponse; + QueryClassification? queryClassification; + String? sessionId; + AiResponse? userMessage; + + AtlasContinueChatResponseDetails({ + this.aiResponse, + this.queryClassification, + this.sessionId, + this.userMessage, + }); + + factory AtlasContinueChatResponseDetails.fromRawJson(String str) => AtlasContinueChatResponseDetails.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AtlasContinueChatResponseDetails.fromJson(Map json) => AtlasContinueChatResponseDetails( + aiResponse: json["ai_response"] == null ? null : AiResponse.fromJson(json["ai_response"]), + queryClassification: json["query_classification"] == null ? null : QueryClassification.fromJson(json["query_classification"]), + sessionId: json["session_id"], + userMessage: json["user_message"] == null ? null : AiResponse.fromJson(json["user_message"]), + ); + + Map toJson() => { + "ai_response": aiResponse?.toJson(), + "query_classification": queryClassification?.toJson(), + "session_id": sessionId, + "user_message": userMessage?.toJson(), + }; +} + +class AiResponse { + DateTime? createdAt; + String? id; + StructuredData? structuredData; + String? text; + + AiResponse({ + this.createdAt, + this.id, + this.structuredData, + this.text, + }); + + factory AiResponse.fromRawJson(String str) => AiResponse.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AiResponse.fromJson(Map json) => AiResponse( + createdAt: json["created_at"] == null ? null : DateTime.parse(json["created_at"]), + id: json["id"], + structuredData: json["structured_data"] == null ? null : StructuredData.fromJson(json["structured_data"]), + text: json["text"], + ); + + Map toJson() => { + "created_at": createdAt?.toIso8601String(), + "id": id, + "structured_data": structuredData?.toJson(), + "text": text, + }; +} + +class StructuredData { + String? answer; + List? sources; + String? type; + + StructuredData({ + this.answer, + this.sources, + this.type, + }); + + factory StructuredData.fromRawJson(String str) => StructuredData.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory StructuredData.fromJson(Map json) => StructuredData( + answer: json["answer"], + sources: json["sources"] == null ? [] : List.from(json["sources"]!.map((x) => x)), + type: json["type"], + ); + + Map toJson() => { + "answer": answer, + "sources": sources == null ? [] : List.from(sources!.map((x) => x)), + "type": type, + }; +} + +class QueryClassification { + int? confidence; + String? type; + + QueryClassification({ + this.confidence, + this.type, + }); + + factory QueryClassification.fromRawJson(String str) => QueryClassification.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory QueryClassification.fromJson(Map json) => QueryClassification( + confidence: json["confidence"], + type: json["type"], + ); + + Map toJson() => { + "confidence": confidence, + "type": type, + }; +} \ No newline at end of file diff --git a/lib/models/get_mobile_login_info_list_model.dart b/lib/models/get_mobile_login_info_list_model.dart index a2f7799b..d544ef58 100644 --- a/lib/models/get_mobile_login_info_list_model.dart +++ b/lib/models/get_mobile_login_info_list_model.dart @@ -1,6 +1,6 @@ class GetMobileLoginInfoListModel { int? iD; - int? employeeID; + String? employeeID; int? channelID; int? companyID; String? deviceType; diff --git a/lib/provider/chat_provider_model.dart b/lib/provider/chat_provider_model.dart index 43315ab4..8a17370a 100644 --- a/lib/provider/chat_provider_model.dart +++ b/lib/provider/chat_provider_model.dart @@ -1609,13 +1609,13 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin { await File('$dirPath/.nomedia').create(); } path = "$dirPath/${AppState().chatDetails!.response!.id}-$receiverID-${DateTime.now().microsecondsSinceEpoch}.aac"; - recorderController = RecorderController() - ..androidEncoder = AndroidEncoder.aac - ..androidOutputFormat = AndroidOutputFormat.mpeg4 - ..iosEncoder = IosEncoder.kAudioFormatMPEG4AAC - ..sampleRate = 6000 - ..updateFrequency = const Duration(milliseconds: 100) - ..bitRate = 18000; + recorderController = RecorderController(); + // ..androidEncoder = AndroidEncoder.aac + // ..androidOutputFormat = AndroidOutputFormat.mpeg4 + // ..iosEncoder = IosEncoder.kAudioFormatMPEG4AAC + // ..sampleRate = 6000 + // ..updateFrequency = const Duration(milliseconds: 100) + // ..bitRate = 18000; playerController = PlayerController(); } diff --git a/lib/theme/app_theme.dart b/lib/theme/app_theme.dart index f2286006..d2d9fab7 100644 --- a/lib/theme/app_theme.dart +++ b/lib/theme/app_theme.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; diff --git a/lib/ui/chatbot/chatbot_screen.dart b/lib/ui/chatbot/chatbot_screen.dart new file mode 100644 index 00000000..964eaa29 --- /dev/null +++ b/lib/ui/chatbot/chatbot_screen.dart @@ -0,0 +1,499 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:mohem_flutter_app/api/dashboard_api_client.dart'; +import 'package:mohem_flutter_app/app_state/app_state.dart'; +import 'package:mohem_flutter_app/classes/colors.dart'; +import 'package:mohem_flutter_app/classes/utils.dart'; +import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; +import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; +import 'package:mohem_flutter_app/models/generic_response_model.dart'; +import 'package:speech_to_text/speech_recognition_result.dart'; +import 'package:speech_to_text/speech_to_text.dart'; + + +class MessagesHolder{ + static List messages = []; +} + +enum ChatMode { home, chatActive } + +class ChatMessage { + final String text; + final bool isUser; + final DateTime timestamp; + + ChatMessage({required this.text, required this.isUser, required this.timestamp}); +} + +class ChatBotScreen extends StatefulWidget { + const ChatBotScreen({Key? key}) : super(key: key); + + @override + State createState() => _ChatBotScreenState(); +} + +class _ChatBotScreenState extends State { + ChatMode _currentMode = ChatMode.home; + final List _messages = []; + final TextEditingController _messageController = TextEditingController(); + final ScrollController _scrollController = ScrollController(); + AtlasInitiateChatResponse? atlasChatTokenResponse; + bool _isThinking = false; + + // Speech to text + final SpeechToText _speechToText = SpeechToText(); + bool _speechEnabled = false; + + @override + void initState() { + // TODO: implement initState + super.initState(); + getChatSession(); + _initSpeech(); + } + + /// Initialize speech to text + void _initSpeech() async { + _speechEnabled = await _speechToText.initialize(); + + if(MessagesHolder.messages.isNotEmpty){ + setState(() { + _messages.addAll(MessagesHolder.messages); + _currentMode = ChatMode.chatActive; + }); + } + setState(() {}); + } + + /// Start listening for speech + void _startListening() async { + await _speechToText.listen( + onResult: _onSpeechResult, + localeId: AppState().isArabic(context) ? 'ar_SA' : 'en_US', + ); + setState(() {}); + } + + /// Stop listening for speech + void _stopListening() async { + await _speechToText.stop(); + setState(() {}); + } + + /// Callback when speech is recognized + void _onSpeechResult(SpeechRecognitionResult result) { + setState(() { + _messageController.text = result.recognizedWords; + // Move cursor to end of text + _messageController.selection = TextSelection.fromPosition(TextPosition(offset: _messageController.text.length)); + }); + } + + void getChatSession() async { + try { + GenericResponseModel? res = await DashboardApiClient().getChatBotSession(); + if (res != null) { + setState(() { + atlasChatTokenResponse = res.atlasInitiateChatResponse; + }); + } + } catch (ex) { + Utils.handleException(ex, context, null); + } + } + + @override + void dispose() { + atlasChatTokenResponse = null; + + _messageController.dispose(); + _scrollController.dispose(); + super.dispose(); + } + + void _sendMessage({String? suggestionText}) async { + String text = suggestionText ?? _messageController.text.trim(); + if (text.isEmpty || atlasChatTokenResponse == null) return; + try { + setState(() { + _messages.add(ChatMessage(text: text, isUser: true, timestamp: DateTime.now())); + _currentMode = ChatMode.chatActive; + _messageController.clear(); + _isThinking = true; + }); + _scrollToBottom(); + GenericResponseModel? res = await DashboardApiClient().sendChatBotMessage(atlasChatTokenResponse: atlasChatTokenResponse!, atlasChatText: text); + if (res != null) { + res.atlasContinueChatResponseDetails; + if (mounted) { + setState(() { + _isThinking = false; + _messages.add( + ChatMessage( + text: res.atlasContinueChatResponseDetails!.aiResponse!.structuredData!.answer ?? "", + isUser: false, + timestamp: res.atlasContinueChatResponseDetails!.aiResponse!.createdAt ?? DateTime.now(), + ), + ); + }); + } + _scrollToBottom(); + } + } catch (ex) { + setState(() { + _isThinking = false; + }); + Utils.handleException(ex, context, null); + } + + _scrollToBottom(); + } + + void _scrollToBottom() { + Future.delayed(const Duration(milliseconds: 100), () { + if (_scrollController.hasClients) { + _scrollController.animateTo(_scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 300), curve: Curves.easeOut); + } + }); + } + + void _sendSuggestion(String text) { + if (atlasChatTokenResponse == null) { + Utils.showToast(LocaleKeys.initializingChatPleaseWait.tr()); + return; + } + _sendMessage(suggestionText: text); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: MyColors.backgroundColor, + appBar: AppBar( + automaticallyImplyLeading: false, + title: Text(LocaleKeys.chatBot.tr(), style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w700, color: Color(0xFF2B353E))), + backgroundColor: MyColors.backgroundColor, + centerTitle: false, + actions: [ + IconButton( + icon: const Icon(Icons.minimize, color: Color(0xFF2B353E)), + onPressed: () { + MessagesHolder.messages = _messages; + Navigator.of(context).pop(); + }, + ), + IconButton( + icon: const Icon(Icons.close, color: Color(0xFF2B353E)), + onPressed: () { + MessagesHolder.messages.clear(); + Navigator.of(context).pop(); + }, + ), + ], + ), + body: Column(children: [Expanded(child: _currentMode == ChatMode.home ? _buildHomeMode() : _buildChatMode()), _buildBottomInputBar()]), + ); + } + + Widget _buildHomeMode() { + return Center( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 24.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text(LocaleKeys.helloAskMe.tr(), textAlign: TextAlign.center, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w700, color: Color(0xFF2E303A))), + Text(LocaleKeys.anything.tr(), textAlign: TextAlign.center, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w700, color: Color(0xFF2E303A))), + const SizedBox(height: 50), + const Wrap( + alignment: WrapAlignment.center, + spacing: 8, + runSpacing: 12, + children: [ + // _buildSuggestionChip("Nostalgia Perfume", () => _sendSuggestion("Nostalgia Perfume")), + // _buildSuggestionChip("Al Nafoura", () => _sendSuggestion("Al Nafoura")), + // _buildSuggestionChip("Al Nafoura", () => _sendSuggestion("Al Nafoura")), + // _buildSuggestionChip("Al Jadi", () => _sendSuggestion("Al Jadi")), + ], + ), + ], + ), + ), + ); + } + + Widget _buildChatMode() { + return ListView.separated( + controller: _scrollController, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), + itemCount: _messages.length + (_isThinking ? 1 : 0), + itemBuilder: (BuildContext context, int index) { + if (index == _messages.length && _isThinking) { + return _buildThinkingIndicator(); + } + ChatMessage message = _messages[index]; + return _buildMessageBubble(message); + }, + separatorBuilder: (BuildContext context, int index) { + return const SizedBox(height: 15); + }, + ); + } + + Widget _buildThinkingIndicator() { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + gradient: const LinearGradient(colors: [Color(0xFF32D892), Color(0xFF259CB8)], begin: Alignment.centerLeft, end: Alignment.centerRight), + borderRadius: BorderRadius.circular(15), + boxShadow: [BoxShadow(color: const Color(0xFF000000).withValues(alpha: 0.0784), offset: const Offset(0, 0), blurRadius: 13, spreadRadius: 0)], + ), + child: const ThinkingDotsAnimation(), + ), + const Spacer(flex: 2), + ], + ); + } + + Widget _buildMessageBubble(ChatMessage message) { + bool isUser = message.isUser; + Widget bubbleContent = Row( + mainAxisAlignment: isUser ? MainAxisAlignment.end : MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (isUser) const Spacer(flex: 2), + Flexible( + flex: 7, + child: Column( + crossAxisAlignment: isUser ? CrossAxisAlignment.end : CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: isUser ? Colors.white : null, + gradient: isUser ? null : const LinearGradient(colors: [Color(0xFF32D892), Color(0xFF259CB8)], begin: Alignment.centerLeft, end: Alignment.centerRight), + borderRadius: BorderRadius.circular(15), + boxShadow: isUser ? null : [BoxShadow(color: const Color(0xFF000000).withValues(alpha: 0.0784), offset: const Offset(0, 0), blurRadius: 13, spreadRadius: 0)], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text(message.text, softWrap: true, style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: isUser ? const Color(0xFF2B353E) : MyColors.white)), + Align( + alignment: Alignment.centerRight, + child: Text( + _formatTimestamp(message.timestamp), + style: TextStyle(fontSize: 11, fontWeight: FontWeight.w500, letterSpacing: -0.4, color: isUser ? const Color(0xFF2B353E) : MyColors.whiteColor), + ), + ), + ], + ), + ), + ], + ), + ), + if (!isUser) const Spacer(flex: 2), + ], + ); + + if (!isUser) { + return AnimatedMessageBubble(child: bubbleContent); + } + return bubbleContent; + } + + String _formatTimestamp(DateTime timestamp) { + int hourInt = timestamp.hour; + int minute = timestamp.minute; + String period = hourInt >= 12 ? LocaleKeys.pm.tr() : LocaleKeys.am.tr(); + int formattedHour = hourInt % 12 == 0 ? 12 : hourInt % 12; + String hour = formattedHour.toString().padLeft(2, '0'); + String min = minute.toString().padLeft(2, '0'); + String month = timestamp.month.toString().padLeft(2, '0'); + String day = timestamp.day.toString().padLeft(2, '0'); + int year = timestamp.year; + return "$month/$day/$year $hour:$min $period"; + } + + Widget _buildSuggestionChip(String label, VoidCallback onTap) { + return Material( + color: Colors.white, + elevation: 3, + shadowColor: const Color(0xFF000015).withValues(alpha: 0.0784), + borderRadius: BorderRadius.circular(10), + child: InkWell( + borderRadius: BorderRadius.circular(10), + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Text(label, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: MyColors.darkTextColor, letterSpacing: 0.2)), + ), + ), + ); + } + + Widget _buildBottomInputBar() { + return Container( + decoration: const BoxDecoration(color: MyColors.backgroundColor), + child: SafeArea( + top: false, + child: Row( + children: [ + // Plus button + // IconButton(padding: EdgeInsets.zero, icon: const Icon(Icons.add, color: MyColors.darkTextColor, size: 34), onPressed: () {}), + // const SizedBox(width: 12), + // Text field + Expanded( + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + height: 48, + decoration: BoxDecoration(color: Colors.transparent, borderRadius: BorderRadius.circular(10), border: BoxBorder.all(width: 1, color: const Color(0xFFE5E5E5))), + child: Row( + children: [ + Expanded( + child: TextField( + controller: _messageController, + onSubmitted: _isThinking ? null : (_) => _sendMessage(), + enabled: !_isThinking, + maxLines: 1, + textAlignVertical: TextAlignVertical.center, + decoration: InputDecoration( + hintText: _isThinking ? LocaleKeys.waitingForResponse.tr() : LocaleKeys.typeAMessage.tr(), + hintStyle: const TextStyle(color: MyColors.hintTextColor, fontSize: 14, fontWeight: FontWeight.normal), + border: InputBorder.none, + isDense: true, + contentPadding: const EdgeInsets.symmetric(vertical: 10), + ), + ), + ), + GestureDetector( + onTap: _isThinking ? null : (_speechEnabled ? (_speechToText.isNotListening ? _startListening : _stopListening) : null), + child: SvgPicture.asset( + "assets/icons/microphone.svg", + colorFilter: ColorFilter.mode(_isThinking ? MyColors.hintTextColor : (_speechToText.isListening ? Colors.red : MyColors.darkTextColor), BlendMode.srcIn), + ), + ), + ], + ), + ), + ), + const SizedBox(width: 6), + // Send button + GestureDetector( + onTap: _isThinking ? null : _sendMessage, + child: Container( + width: 48, + height: 48, + decoration: BoxDecoration(color: _isThinking ? MyColors.hintTextColor.withValues(alpha: 0.3) : MyColors.white, shape: BoxShape.circle), + child: Center( + child: SvgPicture.asset( + "assets/icons/send.svg", + matchTextDirection: true, + colorFilter: _isThinking ? const ColorFilter.mode(MyColors.hintTextColor, BlendMode.srcIn) : null, + ), + ), + ), + ), + ], + ), + ), + ).paddingOnly(left: 16, right: 16, bottom: 16); + } +} + +class ThinkingDotsAnimation extends StatefulWidget { + const ThinkingDotsAnimation({Key? key}) : super(key: key); + + @override + State createState() => _ThinkingDotsAnimationState(); +} + +class _ThinkingDotsAnimationState extends State with SingleTickerProviderStateMixin { + late AnimationController _controller; + + @override + void initState() { + super.initState(); + _controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 1200))..repeat(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text(LocaleKeys.thinking.tr(), style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Colors.white)), + const SizedBox(width: 2), + AnimatedBuilder( + animation: _controller, + builder: (BuildContext context, Widget? child) { + int dotCount = (_controller.value * 4).floor() % 4; + String dots = '.' * dotCount; + return SizedBox(width: 18, child: Text(dots, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Colors.white))); + }, + ), + ], + ); + } +} + +class AnimatedMessageBubble extends StatefulWidget { + final Widget child; + + const AnimatedMessageBubble({Key? key, required this.child}) : super(key: key); + + @override + State createState() => _AnimatedMessageBubbleState(); +} + +class _AnimatedMessageBubbleState extends State with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _fadeAnimation; + late Animation _slideAnimation; + late Animation _scaleAnimation; + + @override + void initState() { + super.initState(); + _controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 400)); + + _fadeAnimation = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut)); + + _slideAnimation = Tween(begin: const Offset(-0.3, 0.0), end: Offset.zero).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic)); + + _scaleAnimation = Tween(begin: 0.8, end: 1.0).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOutBack)); + + _controller.forward(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SlideTransition( + position: _slideAnimation, + child: FadeTransition(opacity: _fadeAnimation, child: ScaleTransition(scale: _scaleAnimation, alignment: Alignment.centerLeft, child: widget.child)), + ); + } +} + + + diff --git a/lib/ui/landing/dashboard_screen.dart b/lib/ui/landing/dashboard_screen.dart index 2c114c3b..9155c0f4 100644 --- a/lib/ui/landing/dashboard_screen.dart +++ b/lib/ui/landing/dashboard_screen.dart @@ -324,103 +324,103 @@ class _DashboardScreenState extends State with WidgetsBindingOb child: Consumer( builder: (BuildContext context, DashboardProviderModel model, Widget? child) { return (model.isAttendanceTrackingLoading - ? GetAttendanceTrackingShimmer() - : Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(15), - gradient: const LinearGradient( - transform: GradientRotation(.46), - begin: Alignment.topRight, - end: Alignment.bottomLeft, - colors: [MyColors.gradiantEndColor, MyColors.gradiantStartColor], - ), - ), - child: Stack( - alignment: Alignment.center, - children: [ - if (model.isTimeRemainingInSeconds == 0) SvgPicture.asset("assets/images/thumb.svg"), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.markAttendance.tr().toText14(color: Colors.white, isBold: true), - if (model.isTimeRemainingInSeconds == 0) DateTime.now().toString().split(" ")[0].toText12(color: Colors.white), - if (model.isTimeRemainingInSeconds != 0) - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, + ? GetAttendanceTrackingShimmer() + : Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), + gradient: const LinearGradient( + transform: GradientRotation(.46), + begin: Alignment.topRight, + end: Alignment.bottomLeft, + colors: [MyColors.gradiantEndColor, MyColors.gradiantStartColor], + ), + ), + child: Stack( + alignment: Alignment.center, + children: [ + if (model.isTimeRemainingInSeconds == 0) SvgPicture.asset("assets/images/thumb.svg"), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.markAttendance.tr().toText14(color: Colors.white, isBold: true), + if (model.isTimeRemainingInSeconds == 0) DateTime.now().toString().split(" ")[0].toText12(color: Colors.white), + if (model.isTimeRemainingInSeconds != 0) + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 9.height, + Directionality( + textDirection: ui.TextDirection.ltr, + child: CountdownTimer( + endTime: model.endTime, + onEnd: null, + endWidget: "00:00:00".toText14(color: Colors.white, isBold: true), + textStyle: const TextStyle(color: Colors.white, fontSize: 14, letterSpacing: -0.48, fontWeight: FontWeight.bold), + ), + ), + LocaleKeys.timeLeftToday.tr().toText12(color: Colors.white), + 9.height, + ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(20)), + child: LinearProgressIndicator( + value: model.progress, + minHeight: 8, + valueColor: const AlwaysStoppedAnimation(Colors.white), + backgroundColor: const Color(0xff196D73), + ), + ), + ], + ), + ], + ).paddingOnly(top: 12, right: 15, left: 12), + ), + Row( children: [ - 9.height, - Directionality( - textDirection: ui.TextDirection.ltr, - child: CountdownTimer( - endTime: model.endTime, - onEnd: null, - endWidget: "00:00:00".toText14(color: Colors.white, isBold: true), - textStyle: const TextStyle(color: Colors.white, fontSize: 14, letterSpacing: -0.48, fontWeight: FontWeight.bold), - ), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.checkIn.tr().toText12(color: Colors.white), + (model.attendanceTracking!.pSwipeIn == null ? "--:--" : model.attendanceTracking!.pSwipeIn).toString().toText14( + color: Colors.white, + isBold: true, + ), + 4.height, + ], + ).paddingOnly(left: 12, right: 12), ), - LocaleKeys.timeLeftToday.tr().toText12(color: Colors.white), - 9.height, - ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(20)), - child: LinearProgressIndicator( - value: model.progress, - minHeight: 8, - valueColor: const AlwaysStoppedAnimation(Colors.white), - backgroundColor: const Color(0xff196D73), + Container( + margin: EdgeInsets.only(top: AppState().isArabic(context) ? 6 : 0), + width: 45, + height: 45, + padding: const EdgeInsets.only(left: 10, right: 10), + decoration: BoxDecoration( + color: const Color(0xff259EA4), + borderRadius: BorderRadius.only( + bottomRight: AppState().isArabic(context) ? const Radius.circular(0) : const Radius.circular(15), + bottomLeft: AppState().isArabic(context) ? const Radius.circular(15) : const Radius.circular(0), + ), ), - ), + child: SvgPicture.asset(model.isTimeRemainingInSeconds == 0 ? "assets/images/biometrics.svg" : "assets/images/biometrics.svg"), + ).onPress(() { + showMyBottomSheet(context, callBackFunc: () {}, child: MarkAttendanceWidget(model, isFromDashboard: true)); + }), ], ), - ], - ).paddingOnly(top: 12, right: 15, left: 12), - ), - Row( - children: [ - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.checkIn.tr().toText12(color: Colors.white), - (model.attendanceTracking!.pSwipeIn == null ? "--:--" : model.attendanceTracking!.pSwipeIn).toString().toText14( - color: Colors.white, - isBold: true, - ), - 4.height, - ], - ).paddingOnly(left: 12, right: 12), + ], ), - Container( - margin: EdgeInsets.only(top: AppState().isArabic(context) ? 6 : 0), - width: 45, - height: 45, - padding: const EdgeInsets.only(left: 10, right: 10), - decoration: BoxDecoration( - color: const Color(0xff259EA4), - borderRadius: BorderRadius.only( - bottomRight: AppState().isArabic(context) ? const Radius.circular(0) : const Radius.circular(15), - bottomLeft: AppState().isArabic(context) ? const Radius.circular(15) : const Radius.circular(0), - ), - ), - child: SvgPicture.asset(model.isTimeRemainingInSeconds == 0 ? "assets/images/biometrics.svg" : "assets/images/biometrics.svg"), - ).onPress(() { - showMyBottomSheet(context, callBackFunc: () {}, child: MarkAttendanceWidget(model, isFromDashboard: true)); - }), ], ), - ], - ), - ], - ), - ).onPress(() { - Navigator.pushNamed(context, AppRoutes.todayAttendance); - })) + ).onPress(() { + Navigator.pushNamed(context, AppRoutes.todayAttendance); + })) .animatedSwither(); }, ), @@ -490,70 +490,70 @@ class _DashboardScreenState extends State with WidgetsBindingOb flex: 2, child: RichText( text: - AppState().isArabic(context) - ? TextSpan( - children: [ - TextSpan( - text: 'اطلع على مميزات', - style: TextStyle( - fontSize: 16, - letterSpacing: -0.2, - fontFamily: AppState().isArabic(context) ? 'Cairo' : 'Poppins', - fontWeight: FontWeight.w700, - height: 24 / 16, - color: const Color(0xFF5D5E5E), - ), - ), - TextSpan( - text: ' مزايا', - style: TextStyle( - fontSize: 16, - fontFamily: AppState().isArabic(context) ? 'Cairo' : 'Poppins', - fontWeight: FontWeight.w700, - letterSpacing: -0.2, - height: 24 / 16, - color: MyColors.mazayaRedColor, // Use your MAZAYA red color here if defined, e.g. MyColors.mazayaRed - ), - ), - ], - ) - : TextSpan( - children: [ - TextSpan( - text: LocaleKeys.explore.tr() + ' ', - style: const TextStyle( - fontSize: 16, - letterSpacing: -0.2, - fontFamily: 'Poppins', - fontWeight: FontWeight.w700, - height: 24 / 16, - color: Color(0xFF5D5E5E), - ), - ), - TextSpan( - text: LocaleKeys.mazaya.tr(), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w700, - fontFamily: 'Poppins', - letterSpacing: -0.2, - height: 24 / 16, - color: MyColors.mazayaRedColor, // Use your MAZAYA red color here if defined, e.g. MyColors.mazayaRed - ), - ), - TextSpan( - text: ' ' + LocaleKeys.benefits.tr(), - style: const TextStyle( - fontSize: 16, - letterSpacing: -0.2, - fontFamily: 'Poppins', - fontWeight: FontWeight.w700, - height: 24 / 16, - color: Color(0xFF5D5E5E), - ), - ), - ], - ), + AppState().isArabic(context) + ? TextSpan( + children: [ + TextSpan( + text: 'اطلع على مميزات', + style: TextStyle( + fontSize: 16, + letterSpacing: -0.2, + fontFamily: AppState().isArabic(context) ? 'Cairo' : 'Poppins', + fontWeight: FontWeight.w700, + height: 24 / 16, + color: const Color(0xFF5D5E5E), + ), + ), + TextSpan( + text: ' مزايا', + style: TextStyle( + fontSize: 16, + fontFamily: AppState().isArabic(context) ? 'Cairo' : 'Poppins', + fontWeight: FontWeight.w700, + letterSpacing: -0.2, + height: 24 / 16, + color: MyColors.mazayaRedColor, // Use your MAZAYA red color here if defined, e.g. MyColors.mazayaRed + ), + ), + ], + ) + : TextSpan( + children: [ + TextSpan( + text: LocaleKeys.explore.tr() + ' ', + style: const TextStyle( + fontSize: 16, + letterSpacing: -0.2, + fontFamily: 'Poppins', + fontWeight: FontWeight.w700, + height: 24 / 16, + color: Color(0xFF5D5E5E), + ), + ), + TextSpan( + text: LocaleKeys.mazaya.tr(), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + fontFamily: 'Poppins', + letterSpacing: -0.2, + height: 24 / 16, + color: MyColors.mazayaRedColor, // Use your MAZAYA red color here if defined, e.g. MyColors.mazayaRed + ), + ), + TextSpan( + text: ' ' + LocaleKeys.benefits.tr(), + style: const TextStyle( + fontSize: 16, + letterSpacing: -0.2, + fontFamily: 'Poppins', + fontWeight: FontWeight.w700, + height: 24 / 16, + color: Color(0xFF5D5E5E), + ), + ), + ], + ), ), ), const Expanded(flex: 1, child: SizedBox()), @@ -797,28 +797,28 @@ class _DashboardScreenState extends State with WidgetsBindingOb SvgPicture.asset( "assets/icons/chat/chat.svg", color: - !checkIfPrivilegedForChat() - ? MyColors.lightGreyE3Color - : currentIndex == 4 - ? MyColors.grey3AColor - : cProvider.disbaleChatForThisUser - ? MyColors.lightGreyE3Color - : MyColors.grey98Color, + !checkIfPrivilegedForChat() + ? MyColors.lightGreyE3Color + : currentIndex == 4 + ? MyColors.grey3AColor + : cProvider.disbaleChatForThisUser + ? MyColors.lightGreyE3Color + : MyColors.grey98Color, ).paddingAll(4), Consumer( builder: (BuildContext cxt, ChatProviderModel data, Widget? child) { return !checkIfPrivilegedForChat() ? const SizedBox() : Positioned( - right: 0, - top: 0, - child: Container( - padding: const EdgeInsets.only(left: 4, right: 4), - alignment: Alignment.center, - decoration: BoxDecoration(color: cProvider.disbaleChatForThisUser ? MyColors.pinkDarkColor : MyColors.redColor, borderRadius: BorderRadius.circular(17)), - child: data.chatUConvCounter.toString().toText10(color: Colors.white), - ), - ); + right: 0, + top: 0, + child: Container( + padding: const EdgeInsets.only(left: 4, right: 4), + alignment: Alignment.center, + decoration: BoxDecoration(color: cProvider.disbaleChatForThisUser ? MyColors.pinkDarkColor : MyColors.redColor, borderRadius: BorderRadius.circular(17)), + child: data.chatUConvCounter.toString().toText10(color: Colors.white), + ), + ); }, ), ], @@ -849,6 +849,19 @@ class _DashboardScreenState extends State with WidgetsBindingOb }, ), ), + // floatingActionButton: FloatingActionButton.large( + // onPressed: () { + // Navigator.pushNamed(context, AppRoutes.chatBotHome); + // }, + // backgroundColor: Colors.transparent, + // elevation: 0, + // focusElevation: 0, + // hoverElevation: 0, + // highlightElevation: 0, + // + // shape: const CircleBorder(), + // child: SvgPicture.asset("assets/icons/chatbot.svg", width: 100, height: 100), + // ), ), ); } diff --git a/lib/ui/login/verify_last_login_screen.dart b/lib/ui/login/verify_last_login_screen.dart index 5c760925..36425aa2 100644 --- a/lib/ui/login/verify_last_login_screen.dart +++ b/lib/ui/login/verify_last_login_screen.dart @@ -112,7 +112,7 @@ class _VerifyLastLoginScreenState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, - children: [LocaleKeys.lastLoginDetails.tr().toText16(), DateUtil.formatDateToDate(DateUtil.convertStringToDate(mobileLoginInfoListModel!.editedOn ?? DateTime.now().toString()), false).toText12()], + children: [LocaleKeys.lastLoginDetails.tr().toText16(), DateUtil.formatDateToDate(DateUtil.convertStringToDate(mobileLoginInfoListModel?.editedOn ?? null), false).toText12()], ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -121,7 +121,7 @@ class _VerifyLastLoginScreenState extends State { LocaleKeys.verificationType.tr().toText10(color: MyColors.grey57Color), getVerificationType(mobileLoginInfoListModel!.loginType!).toText12(), const Expanded(child: SizedBox()), - DateUtil.formatDateToTime(DateUtil.convertStringToDate(mobileLoginInfoListModel!.editedOn!)).toText12(), + DateUtil.formatDateToTime(DateUtil.convertStringToDate(mobileLoginInfoListModel?.editedOn)).toText12(), ], ), ], @@ -208,7 +208,7 @@ class _VerifyLastLoginScreenState extends State { Future loginWithFaceIDAndBiometrics() async { - IOSAuthMessages iosStrings = const IOSAuthMessages(cancelButton: 'cancel', goToSettingsButton: 'settings', goToSettingsDescription: 'Please set up your Touch ID.', lockOut: 'Please reenable your Touch ID'); + IOSAuthMessages iosStrings = const IOSAuthMessages(cancelButton: 'cancel'); // IOSAuthMessages iosStrings = const IOSAuthMessages( // cancelButton: 'cancel', @@ -219,9 +219,7 @@ class _VerifyLastLoginScreenState extends State { bool authenticated = false; try { - authenticated = await auth.authenticate(localizedReason: 'Scan your fingerprint to authenticate', options: const AuthenticationOptions( - useErrorDialogs: true, stickyAuth: true, biometricOnly: true, - ), + authenticated = await auth.authenticate(localizedReason: 'Scan your fingerprint to authenticate', authMessages: [ iosStrings, const AndroidAuthMessages(), diff --git a/lib/ui/login/verify_login_screen.dart b/lib/ui/login/verify_login_screen.dart index e0015e0e..625db60f 100644 --- a/lib/ui/login/verify_login_screen.dart +++ b/lib/ui/login/verify_login_screen.dart @@ -515,11 +515,11 @@ class _VerifyLoginScreenState extends State { Future loginWithFaceIDAndBiometrics() async { IOSAuthMessages iosStrings = - const IOSAuthMessages(cancelButton: 'cancel', goToSettingsButton: 'settings', goToSettingsDescription: 'Please set up your Touch ID.', lockOut: 'Please reenable your Touch ID'); + const IOSAuthMessages(cancelButton: 'cancel',); bool authenticated = false; try { authenticated = await auth.authenticate(localizedReason: 'Scan your fingerprint to authenticate', - options: const AuthenticationOptions(useErrorDialogs: true, stickyAuth: true, biometricOnly: true,), authMessages: [ + authMessages: [ iosStrings, const AndroidAuthMessages(), ],); diff --git a/lib/ui/marathon/widgets/question_card.dart b/lib/ui/marathon/widgets/question_card.dart index 0965c9d5..28395d11 100644 --- a/lib/ui/marathon/widgets/question_card.dart +++ b/lib/ui/marathon/widgets/question_card.dart @@ -30,18 +30,21 @@ class QuestionCard extends StatelessWidget { width: double.infinity, child: AppinioSwiper( duration: const Duration(milliseconds: 400), - padding: EdgeInsets.zero, + swipeOptions: const SwipeOptions.all(), isDisabled: true, controller: provider.swiperController, - unswipe: (int index, AppinioSwiperDirection direction) {}, - onSwipe: (int index, AppinioSwiperDirection direction) {}, - cards: provider.cardContentList, + cardCount: provider.cardContentList.length, + cardBuilder: (BuildContext context, int index) { + return provider.cardContentList[index]; + }, ), ), ); } } + + class CardContent extends StatelessWidget { const CardContent({Key? key}) : super(key: key); diff --git a/lib/ui/my_team/subordinate_leave.dart b/lib/ui/my_team/subordinate_leave.dart index e2fb9995..85e85c0e 100644 --- a/lib/ui/my_team/subordinate_leave.dart +++ b/lib/ui/my_team/subordinate_leave.dart @@ -97,8 +97,8 @@ class _SubordinateLeaveState extends State { separatorBuilder: (BuildContext cxt, int index) => 12.height, itemCount: getSubordinatesLeavesTotalList.length, itemBuilder: (BuildContext context, int index) { - var diffDays = DateUtil.convertStringToDate(getSubordinatesLeavesTotalList[index].dATEEND!) - .difference(DateUtil.convertStringToDate(getSubordinatesLeavesTotalList[index].dATESTART!)) + var diffDays = DateUtil.convertStringToDate(getSubordinatesLeavesTotalList[index].dATEEND) + .difference(DateUtil.convertStringToDate(getSubordinatesLeavesTotalList[index].dATESTART)) .inDays + 1; return Row( crossAxisAlignment: CrossAxisAlignment.start, @@ -109,8 +109,8 @@ class _SubordinateLeaveState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ getSubordinatesLeavesTotalList[index].eMPLOYEENAME!.toText16(), - ItemDetailView(LocaleKeys.from.tr(), DateUtil.getFormattedDate(DateUtil.convertStringToDate(getSubordinatesLeavesTotalList[index].dATESTART!), "MMM dd yyyy")), - ItemDetailView(LocaleKeys.to.tr(), DateUtil.getFormattedDate(DateUtil.convertStringToDate(getSubordinatesLeavesTotalList[index].dATEEND!), "MMM dd yyyy")), + ItemDetailView(LocaleKeys.from.tr(), DateUtil.getFormattedDate(DateUtil.convertStringToDate(getSubordinatesLeavesTotalList[index].dATESTART), "MMM dd yyyy")), + ItemDetailView(LocaleKeys.to.tr(), DateUtil.getFormattedDate(DateUtil.convertStringToDate(getSubordinatesLeavesTotalList[index].dATEEND), "MMM dd yyyy")), ItemDetailView(LocaleKeys.numberDays.tr(), diffDays.toString()), ], ).expanded diff --git a/lib/ui/screens/announcements/announcement_details.dart b/lib/ui/screens/announcements/announcement_details.dart index 210a8f7c..a8cbcac6 100644 --- a/lib/ui/screens/announcements/announcement_details.dart +++ b/lib/ui/screens/announcements/announcement_details.dart @@ -2,7 +2,8 @@ import 'dart:convert'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_html/flutter_html.dart'; +import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; +// import 'package:flutter_html/flutter_html.dart'; import 'package:mohem_flutter_app/api/pending_transactions_api_client.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; @@ -59,8 +60,8 @@ class _AnnouncementDetailsState extends State { ), ), ), - Html( - data: AppState().isArabic(context) ? getAnnouncementDetailsObj?.bodyAR : getAnnouncementDetailsObj?.bodyEN, + HtmlWidget( + AppState().isArabic(context) ? getAnnouncementDetailsObj?.bodyAR??"" : getAnnouncementDetailsObj?.bodyEN ??"", ).paddingOnly(top: 12), ], ).objectContainerView().paddingAll(21), diff --git a/lib/ui/screens/mowadhafhi/mowadhafhi_hr_request.dart b/lib/ui/screens/mowadhafhi/mowadhafhi_hr_request.dart index b3588431..498581ad 100644 --- a/lib/ui/screens/mowadhafhi/mowadhafhi_hr_request.dart +++ b/lib/ui/screens/mowadhafhi/mowadhafhi_hr_request.dart @@ -173,7 +173,7 @@ class _MowadhafhiHRRequestState extends State { } bool checkValidation() { - if (selectedServiceType == null || selectedDepartment == null || selectedSection == null || selectedTopic == null || description == "") { + if (selectedServiceType == null || selectedDepartment == null || selectedSection == null || selectedTopic == null) { return false; } else { return true; @@ -192,78 +192,10 @@ class _MowadhafhiHRRequestState extends State { title.toText16().expanded, 6.width, SimpleButton(LocaleKeys.add.tr(), () async { - FilePickerResult? result = await FilePicker.platform.pickFiles( - allowMultiple: true, - ); + List? result = await FilePicker.pickFiles(); if (result != null) { - // Maximum file size: 2 MB (in bytes) - const int maxFileSizeInBytes = 2 * 1024 * 1024; // 2 MB = 2097152 bytes - List newFiles = []; - List oversizedFiles = []; - - for (PlatformFile platformFile in result.files) { - if (platformFile.path != null) { - File file = File(platformFile.path!); - - // Get file size - check multiple sources for iOS compatibility - int fileSize = 0; - - // Method 1: Check platformFile.size first - if (platformFile.size > 0) { - fileSize = platformFile.size; - debugPrint('Using platformFile.size: $fileSize bytes'); - } - - // Method 2: Always verify with file.length() as well - try { - int fileLengthSize = await file.length(); - debugPrint('Using file.length(): $fileLengthSize bytes'); - // Use the maximum of both to ensure we catch the real size - if (fileLengthSize > fileSize) { - fileSize = fileLengthSize; - debugPrint('Using larger value from file.length()'); - } - } catch (e) { - debugPrint('Error getting file.length(): $e'); - // If file.length() fails, rely on platformFile.size - if (fileSize == 0) { - debugPrint('Cannot determine file size, rejecting file: ${platformFile.name}'); - oversizedFiles.add('${platformFile.name} (unknown size)'); - continue; - } - } - - double fileSizeMB = fileSize / (1024 * 1024); - debugPrint('Final file size - ${platformFile.name}: $fileSize bytes (${fileSizeMB.toStringAsFixed(2)} MB)'); - - // STRICT validation: Only accept files that are 2 MB or less - if (fileSize > 0 && fileSize <= maxFileSizeInBytes) { - debugPrint('✓ File accepted: ${platformFile.name}'); - newFiles.add(file); - } else if (fileSize > maxFileSizeInBytes) { - debugPrint('✗ File REJECTED (too large): ${platformFile.name} - ${fileSizeMB.toStringAsFixed(2)} MB'); - oversizedFiles.add('${platformFile.name} (${fileSizeMB.toStringAsFixed(2)} MB)'); - } else { - debugPrint('✗ File REJECTED (invalid size): ${platformFile.name}'); - oversizedFiles.add('${platformFile.name} (invalid)'); - } - } - } - - // Only add valid files - if (newFiles.isNotEmpty) { - attachmentFiles = attachmentFiles + newFiles; - attachmentFiles = attachmentFiles.toSet().toList(); - debugPrint('Total files attached: ${attachmentFiles.length}'); - } - - // Show error message for rejected files - if (oversizedFiles.isNotEmpty) { - String fileList = oversizedFiles.join('\n'); - Utils.showToast('Max 2MB File allowed.'); - debugPrint('Total rejected files: ${oversizedFiles.length}'); - } - + attachmentFiles = attachmentFiles + result.map((path) => File(path.uri.toFilePath())).toList(); + attachmentFiles = attachmentFiles.toSet().toList(); setState(() {}); } }, fontSize: 14), diff --git a/lib/ui/screens/my_documents/my_document_detail_screen.dart b/lib/ui/screens/my_documents/my_document_detail_screen.dart index ec975862..86ebd1ca 100644 --- a/lib/ui/screens/my_documents/my_document_detail_screen.dart +++ b/lib/ui/screens/my_documents/my_document_detail_screen.dart @@ -92,17 +92,22 @@ class _MyDocumentDetailScreenState extends State { ), 12.height, DottedBorder( - borderType: BorderType.RRect, - radius: const Radius.circular(10), - padding: const EdgeInsets.all(12), - dashPattern: const [2, 1], - color: MyColors.selectedBorderColor, + options: RoundedRectDottedBorderOptions( + radius: const Radius.circular(10), + padding: const EdgeInsets.all(12), + dashPattern: const [2, 1], + color: MyColors.selectedBorderColor, + ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Transform.rotate( angle: 45, - child: const Icon(Icons.attach_file_rounded, size: 16, color: MyColors.selectedBorderColor), + child: const Icon( + Icons.attach_file_rounded, + size: 16, + color: MyColors.selectedBorderColor, + ), ), 4.width, "Attach Image".toText14(color: MyColors.selectedBorderColor), diff --git a/lib/ui/screens/my_requests/my_requests.dart b/lib/ui/screens/my_requests/my_requests.dart index 4d16219a..12410ae7 100644 --- a/lib/ui/screens/my_requests/my_requests.dart +++ b/lib/ui/screens/my_requests/my_requests.dart @@ -67,10 +67,10 @@ class _MyRequestsState extends State { isReadOnly: false, ).paddingOnly(bottom: 12), itemBuilder: (_) => >[ - for (int i = 0; i < getConcurrentProgramsList!.length; i++) PopupMenuItem(child: Text(getConcurrentProgramsList![i].uSERCONCURRENTPROGRAMNAME!), value: i), + for (int i = 0; i < getConcurrentProgramsList.length; i++) PopupMenuItem(child: Text(getConcurrentProgramsList[i].uSERCONCURRENTPROGRAMNAME!), value: i), ], onSelected: (int popupIndex) { - selectedConcurrentProgramList = getConcurrentProgramsList![popupIndex]; + selectedConcurrentProgramList = getConcurrentProgramsList[popupIndex]; getCCPTransactions(selectedConcurrentProgramList?.cONCURRENTPROGRAMNAME); setState(() {}); }), @@ -105,7 +105,7 @@ class _MyRequestsState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ ("Request ID: " + getCCPTransactionsList[index].rEQUESTID.toString()).toText12(color: MyColors.grey57Color), - DateUtil.formatDateToDate(DateUtil.convertStringToDate(getCCPTransactionsList[index].rEQUESTDATE!), false).toText12(color: MyColors.grey70Color), + DateUtil.formatDateToDate(DateUtil.convertStringToDate(getCCPTransactionsList[index].rEQUESTDATE), false).toText12(color: MyColors.grey70Color), ], ), Container( diff --git a/lib/ui/screens/offers_and_discounts/offers_and_discounts_details.dart b/lib/ui/screens/offers_and_discounts/offers_and_discounts_details.dart index 846c52fb..890d1a46 100644 --- a/lib/ui/screens/offers_and_discounts/offers_and_discounts_details.dart +++ b/lib/ui/screens/offers_and_discounts/offers_and_discounts_details.dart @@ -6,8 +6,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; import 'package:mohem_flutter_app/extensions/int_extensions.dart'; @@ -16,7 +16,7 @@ import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; import 'package:mohem_flutter_app/models/offers_and_discounts/get_offers_list.dart'; import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:share_plus/share_plus.dart'; +// import 'package:share_plus/share_plus.dart'; import 'package:url_launcher/url_launcher.dart'; class OffersAndDiscountsDetails extends StatefulWidget { @@ -68,10 +68,12 @@ class _OffersAndDiscountsDetailsState extends State { AppState().isArabic(context) ? getOffersList[0].titleAr!.toText22(isBold: true, color: const Color(0xff2B353E)).center : getOffersList[0].titleEn!.toText22(isBold: true, color: const Color(0xff2B353E)).center, - Html( - data: AppState().isArabic(context) ? getOffersList[0].descriptionAr! : getOffersList[0].descriptionEn ?? "", - onLinkTap: (String? url, context, Map attributes, _) { + HtmlWidget( + AppState().isArabic(context) ? getOffersList[0].descriptionAr! : getOffersList[0].descriptionEn ?? "", + onTapUrl: (String? url) { launchUrl(Uri.parse(url!)); + + return true; }, ), checkDate(getOffersList[0].endDate!).paddingOnly(left: 8), @@ -122,7 +124,7 @@ class _OffersAndDiscountsDetailsState extends State { Directory tempDir = await getTemporaryDirectory(); File file = await File('${tempDir.path}/${DateTime.now().toString()}.png').create(); await file.writeAsBytes(pngBytes); - await SharePlus.instance.share(ShareParams(files: [(XFile(file.path))], text: AppState().isArabic(context) ? getOffersList[0].titleAr : getOffersList[0].titleEn)); + // await SharePlus.instance.share(ShareParams(files: [(XFile(file.path))], text: AppState().isArabic(context) ? getOffersList[0].titleAr : getOffersList[0].titleEn)); } catch (ex) { debugPrint(ex.toString()); } diff --git a/lib/ui/work_list/item_history_screen.dart b/lib/ui/work_list/item_history_screen.dart index 4bc65798..a8c3a756 100644 --- a/lib/ui/work_list/item_history_screen.dart +++ b/lib/ui/work_list/item_history_screen.dart @@ -211,7 +211,7 @@ class _ItemHistoryScreenState extends State { interval: 1, getTitlesWidget: (double value, TitleMeta meta) { return SideTitleWidget( - axisSide: meta.axisSide, + meta: meta, child: Text(reversedList[int.parse(meta.formattedValue)].cREATIONDATE!, style: TextStyle(fontSize: 10)), ); })), @@ -222,7 +222,7 @@ class _ItemHistoryScreenState extends State { interval: 1, getTitlesWidget: (double value, TitleMeta meta) { return SideTitleWidget( - axisSide: meta.axisSide, + meta: meta, child: Text(meta.formattedValue, style: TextStyle(fontSize: 10)), ); })), @@ -233,7 +233,7 @@ class _ItemHistoryScreenState extends State { reservedSize: 15, getTitlesWidget: (double value, TitleMeta meta) { return SideTitleWidget( - axisSide: meta.axisSide, + meta: meta, child: Text("", style: TextStyle(fontSize: 10)), ); })), diff --git a/lib/ui/work_list/work_list_screen.dart b/lib/ui/work_list/work_list_screen.dart index 7e455038..cb666a42 100644 --- a/lib/ui/work_list/work_list_screen.dart +++ b/lib/ui/work_list/work_list_screen.dart @@ -496,7 +496,7 @@ class _WorkListScreenState extends State { 10.height, Row( children: [ - DateUtil.formatDateToDate(DateUtil.convertStringToDate(requestDetails.modifiedDate!), false).toText10(color: MyColors.lightTextColor).expanded, + DateUtil.formatDateToDate(DateUtil.convertStringToDate(requestDetails.modifiedDate), false).toText10(color: MyColors.lightTextColor).expanded, RotatedBox(quarterTurns: AppState().isArabic(context) ? 2 : 4, child: SvgPicture.asset("assets/images/arrow_next.svg", color: MyColors.darkIconColor)), ], ), diff --git a/lib/ui/work_list/worklist_fragments/info_fragments.dart b/lib/ui/work_list/worklist_fragments/info_fragments.dart index 31850aee..65ae41db 100644 --- a/lib/ui/work_list/worklist_fragments/info_fragments.dart +++ b/lib/ui/work_list/worklist_fragments/info_fragments.dart @@ -751,7 +751,7 @@ class InfoFragment extends StatelessWidget { ItemDetailGrid( ItemDetailViewCol( LocaleKeys.requestDate.tr(), DateUtil.formatDateToDate( - DateUtil.convertStringToDate(data.paymentDetailsList![0].requestDate!), false) ?? ""), + DateUtil.convertStringToDate(data.paymentDetailsList![0].requestDate), false) ?? ""), ItemDetailViewCol( LocaleKeys.requesterEmpName.tr(), data.paymentDetailsList![0].requesterEmployeeName ?? ""), ), diff --git a/lib/widgets/image_picker.dart b/lib/widgets/image_picker.dart index 984d6ad0..165eb012 100644 --- a/lib/widgets/image_picker.dart +++ b/lib/widgets/image_picker.dart @@ -47,7 +47,7 @@ class ImageOptions { } }, onFilesTap: () async { - FilePickerResult? result = await FilePicker.platform.pickFiles( + List? result = await FilePicker.pickFiles( type: FileType.custom, allowedExtensions: [ 'jpg', @@ -63,8 +63,8 @@ class ImageOptions { 'zip', ], ); - List files = result!.paths.map((path) => File(path!)).toList(); - image(result.files.first.path.toString(), files.first); + List files = result!.map((path) => File(path.uri.toFilePath())).toList(); + image(result.first.path.toString(), files.first); }, ), ); diff --git a/lib/widgets/nfc/nfc_reader_sheet.dart b/lib/widgets/nfc/nfc_reader_sheet.dart index 5e0b62d3..136c504b 100644 --- a/lib/widgets/nfc/nfc_reader_sheet.dart +++ b/lib/widgets/nfc/nfc_reader_sheet.dart @@ -3,7 +3,9 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:nfc_manager/nfc_manager.dart'; -import 'package:nfc_manager/platform_tags.dart'; +import 'package:nfc_manager/nfc_manager_android.dart'; +import 'package:nfc_manager/nfc_manager_ios.dart'; +import 'dart:typed_data'; void showNfcReader(BuildContext context, {required Function(String? nfcId) onNcfScan}) { showModalBottomSheet( @@ -40,14 +42,23 @@ class _NfcLayoutState extends State { void initState() { super.initState(); - NfcManager.instance.startSession(onDiscovered: (NfcTag tag) async { - var f; + NfcManager.instance.startSession( pollingOptions: {NfcPollingOption.iso14443},onDiscovered: (NfcTag tag) async { + Uint8List? id; + if (Platform.isAndroid) { - f = MifareUltralight(tag: tag, identifier: tag.data["nfca"]["identifier"], type: 2, maxTransceiveLength: 252, timeout: 22); - } else { - f = MifareUltralight(tag: tag, identifier: tag.data["mifare"]["identifier"], type: 2, maxTransceiveLength: 252, timeout: 22); + id = NfcTagAndroid.from(tag)?.id; + } else if (Platform.isIOS) { + id = MiFareIos.from(tag)?.identifier; + } + + if (id == null) { + await NfcManager.instance.stopSession(); + return; } - String identifier = f.identifier.map((e) => e.toRadixString(16).padLeft(2, '0')).join(''); + + String identifier = + id.map((e) => e.toRadixString(16).padLeft(2, '0')).join(''); + print(identifier); nfcId = identifier; setState(() { diff --git a/pubspec.yaml b/pubspec.yaml index 3ebbf7b9..1d79577a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,82 +37,81 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.4 -# path_provider: ^2.0.8 - path_provider: ^2.1.5 - injector: ^2.0.0 + path_provider: ^2.0.8 + injector: ^4.0.0 provider: ^6.0.1 easy_localization: ^3.0.0 http: ^1.4.0 - permission_handler: ^12.0.1 + permission_handler: ^13.0.2 flutter_svg: any - sizer: ^2.0.15 - local_auth: ^2.3.0 - fluttertoast: ^8.2.12 - syncfusion_flutter_calendar: ^29.1.38 + sizer: ^3.1.3 + local_auth: ^3.0.2 + fluttertoast: ^10.0.0 + syncfusion_flutter_calendar: ^35.1.37 # flutter_calendar_carousel: ^2.1.0 pie_chart: ^5.1.0 -# shared_preferences: ^2.0.12 - shared_preferences: ^2.5.3 - firebase_messaging: ^15.2.9 - shimmer: ^2.0.0 - logger: ^1.1.0 + shared_preferences: ^2.5.5 + firebase_messaging: ^16.7.0 + shimmer: ^4.0.0 + logger: ^2.8.0 flutter_countdown_timer: ^4.1.0 # platform_device_id_plus: ^1.0.7 - mobile_device_identifier: ^0.0.3 - device_info_plus: ^11.5.0 +# mobile_device_identifier: ^0.0.3 + device_info_plus: ^13.2.0 image_picker: ^1.1.2 - file_picker: ^8.3.1 - geolocator: ^9.0.2 + file_picker: ^13.1.0 + geolocator: ^14.1.1 month_year_picker: ^0.5.0+1 month_picker_dialog: ^6.0.3 # open_file: ^3.2.1 open_filex: ^4.6.0 - wifi_iot: ^0.3.19+2 - flutter_html: ^3.0.0-alpha.6 - mobile_scanner: ^6.0.10 + wifi_iot: ^0.4.0 +# flutter_html: ^3.0.0-alpha.6 + flutter_widget_from_html: ^0.17.4 + just_audio: ^0.10.6 + + +# mobile@1234 + mobile_scanner: ^7.4.2 # flutter_barcode_scanner: ^2.0.0 # qr_code_scanner: ^1.0.1 # qr_flutter: ^4.0.0 - url_launcher: ^6.3.2 - share_plus: ^11.0.0 + url_launcher: ^6.3.3 +# share_plus: ^13.3.0 flutter_rating_bar: ^4.0.1 auto_size_text: ^3.0.0 pull_to_refresh: ^2.0.0 - fl_chart: ^0.66.0 + fl_chart: ^1.2.0 # lottie json animations - lottie: ^3.3.1 + lottie: any # Marathon Card Swipe - appinio_swiper: ^1.1.1 + appinio_swiper: ^2.1.1 expandable: ^5.0.1 - safe_device: ^1.1.9 + safe_device: ^1.4.1 # networkImage -# cached_network_image: ^3.2.2 - cached_network_image: ^3.4.1 + cached_network_image: ^4.0.3 #Chat signalr_netcore: ^1.3.3 logging: ^1.0.1 swipe_to: ^1.0.2 - flutter_webrtc: ^0.12.11 + flutter_webrtc: ^1.6.2+hotfix.3 # camera: ^0.10.3 - camera: ^0.11.2 - flutter_local_notifications: ^18.0.1 + camera: ^0.12.1 + flutter_local_notifications: ^22.3.1 #firebase_analytics: any #Chat Voice Message Recoding & Play - audio_waveforms: ^1.3.0 - rxdart: ^0.27.7 - - #Speech to Text - speech_to_text: ^7.0.0 + audio_waveforms: ^2.0.2 + rxdart: ^0.28.0 #Encryption - flutter_des: ^2.1.0 + flutter_des: ^3.0.0 - video_player: ^2.10.0 - just_audio: ^0.9.30 + video_player: ^2.14.0 +# just_audio: ^0.9.30 # safe_device: ^1.1.2 flutter_layout_grid: ^2.0.1 jiffy: ^6.4.4 @@ -120,18 +119,18 @@ dependencies: #Huawei Dependencies # huawei_hmsavailability: ^6.6.0+300 # huawei_location: 6.0.0+302 - huawei_location: ^6.14.2+301 + huawei_location: ^6.17.0+300 # huawei_push: ^6.7.0+300 - huawei_map: - git: - url: https://github.com/fleoparra/hms-flutter-plugin.git - path: flutter-hms-map - huawei_push: - git: - url: https://github.com/crasowas/hms-flutter-plugin.git - path: flutter-hms-push - firebase_crashlytics: ^4.3.9 +# huawei_map: +# git: +# url: https://github.com/fleoparra/hms-flutter-plugin.git +# path: flutter-hms-map + huawei_push: ^6.15.0+300 + # git: +# url: https://github.com/crasowas/hms-flutter-plugin.git +# path: flutter-hms-push + firebase_crashlytics: ^5.4.0 #Items for sale Image Carousel Slider carousel_slider: ^5.1.1 @@ -139,15 +138,17 @@ dependencies: #Huawei Specified # store_checker: ^1.1.0 google_api_availability: ^5.0.1 - google_maps_flutter_web: ^0.5.4 - in_app_update: 4.1.0 + google_maps_flutter_web: ^0.6.4+1 + in_app_update: 5.0.0 flutter_inappwebview: ^6.1.5 - flutter_blurhash: ^0.8.0 + flutter_blurhash: ^0.9.1 #todo its for temporary purpose, later will remove this. - dotted_border: ^2.0.0+3 + dotted_border: ^3.1.0 webview_flutter: ^4.13.0 - nfc_manager: ^3.2.0 + nfc_manager: ^4.2.1 + + speech_to_text: ^7.3.0 # saf: ^1.0.3+4 @@ -166,7 +167,7 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^1.0.4 + flutter_lints: ^6.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec