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