Compare commits
No commits in common. "master" and "main" have entirely different histories.
98
.gitignore
vendored
Executable file → Normal file
@ -1,68 +1,50 @@
|
||||
# Miscellaneous
|
||||
# These are some examples of commonly ignored file patterns.
|
||||
# You should customize this list as applicable to your project.
|
||||
# Learn more about .gitignore:
|
||||
# https://www.atlassian.com/git/tutorials/saving-changes/gitignore
|
||||
|
||||
# Node artifact files
|
||||
node_modules/
|
||||
dist/
|
||||
|
||||
# Compiled Java class files
|
||||
*.class
|
||||
|
||||
# Compiled Python bytecode
|
||||
*.py[cod]
|
||||
|
||||
# Log files
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.env
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.lock
|
||||
*.packages
|
||||
*.ipr
|
||||
*.iws
|
||||
# Package files
|
||||
*.jar
|
||||
|
||||
# Maven
|
||||
target/
|
||||
dist/
|
||||
|
||||
# JetBrains IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
# Unit test reports
|
||||
TEST*.xml
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
# Generated by MacOS
|
||||
.DS_Store
|
||||
|
||||
# Android specific files
|
||||
*.android/
|
||||
*.android.iml
|
||||
*.apk
|
||||
*.ap_
|
||||
# Generated by Windows
|
||||
Thumbs.db
|
||||
|
||||
# iOS specific files
|
||||
*.ios/
|
||||
*.ios.iml
|
||||
*.ipa
|
||||
*.xcworkspace
|
||||
*.xcodeproj/
|
||||
*.xcuserdatad/
|
||||
# Applications
|
||||
*.app
|
||||
*.exe
|
||||
*.war
|
||||
|
||||
# macOS specific files
|
||||
.idea/workspace.xml
|
||||
# Large media files
|
||||
*.mp4
|
||||
*.tiff
|
||||
*.avi
|
||||
*.flv
|
||||
*.mov
|
||||
*.wmv
|
||||
|
||||
|
||||
# Dependency lock files
|
||||
.pubspec.lock
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
|
||||
45
.metadata
@ -1,45 +0,0 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "7482962148e8d758338d8a28f589f317e1e42ba4"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
|
||||
base_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
|
||||
- platform: android
|
||||
create_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
|
||||
base_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
|
||||
- platform: ios
|
||||
create_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
|
||||
base_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
|
||||
- platform: linux
|
||||
create_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
|
||||
base_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
|
||||
- platform: macos
|
||||
create_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
|
||||
base_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
|
||||
- platform: web
|
||||
create_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
|
||||
base_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
|
||||
- platform: windows
|
||||
create_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
|
||||
base_revision: 7482962148e8d758338d8a28f589f317e1e42ba4
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
33
README.md
Executable file → Normal file
@ -1,16 +1,29 @@
|
||||
# nhancepolicy
|
||||
# README #
|
||||
|
||||
A new Flutter project.
|
||||
This README would normally document whatever steps are necessary to get your application up and running.
|
||||
|
||||
## Getting Started
|
||||
### What is this repository for? ###
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
* Quick summary
|
||||
* Version
|
||||
* [Learn Markdown](https://bitbucket.org/tutorials/markdowndemo)
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
### How do I get set up? ###
|
||||
|
||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||
* Summary of set up
|
||||
* Configuration
|
||||
* Dependencies
|
||||
* Database configuration
|
||||
* How to run tests
|
||||
* Deployment instructions
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
### Contribution guidelines ###
|
||||
|
||||
* Writing tests
|
||||
* Code review
|
||||
* Other guidelines
|
||||
|
||||
### Who do I talk to? ###
|
||||
|
||||
* Repo owner or admin
|
||||
* Other community or team contact
|
||||
@ -1,28 +0,0 @@
|
||||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at https://dart.dev/lints.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
13
android/.gitignore
vendored
@ -1,13 +0,0 @@
|
||||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
@ -1,67 +0,0 @@
|
||||
plugins {
|
||||
id "com.android.application"
|
||||
id "kotlin-android"
|
||||
id "dev.flutter.flutter-gradle-plugin"
|
||||
}
|
||||
|
||||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file('local.properties')
|
||||
if (localPropertiesFile.exists()) {
|
||||
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||
localProperties.load(reader)
|
||||
}
|
||||
}
|
||||
|
||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = '1'
|
||||
}
|
||||
|
||||
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||
if (flutterVersionName == null) {
|
||||
flutterVersionName = '1.0'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace "com.example.nhance_app_pwa"
|
||||
compileSdk flutter.compileSdkVersion
|
||||
ndkVersion flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "com.example.nhance_app_pwa"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||
minSdkVersion flutter.minSdkVersion
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source '../..'
|
||||
}
|
||||
|
||||
dependencies {}
|
||||
@ -1,29 +0,0 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "508866094093",
|
||||
"project_id": "nhance-app-4fedb",
|
||||
"storage_bucket": "nhance-app-4fedb.appspot.com"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:508866094093:android:9969e766daa5b770366144",
|
||||
"android_client_info": {
|
||||
"package_name": "com.example.nhance_app_pwa"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyAZdKJco4fe0e1HP1A249BVZzA3WMYb_3g"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@ -1,49 +0,0 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.nhance_app_pwa">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<application
|
||||
android:label="nhancepolicy"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility?hl=en and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
@ -1,5 +0,0 @@
|
||||
package com.example.nhance_app_pwa
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity: FlutterActivity()
|
||||
@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 996 B |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@ -1,7 +0,0 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@ -1,18 +0,0 @@
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = '../build'
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
org.gradle.jvmargs=-Xmx4G
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
@ -1,5 +0,0 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
|
||||
@ -1,26 +0,0 @@
|
||||
pluginManagement {
|
||||
def flutterSdkPath = {
|
||||
def properties = new Properties()
|
||||
file("local.properties").withInputStream { properties.load(it) }
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
return flutterSdkPath
|
||||
}
|
||||
settings.ext.flutterSdkPath = flutterSdkPath()
|
||||
|
||||
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "7.3.0" apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 99 B |
|
Before Width: | Height: | Size: 840 B |
BIN
assets/claim.png
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 331 B |
|
Before Width: | Height: | Size: 283 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
BIN
assets/ecard.png
|
Before Width: | Height: | Size: 218 B |
|
Before Width: | Height: | Size: 476 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 833 KiB |
|
Before Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 493 KiB |
|
Before Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 285 KiB |
@ -1,4 +0,0 @@
|
||||
description: This file stores settings for Dart & Flutter DevTools.
|
||||
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||
extensions:
|
||||
- shared_preferences: true
|
||||
34
ios/.gitignore
vendored
@ -1,34 +0,0 @@
|
||||
**/dgph
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
*.perspectivev3
|
||||
**/*sync/
|
||||
.sconsign.dblite
|
||||
.tags*
|
||||
**/.vagrant/
|
||||
**/DerivedData/
|
||||
Icon?
|
||||
**/Pods/
|
||||
**/.symlinks/
|
||||
profile
|
||||
xcuserdata
|
||||
**/.generated/
|
||||
Flutter/App.framework
|
||||
Flutter/Flutter.framework
|
||||
Flutter/Flutter.podspec
|
||||
Flutter/Generated.xcconfig
|
||||
Flutter/ephemeral/
|
||||
Flutter/app.flx
|
||||
Flutter/app.zip
|
||||
Flutter/flutter_assets/
|
||||
Flutter/flutter_export_environment.sh
|
||||
ServiceDefinitions.json
|
||||
Runner/GeneratedPluginRegistrant.*
|
||||
|
||||
# Exceptions to above rules.
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.pbxuser
|
||||
!default.perspectivev3
|
||||
@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>App</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>io.flutter.flutter.app</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>App</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>12.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -1,2 +0,0 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
@ -1,2 +0,0 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
43
ios/Podfile
@ -1,43 +0,0 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
# platform :ios, '12.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
project 'Runner', {
|
||||
'Debug' => :debug,
|
||||
'Profile' => :release,
|
||||
'Release' => :release,
|
||||
}
|
||||
|
||||
def flutter_root
|
||||
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
||||
unless File.exist?(generated_xcode_build_settings_path)
|
||||
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
||||
end
|
||||
|
||||
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||
return matches[1].strip if matches
|
||||
end
|
||||
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
|
||||
end
|
||||
|
||||
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||
|
||||
flutter_ios_podfile_setup
|
||||
|
||||
target 'Runner' do
|
||||
use_frameworks!
|
||||
|
||||
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||
target 'RunnerTests' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_ios_build_settings(target)
|
||||
end
|
||||
end
|
||||
@ -1,13 +0,0 @@
|
||||
import UIKit
|
||||
import Flutter
|
||||
|
||||
@UIApplicationMain
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
}
|
||||
@ -1,122 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "Icon-App-1024x1024@1x.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 494 B |
|
Before Width: | Height: | Size: 772 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 663 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 772 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
@ -1,23 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 68 B |
@ -1,5 +0,0 @@
|
||||
# Launch Screen Assets
|
||||
|
||||
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||
|
||||
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
||||
@ -1,37 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchImage" width="168" height="185"/>
|
||||
</resources>
|
||||
</document>
|
||||
@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Flutter View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@ -1,49 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Nhancepolicy</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>nhancepolicy</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -1 +0,0 @@
|
||||
#import "GeneratedPluginRegistrant.h"
|
||||
@ -1,12 +0,0 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
import XCTest
|
||||
|
||||
class RunnerTests: XCTestCase {
|
||||
|
||||
func testExample() {
|
||||
// If you add code to the Runner application, consider adding tests here.
|
||||
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
|
||||
}
|
||||
|
||||
}
|
||||
4746
lib/addons.dart
@ -1,67 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
class BranchCard extends StatelessWidget {
|
||||
final String clientName;
|
||||
final String branchName;
|
||||
final bool isSelected;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const BranchCard({
|
||||
Key? key,
|
||||
required this.clientName,
|
||||
required this.branchName,
|
||||
required this.isSelected,
|
||||
required this.onTap,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Tooltip(
|
||||
message: clientName, // 🖱 hover shows full name
|
||||
waitDuration: const Duration(milliseconds: 400),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
isSelected ? const Color(0xFF00999E) : const Color(0xFFF0F9F9),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: const Color(0xFF00999E),
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
clientName,
|
||||
maxLines: 1, // ✅ SINGLE LINE
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: isSelected ? Colors.white : const Color(0xFF00999E),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
branchName,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 11,
|
||||
color: isSelected ? Colors.white70 : Colors.black87,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,220 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../customAppBar/base_layout.dart';
|
||||
import '../customAppBar/toastHelper.dart';
|
||||
import '../service/api_service.dart';
|
||||
import '../service/secure_pop_scope.dart';
|
||||
import '../service/token_storage_service.dart';
|
||||
import 'branch_card_widget.dart';
|
||||
import 'dart:html' as html;
|
||||
|
||||
class BranchSelectionPage extends StatefulWidget {
|
||||
const BranchSelectionPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<BranchSelectionPage> createState() => _BranchSelectionPageState();
|
||||
}
|
||||
|
||||
class _BranchSelectionPageState extends State<BranchSelectionPage> {
|
||||
List<Map<String, dynamic>> branches = [];
|
||||
int? selectedIndex;
|
||||
|
||||
final tokenStorage = TokenStorageService();
|
||||
late ApiService apiService;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
apiService = ApiService(context);
|
||||
html.window.onPopState.listen((event) async {
|
||||
final shouldLogout = await _showLogoutDialog();
|
||||
if (shouldLogout) {
|
||||
await apiService.logout();
|
||||
if (!mounted) return;
|
||||
Navigator.pushNamedAndRemoveUntil(
|
||||
context,
|
||||
'hrLogin',
|
||||
(route) => false,
|
||||
);
|
||||
} else {
|
||||
// Push state back to prevent browser navigation
|
||||
html.window.history.pushState(null, '', html.window.location.href);
|
||||
}
|
||||
});
|
||||
_loadBranches();
|
||||
}
|
||||
|
||||
void _loadBranches() {
|
||||
setState(() {
|
||||
branches = tokenStorage.getCombinedBranches();
|
||||
});
|
||||
}
|
||||
|
||||
void _selectBranch(int index) {
|
||||
setState(() => selectedIndex = index);
|
||||
}
|
||||
|
||||
Future<void> _handleNext() async {
|
||||
if (selectedIndex == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Please select a branch')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final selectedBranch = branches[selectedIndex!];
|
||||
await tokenStorage.saveSelectedBranch(selectedBranch);
|
||||
|
||||
final decodedToken = tokenStorage.getDecodedToken();
|
||||
final token = tokenStorage.getCurrentToken();
|
||||
|
||||
if (decodedToken == null || token == null) {
|
||||
ToastHelper.showErrorToast(context, 'Invalid token data');
|
||||
return;
|
||||
}
|
||||
|
||||
// 🔐 Save decoded values securely
|
||||
await tokenStorage.saveDecodedSessionData(decodedToken, token);
|
||||
|
||||
// ToastHelper.showSuccessToast(context, 'Successfully Login');
|
||||
|
||||
if (!mounted) return;
|
||||
Navigator.pushReplacementNamed(context, 'policies');
|
||||
}
|
||||
|
||||
Future<bool> _showLogoutDialog() async {
|
||||
return await showDialog<bool>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text("Confirm Logout"),
|
||||
content: Text("Do you want to logout?"),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: Text("Cancel"),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: Text("Logout"),
|
||||
),
|
||||
],
|
||||
),
|
||||
) ??
|
||||
false;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BaseLayout(
|
||||
child: SecurePopScope(
|
||||
child: _buildContent(context),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF5F7F7),
|
||||
body: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final width = constraints.maxWidth;
|
||||
|
||||
/// 🔹 RESPONSIVE BREAKPOINTS
|
||||
int crossAxisCount;
|
||||
if (width < 600) {
|
||||
crossAxisCount = 1;
|
||||
} else if (width < 900) {
|
||||
crossAxisCount = 2;
|
||||
} else {
|
||||
crossAxisCount = 3;
|
||||
}
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: width * 0.08,
|
||||
vertical: 30,
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Select Client',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
/// 🔹 GRID
|
||||
GridView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: branches.length,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: crossAxisCount,
|
||||
mainAxisExtent: 90, // 🔥 FIXED HEIGHT
|
||||
mainAxisSpacing: 15,
|
||||
crossAxisSpacing: 15,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final branch = branches[index];
|
||||
return BranchCard(
|
||||
clientName: branch['client_name'] ??
|
||||
'Unknown Client kjbgjsk jsdhbjbf sdjfjbds fdjsgjdbs gdsjbgjds',
|
||||
branchName: branch['branch_name'] ?? 'Unknown Branch',
|
||||
isSelected: selectedIndex == index,
|
||||
onTap: () => _selectBranch(index),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 30),
|
||||
|
||||
/// 🔹 NEXT BUTTON
|
||||
Center(
|
||||
child: SizedBox(
|
||||
width: 140,
|
||||
height: 46,
|
||||
child: ElevatedButton(
|
||||
onPressed: _handleNext,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFFFF6B35),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
elevation: 0,
|
||||
),
|
||||
child: const Text(
|
||||
'Next',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,77 +0,0 @@
|
||||
// lib/environment.dart
|
||||
enum Flavor { dev, uat, prod, prod1 }
|
||||
|
||||
class Environment {
|
||||
static Flavor flavor = Flavor.dev; // overwritten by each main_*.dart
|
||||
|
||||
static bool get isProd => flavor == Flavor.prod || flavor == Flavor.prod1;
|
||||
|
||||
static String get apiUrl {
|
||||
switch (flavor) {
|
||||
case Flavor.dev:
|
||||
return "https://venbait.in/enrollment/employeeRest/";
|
||||
case Flavor.uat:
|
||||
return "https://appstage.nhanceindia.in/apex/employeeRest/";
|
||||
case Flavor.prod:
|
||||
return "https://app.nhanceindia.in/enrolment/employeeRest/";
|
||||
case Flavor.prod1:
|
||||
return "https://app.nhanceindia.in/enrolment/employeeRest/";
|
||||
}
|
||||
}
|
||||
|
||||
static String get apiUrlPost {
|
||||
switch (flavor) {
|
||||
case Flavor.dev:
|
||||
return "https://venbait.in/nhance/dev/employeeRest/";
|
||||
case Flavor.uat:
|
||||
return "https://appstage.nhanceindia.in/nexus/employeeRest/";
|
||||
case Flavor.prod:
|
||||
return "https://app.nhanceindia.in/zenith/employeeRest/";
|
||||
case Flavor.prod1:
|
||||
return "https://app.nhanceindia.in/zenith/employeeRest/";
|
||||
}
|
||||
}
|
||||
|
||||
static String get baseHref {
|
||||
switch (flavor) {
|
||||
case Flavor.dev:
|
||||
return "/nhance/hr/dev/";
|
||||
case Flavor.uat:
|
||||
return "/";
|
||||
case Flavor.prod:
|
||||
return "/hr/";
|
||||
case Flavor.prod1:
|
||||
return "/";
|
||||
}
|
||||
}
|
||||
|
||||
static String getPageTitle(String? routeName) {
|
||||
String base = "Nhance HR";
|
||||
if (isProd) base = "Nhance HR"; // You can differentiate names if needed
|
||||
|
||||
switch (routeName) {
|
||||
case 'claimsOverviewDashboard':
|
||||
return "$base - Dashboard";
|
||||
case 'policies':
|
||||
return "$base - Policies";
|
||||
case 'pendingDependentApproval':
|
||||
return "$base - Approvals";
|
||||
case 'CdPoliciesList':
|
||||
return "$base - CD Details";
|
||||
case 'ClaimsPolicies':
|
||||
return "$base - Claims";
|
||||
case 'hrLogin':
|
||||
return "$base - Login";
|
||||
case 'cdTransactionDetails':
|
||||
return "$base - Transaction Details";
|
||||
case 'hrPolicyDetails':
|
||||
return "$base - Member Details";
|
||||
case 'preFileUpload':
|
||||
return "$base - Member Upload";
|
||||
case 'postFileUpload':
|
||||
return "$base - Member Upload";
|
||||
default:
|
||||
return base;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
import '../main.dart';
|
||||
import 'environment.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
Environment.flavor = Flavor.dev;
|
||||
await startApp();
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
import '../main.dart';
|
||||
import 'environment.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
Environment.flavor = Flavor.prod;
|
||||
await startApp();
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
import '../main.dart';
|
||||
import 'environment.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
Environment.flavor = Flavor.prod1;
|
||||
await startApp();
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
import '../main.dart';
|
||||
import 'environment.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
Environment.flavor = Flavor.uat;
|
||||
await startApp();
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'top_app_bar.dart';
|
||||
import 'side_bar.dart';
|
||||
import '../config/environment.dart';
|
||||
|
||||
class BaseLayout extends StatelessWidget {
|
||||
final Widget child;
|
||||
|
||||
const BaseLayout({super.key, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 1. Capture the current route name from the navigation context
|
||||
final String? routeName = ModalRoute.of(context)?.settings.name;
|
||||
|
||||
// 2. Use the Title widget to communicate with the browser's tab
|
||||
return Title(
|
||||
title: Environment.getPageTitle(routeName),
|
||||
color: const Color(0xFF00999E), // Required, usually matches your theme
|
||||
child: Scaffold(
|
||||
appBar: const NhanceTopBar(),
|
||||
body: Row(
|
||||
children: [
|
||||
const NhanceSideBar(),
|
||||
Expanded(
|
||||
child: Container(
|
||||
color: const Color(0xFFF5F7F7),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -1,204 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:adaptive_navbar/adaptive_navbar.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:nhancepolicy/customAppBar/toastHelper.dart';
|
||||
import 'package:nhancepolicy/responsive.dart';
|
||||
import 'package:nhancepolicy/responsive.dart';
|
||||
|
||||
import '../service/api_service.dart';
|
||||
import '../service/token_storage_service.dart';
|
||||
import 'package:nhancepolicy/logger.dart';
|
||||
|
||||
class CustomAppBar extends StatefulWidget implements PreferredSizeWidget {
|
||||
@override
|
||||
_CustomAppBarState createState() => _CustomAppBarState();
|
||||
|
||||
@override
|
||||
Size get preferredSize => Size.fromHeight(kToolbarHeight);
|
||||
}
|
||||
|
||||
class _CustomAppBarState extends State<CustomAppBar> {
|
||||
bool showBackToHR = false;
|
||||
late ApiService apiService;
|
||||
final tokenService = TokenStorageService();
|
||||
// 🔐 Secure storage instance
|
||||
static const FlutterSecureStorage _secureStorage = FlutterSecureStorage();
|
||||
// bool isLoading = true; // Add a loading state
|
||||
// bool hideInactiveStatus = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
apiService = ApiService(context);
|
||||
// _checkTokens();
|
||||
}
|
||||
|
||||
Future<void> logout(BuildContext context) async {
|
||||
// final prefs = await SharedPreferences.getInstance();
|
||||
// final String? hrtoken = prefs.getString('_postToken');
|
||||
// final String? token = prefs.getString('enrollToken');
|
||||
// prefs.clear();
|
||||
logDebug('LocalStorage Cleared');
|
||||
apiService.logout();
|
||||
// Navigator.pushNamed(context, 'hrLogin');
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sw = MediaQuery.of(context).size.width;
|
||||
return Scaffold(
|
||||
backgroundColor: Color(0xFFFFFCE5), // Set background color for AppBar
|
||||
appBar: PreferredSize(
|
||||
preferredSize: widget.preferredSize,
|
||||
child: SafeArea(
|
||||
child: Container(
|
||||
// padding: Responsive.isDesktop(context)
|
||||
// ? EdgeInsets.symmetric(horizontal: 46.0)
|
||||
// : EdgeInsets.symmetric(horizontal: 0),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: MediaQuery.of(context).size.width *
|
||||
(Responsive.isDesktop(context) ? 0.03 : 0.02),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// Logo Column
|
||||
// Expanded(
|
||||
// // flex: Responsive.isDesktop(context) ? 1 : 9,
|
||||
// child:
|
||||
Row(
|
||||
mainAxisAlignment: Responsive.isDesktop(context)
|
||||
? MainAxisAlignment.spaceEvenly
|
||||
: MainAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 10, bottom: 10),
|
||||
width: 150,
|
||||
height: 150,
|
||||
child: Image.asset(
|
||||
'assets/nhance_client_logo.png',
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// ),
|
||||
// AdaptiveNavBar Column
|
||||
|
||||
// Expanded(
|
||||
// // flex: Responsive.isDesktop(context) ? 1 : 9,
|
||||
// child:
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
NavBarItem(
|
||||
text: "Change Branch",
|
||||
onTap: () async {
|
||||
await tokenService.clearBranchSession();
|
||||
|
||||
if (!mounted) return;
|
||||
Navigator.pushNamedAndRemoveUntil(
|
||||
context,
|
||||
'branchSelection',
|
||||
(route) => false,
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
NavBarItem(
|
||||
text: "Logout",
|
||||
onTap: () => logout(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
// )
|
||||
|
||||
// Expanded(
|
||||
// flex: Responsive.isDesktop(context) ? 9 : 3,
|
||||
// child: AdaptiveNavBar(
|
||||
// screenWidth: sw,
|
||||
// backgroundColor: Color(0xFFFFFCE5),
|
||||
// leading:
|
||||
// Container(), // Set an empty container as we have the logo separately
|
||||
// title: Text(''),
|
||||
// navBarItems: [
|
||||
// // if (Responsive.isDesktop(context))
|
||||
// // if (hideInactiveStatus)
|
||||
// // NavBarItem(
|
||||
// // text: "Inactive Policy",
|
||||
// // onTap: () {
|
||||
// // Navigator.pushNamed(context, 'oldPolicy');
|
||||
// // },
|
||||
// // ),
|
||||
// // if (showBackToHR)
|
||||
// NavBarItem(
|
||||
// text: "Change Branch",
|
||||
// onTap: () async {
|
||||
// final prefs = await SharedPreferences.getInstance();
|
||||
// await prefs.remove('selected_branch');
|
||||
// await prefs.remove('decoded_token');
|
||||
// await prefs.remove('clientLogo');
|
||||
// await prefs.remove('clientName');
|
||||
// await prefs.remove('empAllowed_modules');
|
||||
// await prefs.remove('empClientBranchId');
|
||||
// await prefs.remove('empClientId');
|
||||
// await prefs.remove('empEmail');
|
||||
// await prefs.remove('empHrId');
|
||||
// await prefs.remove('empPrimaryId');
|
||||
// await prefs.remove('enrollmentAllowed_modules');
|
||||
// await prefs.remove('enrollmentClient_id');
|
||||
// await prefs.remove('enrollmentEmpClientBranchId');
|
||||
// await prefs.remove('enrollmentEmpPrimaryId');
|
||||
// await prefs.remove('enrollmentHrId');
|
||||
// await prefs.remove('token');
|
||||
// Navigator.pushNamed(context, 'branchSelection');
|
||||
// },
|
||||
// ),
|
||||
// NavBarItem(
|
||||
// text: "Logout",
|
||||
// onTap: () async {
|
||||
// logout(context);
|
||||
// },
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NavBarItem extends StatelessWidget {
|
||||
final String text;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const NavBarItem({
|
||||
Key? key,
|
||||
required this.text,
|
||||
this.onTap,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
onTap: onTap,
|
||||
hoverColor: const Color(0xFFF4F3E7), // subtle hover background
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
child: Text(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,88 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:adaptive_navbar/adaptive_navbar.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../responsive.dart';
|
||||
|
||||
class CustomAppBarChatbot extends StatelessWidget
|
||||
implements PreferredSizeWidget {
|
||||
@override
|
||||
Size get preferredSize => Size.fromHeight(kToolbarHeight);
|
||||
|
||||
Future<void> logout(BuildContext context) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final String? token = prefs.getString('token');
|
||||
|
||||
if (token != null && token.isNotEmpty) {
|
||||
await prefs.clear();
|
||||
Navigator.pushNamed(context, 'phone');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sw = MediaQuery.of(context).size.width;
|
||||
final isPoliciesPage = Uri.base.fragment == 'policies';
|
||||
return ClipRRect(
|
||||
borderRadius: Responsive.isDesktop(context) || isPoliciesPage
|
||||
? BorderRadius.zero
|
||||
: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(32.0),
|
||||
bottomRight: Radius.circular(32.0),
|
||||
),
|
||||
child: AppBar(
|
||||
backgroundColor: const Color(0xFFFFFBDE),
|
||||
elevation: 0, // Set background color for AppBar
|
||||
toolbarHeight: kToolbarHeight,
|
||||
titleSpacing: 0.0,
|
||||
automaticallyImplyLeading: false,
|
||||
title: Padding(
|
||||
padding: Responsive.isDesktop(context)
|
||||
? EdgeInsets.symmetric(horizontal: 16.0)
|
||||
: EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
// Logo Column
|
||||
Expanded(
|
||||
flex: Responsive.isDesktop(context) ? 3 : 9,
|
||||
child: Row(
|
||||
mainAxisAlignment: Responsive.isDesktop(context)
|
||||
? MainAxisAlignment.spaceEvenly
|
||||
: MainAxisAlignment
|
||||
.start, // Adjust the alignment as needed
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
top: 10, bottom: 10, left: 10, right: 10),
|
||||
width: Responsive.isDesktop(context) ? 230 : 170,
|
||||
height: Responsive.isDesktop(context) ? 230 : 170,
|
||||
child: Image.asset(
|
||||
'assets/nhance_client_logo.png',
|
||||
fit: BoxFit.contain, // Adjust the fit as needed
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// AdaptiveNavBar Column
|
||||
|
||||
Expanded(
|
||||
flex: Responsive.isDesktop(context) ? 9 : 3,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Icon(
|
||||
Icons.close_outlined, // Replace with your desired icon
|
||||
size: 24.0, // Adjust the icon size
|
||||
color: Colors.black, // Adjust the icon color
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CustomFooter extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
color: Color(0xFFFFFBDE), // Background color of the footer
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'Copyright @ ${DateTime.now().year} Nhance',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey[600], // Text color
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,162 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:adaptive_navbar/adaptive_navbar.dart';
|
||||
import 'package:nhancepolicy/customAppBar/toastHelper.dart';
|
||||
import 'package:nhancepolicy/responsive.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:nhancepolicy/responsive.dart';
|
||||
import 'package:nhancepolicy/logger.dart';
|
||||
|
||||
class CustomAppBar extends StatefulWidget implements PreferredSizeWidget {
|
||||
@override
|
||||
_CustomAppBarState createState() => _CustomAppBarState();
|
||||
|
||||
@override
|
||||
Size get preferredSize => Size.fromHeight(kToolbarHeight);
|
||||
}
|
||||
|
||||
class _CustomAppBarState extends State<CustomAppBar> {
|
||||
bool showBackToHR = false;
|
||||
// bool isLoading = true; // Add a loading state
|
||||
// bool hideInactiveStatus = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_checkTokens();
|
||||
}
|
||||
|
||||
Future<void> _checkTokens() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
// final String? hrtoken = prefs.getString('hrtoken');
|
||||
final String? hrtoken = prefs.getString('enrollToken');
|
||||
final String? token = prefs.getString('token');
|
||||
// showBackToHR = prefs.getBool('showBackToHR')!;
|
||||
|
||||
setState(() {
|
||||
showBackToHR = prefs.getBool('showBackToHR')!;
|
||||
// showBackToHR = (hrtoken != null && hrtoken.isNotEmpty) &&
|
||||
// (token != null && token.isNotEmpty);
|
||||
// logDebug((hrtoken != null && hrtoken.isNotEmpty));
|
||||
// logDebug((token != null && token.isNotEmpty));
|
||||
// logDebug('showBackToHR $showBackToHR');
|
||||
|
||||
// hideInactiveStatus = token != null && token.isNotEmpty;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> hrLogout(BuildContext context) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final String? hrtoken = prefs.getString('hrtoken');
|
||||
|
||||
if (hrtoken != null && hrtoken.isNotEmpty) {
|
||||
prefs.setBool('showBackToHR', false);
|
||||
await prefs.remove('hrSelectArgumentsData');
|
||||
prefs.remove('token');
|
||||
Navigator.pushNamed(context, 'policies');
|
||||
} else {
|
||||
await prefs.clear();
|
||||
Navigator.pushNamed(context, 'phone');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> logout(BuildContext context) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final String? hrtoken = prefs.getString('hrtoken');
|
||||
final String? token = prefs.getString('token');
|
||||
|
||||
if (token != null && token.isNotEmpty) {
|
||||
if (hrtoken != null && hrtoken.isNotEmpty) {
|
||||
await prefs.clear();
|
||||
Navigator.pushNamed(context, 'hrLogin');
|
||||
} else {
|
||||
await prefs.clear();
|
||||
Navigator.pushNamed(context, 'phone');
|
||||
}
|
||||
} else if (hrtoken != null && hrtoken.isNotEmpty) {
|
||||
await prefs.clear();
|
||||
Navigator.pushNamed(context, 'hrLogin');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sw = MediaQuery.of(context).size.width;
|
||||
// if (isLoading) {
|
||||
// // Show a loading indicator or a placeholder AppBar while waiting
|
||||
// return AppBar(
|
||||
// title: Text('Loading...'),
|
||||
// );
|
||||
// }
|
||||
return Scaffold(
|
||||
backgroundColor: Color(0xFFFFFCE5), // Set background color for AppBar
|
||||
appBar: PreferredSize(
|
||||
preferredSize: widget.preferredSize,
|
||||
child: SafeArea(
|
||||
child: Container(
|
||||
padding: Responsive.isDesktop(context)
|
||||
? EdgeInsets.symmetric(horizontal: 16.0)
|
||||
: EdgeInsets.symmetric(horizontal: 0),
|
||||
child: Row(
|
||||
children: [
|
||||
// Logo Column
|
||||
Expanded(
|
||||
flex: Responsive.isDesktop(context) ? 3 : 9,
|
||||
child: Row(
|
||||
mainAxisAlignment: Responsive.isDesktop(context)
|
||||
? MainAxisAlignment.spaceEvenly
|
||||
: MainAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 10, bottom: 10),
|
||||
width: 150,
|
||||
height: 150,
|
||||
child: Image.asset(
|
||||
'assets/nhance_client_logo.png',
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// AdaptiveNavBar Column
|
||||
Expanded(
|
||||
flex: Responsive.isDesktop(context) ? 9 : 3,
|
||||
child: AdaptiveNavBar(
|
||||
screenWidth: sw,
|
||||
backgroundColor: Color(0xFFFFFCE5),
|
||||
leading:
|
||||
Container(), // Set an empty container as we have the logo separately
|
||||
title: Text(''),
|
||||
navBarItems: [
|
||||
// if (Responsive.isDesktop(context))
|
||||
// if (hideInactiveStatus)
|
||||
// NavBarItem(
|
||||
// text: "Inactive Policy",
|
||||
// onTap: () {
|
||||
// Navigator.pushNamed(context, 'oldPolicy');
|
||||
// },
|
||||
// ),
|
||||
if (showBackToHR)
|
||||
NavBarItem(
|
||||
text: "Back To HR",
|
||||
onTap: () {
|
||||
hrLogout(context);
|
||||
},
|
||||
),
|
||||
NavBarItem(
|
||||
text: "Logout",
|
||||
onTap: () async {
|
||||
logout(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,807 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:nhancepolicy/service/api_service.dart';
|
||||
import 'package:nhancepolicy/service/svg_service.dart';
|
||||
import 'package:nhancepolicy/service/token_storage_service.dart';
|
||||
import 'package:nhancepolicy/logger.dart';
|
||||
|
||||
class NhanceSideBar extends StatefulWidget {
|
||||
const NhanceSideBar({super.key});
|
||||
|
||||
@override
|
||||
State<NhanceSideBar> createState() => _NhanceSideBarState();
|
||||
}
|
||||
|
||||
class _NhanceSideBarState extends State<NhanceSideBar> {
|
||||
String? activeRoute;
|
||||
late ApiService apiService;
|
||||
OverlayEntry? _claimsOverlayEntry;
|
||||
bool _isHoveringClaimsItem = false;
|
||||
bool _isHoveringClaimsMenu = false;
|
||||
DateTime? _claimsMenuSuppressUntil;
|
||||
// bool isLoading = true; // Add a loading state
|
||||
// bool hideInactiveStatus = true;
|
||||
final tokenService = TokenStorageService();
|
||||
// dynamic enrollmentModules = [];
|
||||
// dynamic postModules = [];
|
||||
|
||||
List<Map<String, dynamic>> sideMenuItems = [];
|
||||
List<int> postModules = [];
|
||||
List<int> enrollmentModules = [];
|
||||
List<String> claimsSubMenu = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
apiService = ApiService(context);
|
||||
_buildSideMenu();
|
||||
// _checkTokens();
|
||||
}
|
||||
|
||||
Future<void> _buildSideMenu() async {
|
||||
final enrollmentRaw =
|
||||
await tokenService.readValue('enrollmentAllowed_modules'); // "[1]"
|
||||
final postRaw =
|
||||
await tokenService.readValue('empAllowed_modules'); // "[2,3,4]"
|
||||
|
||||
logDebug('enrollmentRaw $enrollmentRaw');
|
||||
logDebug('postRaw $postRaw');
|
||||
|
||||
// ✅ Decode safely
|
||||
enrollmentModules = enrollmentRaw != null && enrollmentRaw.isNotEmpty
|
||||
? List<int>.from(jsonDecode(enrollmentRaw))
|
||||
: [];
|
||||
|
||||
postModules = postRaw != null && postRaw.isNotEmpty
|
||||
? List<int>.from(jsonDecode(postRaw))
|
||||
: [];
|
||||
|
||||
final claimsSubMenuRaw = await tokenService.readValue('claims_sub_menu');
|
||||
if (claimsSubMenuRaw != null && claimsSubMenuRaw.isNotEmpty) {
|
||||
claimsSubMenu = List<dynamic>.from(jsonDecode(claimsSubMenuRaw))
|
||||
.map((e) => e.toString())
|
||||
.toList();
|
||||
} else {
|
||||
// Fallback: decoded JWT / selected branch may still have the list.
|
||||
final decoded = tokenService.getDecodedToken();
|
||||
final branch = tokenService.getSelectedBranch();
|
||||
final fromDecoded = decoded?['claims_sub_menu'];
|
||||
final fromBranch = branch?['claims_sub_menu'];
|
||||
final source = fromBranch is List
|
||||
? fromBranch
|
||||
: (fromDecoded is List ? fromDecoded : const []);
|
||||
claimsSubMenu = source.map((e) => e.toString()).toList();
|
||||
}
|
||||
|
||||
logDebug('enrollmentModules $enrollmentModules');
|
||||
logDebug('postModules $postModules');
|
||||
logDebug('claimsSubMenu $claimsSubMenu');
|
||||
|
||||
final List<Map<String, dynamic>> items = [];
|
||||
|
||||
// ✅ POLICIES (1 OR 2)
|
||||
if (enrollmentModules.contains(1) || postModules.contains(2)) {
|
||||
items.add({
|
||||
'route': 'policies',
|
||||
'label': 'Policies',
|
||||
'icon': 'policies',
|
||||
});
|
||||
items.add({
|
||||
'route': 'pendingDependentApproval',
|
||||
'label': 'Approvals',
|
||||
'icon': 'pending_approve',
|
||||
'useMaterialIcon': true,
|
||||
});
|
||||
}
|
||||
|
||||
// CD
|
||||
if (postModules.contains(3)) {
|
||||
items.add({
|
||||
'route': 'CdPoliciesList',
|
||||
'label': 'CD',
|
||||
'icon': 'cd',
|
||||
});
|
||||
}
|
||||
|
||||
// CLAIMS — only when module 4 and at least one sub-menu is allowed
|
||||
if (postModules.contains(4) && _claimsSubMenuRoutes.isNotEmpty) {
|
||||
items.add({
|
||||
'route': 'ClaimsPolicies',
|
||||
'label': 'Claims',
|
||||
'icon': 'claims',
|
||||
});
|
||||
}
|
||||
|
||||
setState(() {
|
||||
sideMenuItems = items;
|
||||
});
|
||||
}
|
||||
|
||||
/// Allowed Claims destinations based on claims_sub_menu.
|
||||
List<({String label, String route})> get _claimsSubMenuRoutes {
|
||||
final items = <({String label, String route})>[];
|
||||
if (claimsSubMenu.contains('EB')) {
|
||||
items.add((label: 'Employee Benefits', route: 'ClaimsPolicies'));
|
||||
}
|
||||
if (claimsSubMenu.contains('Non-EB')) {
|
||||
items.add((label: 'Non-Employee Benefits', route: 'nonEBClaimsList'));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
bool get _hasMultipleClaimsSubMenus => _claimsSubMenuRoutes.length > 1;
|
||||
|
||||
String? get _singleClaimsRoute {
|
||||
final routes = _claimsSubMenuRoutes;
|
||||
if (routes.length == 1) return routes.first.route;
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> logout(BuildContext context) async {
|
||||
// final prefs = await SharedPreferences.getInstance();
|
||||
// final String? hrtoken = prefs.getString('_postToken');
|
||||
// final String? token = prefs.getString('enrollToken');
|
||||
// prefs.clear();
|
||||
logDebug('LocalStorage Cleared');
|
||||
apiService.logout();
|
||||
// Navigator.pushNamed(context, 'hrLogin');
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
// Safely capture the current route name
|
||||
final newRoute = ModalRoute.of(context)?.settings.name;
|
||||
if (activeRoute != newRoute) {
|
||||
setState(() {
|
||||
activeRoute = newRoute;
|
||||
});
|
||||
_closeClaimsMenu();
|
||||
// Re-verify the menu items if the route changes
|
||||
_buildSideMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void _navigate(String routeName) {
|
||||
_closeClaimsMenu();
|
||||
if (activeRoute == routeName) return;
|
||||
// Navigation should happen first, didChangeDependencies will handle the state
|
||||
Navigator.pushReplacementNamed(context, routeName);
|
||||
}
|
||||
|
||||
void _showClaimsSubMenu(GlobalKey key) {
|
||||
if (!_hasMultipleClaimsSubMenus) return;
|
||||
|
||||
if (_claimsMenuSuppressUntil != null &&
|
||||
DateTime.now().isBefore(_claimsMenuSuppressUntil!)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final targetContext = key.currentContext;
|
||||
if (targetContext == null) return;
|
||||
|
||||
final box = targetContext.findRenderObject() as RenderBox?;
|
||||
if (box == null) return;
|
||||
|
||||
final offset = box.localToGlobal(Offset.zero);
|
||||
final subMenus = _claimsSubMenuRoutes;
|
||||
_claimsOverlayEntry?.remove();
|
||||
_claimsOverlayEntry = OverlayEntry(
|
||||
builder: (context) => Positioned(
|
||||
left: offset.dx + box.size.width + 6,
|
||||
top: offset.dy,
|
||||
child: MouseRegion(
|
||||
onEnter: (_) {
|
||||
_isHoveringClaimsMenu = true;
|
||||
},
|
||||
onExit: (_) {
|
||||
_isHoveringClaimsMenu = false;
|
||||
_scheduleCloseClaimsMenu();
|
||||
},
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Container(
|
||||
width: 200,
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Colors.black26,
|
||||
blurRadius: 8,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
for (final item in subMenus)
|
||||
_buildClaimsMenuItem(item.label, item.route),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
Overlay.of(context).insert(_claimsOverlayEntry!);
|
||||
}
|
||||
|
||||
Widget _buildClaimsMenuItem(String label, String route) {
|
||||
return _ClaimsSubMenuItem(
|
||||
label: label,
|
||||
onTap: () {
|
||||
_claimsMenuSuppressUntil =
|
||||
DateTime.now().add(const Duration(milliseconds: 700));
|
||||
_isHoveringClaimsItem = false;
|
||||
_isHoveringClaimsMenu = false;
|
||||
_closeClaimsMenu();
|
||||
_navigate(route);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _scheduleCloseClaimsMenu() {
|
||||
Future.delayed(const Duration(milliseconds: 120), () {
|
||||
if (!_isHoveringClaimsItem && !_isHoveringClaimsMenu) {
|
||||
_closeClaimsMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _closeClaimsMenu() {
|
||||
_claimsOverlayEntry?.remove();
|
||||
_claimsOverlayEntry = null;
|
||||
_isHoveringClaimsMenu = false;
|
||||
}
|
||||
|
||||
Future<void> _openHelpDialog() async {
|
||||
final isPostUser = postModules.isNotEmpty;
|
||||
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) => _HelpContactDialog(
|
||||
isPostUser: isPostUser,
|
||||
apiService: apiService,
|
||||
tokenService: tokenService,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 70,
|
||||
color: const Color(0xFF009E9E),
|
||||
child: Column(
|
||||
children: [
|
||||
// const SizedBox(height: 12),
|
||||
|
||||
//
|
||||
// _SideItem(
|
||||
// // icon: Icons.policy,
|
||||
// icon: SvgPicture.string(
|
||||
// SvgService.getSvg('policies'),
|
||||
// width: 35,
|
||||
// height: 35,
|
||||
// colorFilter: const ColorFilter.mode(
|
||||
// Colors.white,
|
||||
// BlendMode.srcIn,
|
||||
// ),
|
||||
// ),
|
||||
// label: "Policies",
|
||||
// isActive: activeRoute == 'oldPolicy', // or correct policy route
|
||||
// onTap: () => _navigate('oldPolicy'),
|
||||
// ),
|
||||
//
|
||||
// _SideItem(
|
||||
// // icon: Icons.credit_card,
|
||||
// icon: SvgPicture.string(
|
||||
// SvgService.getSvg('cd'),
|
||||
// width: 35,
|
||||
// height: 35,
|
||||
// colorFilter: const ColorFilter.mode(
|
||||
// Colors.white,
|
||||
// BlendMode.srcIn,
|
||||
// ),
|
||||
// ),
|
||||
// label: "CD",
|
||||
// isActive: activeRoute == 'cdTransactionDetails',
|
||||
// onTap: () => _navigate('cdTransactionDetails'),
|
||||
// ),
|
||||
//
|
||||
// _SideItem(
|
||||
// // icon: Icons.assignment,
|
||||
// icon: SvgPicture.string(
|
||||
// SvgService.getSvg('claims'),
|
||||
// width: 35,
|
||||
// height: 35,
|
||||
// colorFilter: const ColorFilter.mode(
|
||||
// Colors.white,
|
||||
// BlendMode.srcIn,
|
||||
// ),
|
||||
// ),
|
||||
// label: "Claims",
|
||||
// isActive: activeRoute == 'claims', // claims tab inside dashboard
|
||||
// onTap: () => _navigate('claims'),
|
||||
// ),
|
||||
//
|
||||
// const Spacer(),
|
||||
//
|
||||
// _SideItem(
|
||||
// // icon: Icons.logout,
|
||||
// icon: SvgPicture.string(
|
||||
// SvgService.getSvg('logout'),
|
||||
// width: 35,
|
||||
// height: 35,
|
||||
// colorFilter: const ColorFilter.mode(
|
||||
// Colors.white,
|
||||
// BlendMode.srcIn,
|
||||
// ),
|
||||
// ),
|
||||
// label: "Logout",
|
||||
// onTap: () {
|
||||
// logout(context);
|
||||
// },
|
||||
// ),
|
||||
if (postModules.isNotEmpty && postModules.contains(5))
|
||||
_SideItem(
|
||||
icon: const Icon(
|
||||
Icons.space_dashboard_outlined,
|
||||
color: Colors.white,
|
||||
size: 30,
|
||||
),
|
||||
label: "Dashboard",
|
||||
isActive: activeRoute == 'claimsOverviewDashboard',
|
||||
onTap: () => _navigate('claimsOverviewDashboard'),
|
||||
),
|
||||
|
||||
...sideMenuItems.map((item) {
|
||||
final isClaims = item['route'] == 'ClaimsPolicies';
|
||||
final isClaimsActive =
|
||||
activeRoute == 'ClaimsPolicies' || activeRoute == 'nonEBClaimsList';
|
||||
final itemKey = GlobalKey();
|
||||
final showClaimsSubMenu = isClaims && _hasMultipleClaimsSubMenus;
|
||||
final claimsDirectRoute =
|
||||
isClaims ? (_singleClaimsRoute ?? 'ClaimsPolicies') : null;
|
||||
final useMaterialIcon = item['useMaterialIcon'] == true;
|
||||
|
||||
return _SideItem(
|
||||
key: itemKey,
|
||||
icon: useMaterialIcon
|
||||
? const Icon(
|
||||
Icons.how_to_reg_outlined,
|
||||
color: Colors.white,
|
||||
size: 30,
|
||||
)
|
||||
: SvgPicture.string(
|
||||
SvgService.getSvg(item['icon']),
|
||||
width: 35,
|
||||
height: 35,
|
||||
colorFilter: const ColorFilter.mode(
|
||||
Colors.white,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
label: item['label'],
|
||||
isActive: isClaims ? isClaimsActive : activeRoute == item['route'],
|
||||
onTap: showClaimsSubMenu
|
||||
? null
|
||||
: () => _navigate(
|
||||
claimsDirectRoute ?? item['route'] as String,
|
||||
),
|
||||
onHoverEnter: showClaimsSubMenu
|
||||
? () {
|
||||
_isHoveringClaimsItem = true;
|
||||
_showClaimsSubMenu(itemKey);
|
||||
}
|
||||
: null,
|
||||
onHoverExit: showClaimsSubMenu
|
||||
? () {
|
||||
_isHoveringClaimsItem = false;
|
||||
_scheduleCloseClaimsMenu();
|
||||
}
|
||||
: null,
|
||||
);
|
||||
}),
|
||||
|
||||
|
||||
|
||||
// if (postModules.isNotEmpty && postModules.contains(5))
|
||||
// _SideItem(
|
||||
// // icon: Icons.dashboard,
|
||||
// icon: SvgPicture.string(
|
||||
// SvgService.getSvg('dashboard'),
|
||||
// width: 35,
|
||||
// height: 35,
|
||||
// colorFilter: const ColorFilter.mode(
|
||||
// Colors.white,
|
||||
// BlendMode.srcIn,
|
||||
// ),
|
||||
// ),
|
||||
// label: "Insights",
|
||||
// isActive: activeRoute == 'hrDashboard',
|
||||
// onTap: () => _navigate('hrDashboard'),
|
||||
// ),
|
||||
|
||||
const Spacer(),
|
||||
|
||||
if (enrollmentModules.isNotEmpty || postModules.isNotEmpty)
|
||||
_SideItem(
|
||||
icon: const Icon(
|
||||
Icons.help_outline,
|
||||
color: Colors.white,
|
||||
size: 30,
|
||||
),
|
||||
label: 'Help',
|
||||
onTap: _openHelpDialog,
|
||||
),
|
||||
|
||||
_SideItem(
|
||||
icon: SvgPicture.string(
|
||||
SvgService.getSvg('logout'),
|
||||
width: 35,
|
||||
height: 35,
|
||||
colorFilter: const ColorFilter.mode(
|
||||
Colors.white,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
label: "Logout",
|
||||
onTap: () => logout(context),
|
||||
),
|
||||
|
||||
// const SizedBox(height: 10),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HelpContactDialog extends StatefulWidget {
|
||||
final bool isPostUser;
|
||||
final ApiService apiService;
|
||||
final TokenStorageService tokenService;
|
||||
|
||||
const _HelpContactDialog({
|
||||
required this.isPostUser,
|
||||
required this.apiService,
|
||||
required this.tokenService,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_HelpContactDialog> createState() => _HelpContactDialogState();
|
||||
}
|
||||
|
||||
class _HelpContactDialogState extends State<_HelpContactDialog> {
|
||||
bool _isLoading = false;
|
||||
List<Map<String, dynamic>> _level1Contacts = [];
|
||||
List<Map<String, dynamic>> _level2Contacts = [];
|
||||
String? _errorMessage;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.isPostUser) {
|
||||
_loadClientRM();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadClientRM() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_errorMessage = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final clientId = await widget.tokenService.readValue('empClientId');
|
||||
final token = widget.tokenService.getCurrentToken();
|
||||
|
||||
if (clientId == null ||
|
||||
clientId.toString().isEmpty ||
|
||||
token == null ||
|
||||
token.isEmpty) {
|
||||
setState(() {
|
||||
_errorMessage = 'Client details not found';
|
||||
_isLoading = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final response = await widget.apiService.getClientRMApi(
|
||||
clientId.toString(),
|
||||
token,
|
||||
);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
if (response['status'] == 'success' && response['data'] is Map) {
|
||||
final data = Map<String, dynamic>.from(response['data'] as Map);
|
||||
setState(() {
|
||||
_level1Contacts = _parseContacts(data['level_1']);
|
||||
_level2Contacts = _parseContacts(data['level_2']);
|
||||
_isLoading = false;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_errorMessage =
|
||||
response['message']?.toString() ?? 'Failed to load contacts';
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
logDebug('getClientRM failed: $e');
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_errorMessage = 'Failed to load contacts';
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> _parseContacts(dynamic raw) {
|
||||
if (raw is! List) return [];
|
||||
return raw
|
||||
.whereType<Map>()
|
||||
.map((item) => Map<String, dynamic>.from(item))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Widget _buildContactCard(Map<String, dynamic> contact) {
|
||||
final name = contact['first_name']?.toString() ?? '-';
|
||||
final email = contact['email']?.toString() ?? '-';
|
||||
final mobile = contact['mobile']?.toString() ?? '-';
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF5F5F5),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: const Color(0xFFE8E8E8)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
name,
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: const Color(0xFF009195),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
email,
|
||||
style: GoogleFonts.poppins(fontSize: 13, color: Colors.black87),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
mobile,
|
||||
style: GoogleFonts.poppins(fontSize: 13, color: Colors.black87),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLevelSection({
|
||||
required String title,
|
||||
required List<Map<String, dynamic>> contacts,
|
||||
}) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (contacts.isEmpty)
|
||||
Text(
|
||||
'No contact assigned',
|
||||
style: GoogleFonts.poppins(fontSize: 13, color: Colors.black54),
|
||||
)
|
||||
else
|
||||
...contacts.map(_buildContactCard),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent() {
|
||||
if (!widget.isPostUser) {
|
||||
return Center(
|
||||
child: Text(
|
||||
'No Level Contact',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black54,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (_isLoading) {
|
||||
return const SizedBox(
|
||||
height: 180,
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
if (_errorMessage != null) {
|
||||
return Center(
|
||||
child: Text(
|
||||
_errorMessage!,
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.poppins(fontSize: 14, color: Colors.red),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildLevelSection(
|
||||
title: 'Level 1 - Account Manager',
|
||||
contacts: _level1Contacts,
|
||||
),
|
||||
_buildLevelSection(
|
||||
title: 'Level 2 - Head',
|
||||
contacts: _level2Contacts,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final maxHeight = MediaQuery.of(context).size.height * 0.65;
|
||||
final cappedMaxHeight = maxHeight.clamp(280.0, 520.0);
|
||||
|
||||
return AlertDialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
title: Text(
|
||||
'Help',
|
||||
style: GoogleFonts.poppins(fontWeight: FontWeight.w600, fontSize: 18),
|
||||
),
|
||||
content: SizedBox(
|
||||
width: 560,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxHeight: cappedMaxHeight),
|
||||
child: Scrollbar(
|
||||
child: SingleChildScrollView(child: _buildContent()),
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF009195),
|
||||
),
|
||||
child: Text('Close', style: GoogleFonts.poppins(color: Colors.white)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SideItem extends StatelessWidget {
|
||||
// final IconData icon;
|
||||
final Widget icon; // 👈 changed
|
||||
final String label;
|
||||
final bool isActive;
|
||||
final VoidCallback? onTap;
|
||||
final VoidCallback? onHoverEnter;
|
||||
final VoidCallback? onHoverExit;
|
||||
|
||||
const _SideItem({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.label,
|
||||
this.isActive = false,
|
||||
this.onTap,
|
||||
this.onHoverEnter,
|
||||
this.onHoverExit,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MouseRegion(
|
||||
onEnter: (_) => onHoverEnter?.call(),
|
||||
onExit: (_) => onHoverExit?.call(),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.symmetric(vertical: 6),
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: isActive
|
||||
? const Color(0xFF065D61) // ✅ ACTIVE like your screenshot
|
||||
: Colors.transparent,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// 👇 SVG or Icon widget
|
||||
icon,
|
||||
// Icon(icon, color: Colors.white, size: 22),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
label,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.15,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ClaimsSubMenuItem extends StatefulWidget {
|
||||
final String label;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _ClaimsSubMenuItem({
|
||||
required this.label,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_ClaimsSubMenuItem> createState() => _ClaimsSubMenuItemState();
|
||||
}
|
||||
|
||||
class _ClaimsSubMenuItemState extends State<_ClaimsSubMenuItem> {
|
||||
bool isHovered = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MouseRegion(
|
||||
onEnter: (_) => setState(() => isHovered = true),
|
||||
onExit: (_) => setState(() => isHovered = false),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: widget.onTap,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
color: isHovered ? const Color(0xFFE5F6F6) : Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
widget.label,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: isHovered ? const Color(0xFF006F73) : Colors.black87,
|
||||
fontWeight: isHovered ? FontWeight.w600 : FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,286 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
// import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:toastification/toastification.dart';
|
||||
import 'package:nhancepolicy/logger.dart';
|
||||
|
||||
class ToastHelper {
|
||||
static void showSuccessToast(BuildContext context, String message) {
|
||||
toastification.show(
|
||||
context: context,
|
||||
type: ToastificationType.success,
|
||||
style: ToastificationStyle.flatColored,
|
||||
autoCloseDuration: const Duration(seconds: 2),
|
||||
title: Text(
|
||||
message,
|
||||
maxLines: 3, // ✅ allow wrapping
|
||||
overflow: TextOverflow.visible,
|
||||
softWrap: true,
|
||||
),
|
||||
// you can also use RichText widget for title and description parameters
|
||||
// description: RichText(
|
||||
// text: const TextSpan(text: 'This is a sample toast message. ')),
|
||||
alignment: Alignment.topRight,
|
||||
direction: TextDirection.ltr,
|
||||
animationDuration: const Duration(milliseconds: 100),
|
||||
animationBuilder: (context, animation, alignment, child) {
|
||||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.check),
|
||||
primaryColor: Colors.green,
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.black,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x07000000),
|
||||
blurRadius: 16,
|
||||
offset: Offset(0, 16),
|
||||
spreadRadius: 0,
|
||||
)
|
||||
],
|
||||
showProgressBar: true,
|
||||
closeButtonShowType: CloseButtonShowType.onHover,
|
||||
closeOnClick: false,
|
||||
pauseOnHover: true,
|
||||
dragToClose: true,
|
||||
applyBlurEffect: true,
|
||||
callbacks: ToastificationCallbacks(
|
||||
onTap: (toastItem) => logDebug('Toast ${toastItem.id} tapped'),
|
||||
onCloseButtonTap: (toastItem) =>
|
||||
logDebug('Toast ${toastItem.id} close button tapped'),
|
||||
onAutoCompleteCompleted: (toastItem) =>
|
||||
logDebug('Toast ${toastItem.id} auto complete completed'),
|
||||
onDismissed: (toastItem) => logDebug('Toast ${toastItem.id} dismissed'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static void showWarningToast(BuildContext context, String message) {
|
||||
toastification.show(
|
||||
context: context,
|
||||
type: ToastificationType.warning,
|
||||
style: ToastificationStyle.flatColored,
|
||||
autoCloseDuration: const Duration(seconds: 2),
|
||||
title: Text(message),
|
||||
// you can also use RichText widget for title and description parameters
|
||||
// description: RichText(
|
||||
// text: const TextSpan(text: 'This is a sample toast message. ')),
|
||||
alignment: Alignment.topRight,
|
||||
direction: TextDirection.ltr,
|
||||
animationDuration: const Duration(milliseconds: 100),
|
||||
animationBuilder: (context, animation, alignment, child) {
|
||||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.warning),
|
||||
primaryColor: Colors.amberAccent,
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.black,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x07000000),
|
||||
blurRadius: 16,
|
||||
offset: Offset(0, 16),
|
||||
spreadRadius: 0,
|
||||
)
|
||||
],
|
||||
showProgressBar: true,
|
||||
closeButtonShowType: CloseButtonShowType.onHover,
|
||||
closeOnClick: false,
|
||||
pauseOnHover: true,
|
||||
dragToClose: true,
|
||||
applyBlurEffect: true,
|
||||
callbacks: ToastificationCallbacks(
|
||||
onTap: (toastItem) => logDebug('Toast ${toastItem.id} tapped'),
|
||||
onCloseButtonTap: (toastItem) =>
|
||||
logDebug('Toast ${toastItem.id} close button tapped'),
|
||||
onAutoCompleteCompleted: (toastItem) =>
|
||||
logDebug('Toast ${toastItem.id} auto complete completed'),
|
||||
onDismissed: (toastItem) => logDebug('Toast ${toastItem.id} dismissed'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static void showErrorToast(BuildContext context, String message) {
|
||||
toastification.show(
|
||||
context: context,
|
||||
type: ToastificationType.error,
|
||||
style: ToastificationStyle.flatColored,
|
||||
autoCloseDuration: const Duration(seconds: 2),
|
||||
title: Text(
|
||||
message,
|
||||
maxLines: 3, // ✅ allow wrapping
|
||||
overflow: TextOverflow.visible,
|
||||
softWrap: true,
|
||||
),
|
||||
// you can also use RichText widget for title and description parameters
|
||||
// description: RichText(
|
||||
// text: const TextSpan(text: 'This is a sample toast message. ')),
|
||||
alignment: Alignment.topRight,
|
||||
direction: TextDirection.ltr,
|
||||
animationDuration: const Duration(milliseconds: 100),
|
||||
animationBuilder: (context, animation, alignment, child) {
|
||||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.error),
|
||||
primaryColor: Colors.redAccent,
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.black,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x07000000),
|
||||
blurRadius: 16,
|
||||
offset: Offset(0, 16),
|
||||
spreadRadius: 0,
|
||||
)
|
||||
],
|
||||
showProgressBar: true,
|
||||
closeButtonShowType: CloseButtonShowType.onHover,
|
||||
closeOnClick: false,
|
||||
pauseOnHover: true,
|
||||
dragToClose: true,
|
||||
applyBlurEffect: true,
|
||||
callbacks: ToastificationCallbacks(
|
||||
onTap: (toastItem) => logDebug('Toast ${toastItem.id} tapped'),
|
||||
onCloseButtonTap: (toastItem) =>
|
||||
logDebug('Toast ${toastItem.id} close button tapped'),
|
||||
onAutoCompleteCompleted: (toastItem) =>
|
||||
logDebug('Toast ${toastItem.id} auto complete completed'),
|
||||
onDismissed: (toastItem) => logDebug('Toast ${toastItem.id} dismissed'),
|
||||
),
|
||||
);
|
||||
// _showToast(context, message, Colors.red);
|
||||
}
|
||||
|
||||
static void showErrorToast2(
|
||||
BuildContext context, String message, String decmessage) {
|
||||
toastification.show(
|
||||
context: context,
|
||||
type: ToastificationType.error,
|
||||
style: ToastificationStyle.flatColored,
|
||||
autoCloseDuration: const Duration(seconds: 2),
|
||||
title: Text(message),
|
||||
// you can also use RichText widget for title and description parameters
|
||||
description: RichText(text: TextSpan(text: decmessage)),
|
||||
alignment: Alignment.topRight,
|
||||
direction: TextDirection.ltr,
|
||||
animationDuration: const Duration(milliseconds: 100),
|
||||
animationBuilder: (context, animation, alignment, child) {
|
||||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.error),
|
||||
primaryColor: Colors.redAccent,
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.black,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x07000000),
|
||||
blurRadius: 16,
|
||||
offset: Offset(0, 16),
|
||||
spreadRadius: 0,
|
||||
)
|
||||
],
|
||||
showProgressBar: true,
|
||||
closeButtonShowType: CloseButtonShowType.onHover,
|
||||
closeOnClick: false,
|
||||
pauseOnHover: true,
|
||||
dragToClose: true,
|
||||
applyBlurEffect: true,
|
||||
callbacks: ToastificationCallbacks(
|
||||
onTap: (toastItem) => logDebug('Toast ${toastItem.id} tapped'),
|
||||
onCloseButtonTap: (toastItem) =>
|
||||
logDebug('Toast ${toastItem.id} close button tapped'),
|
||||
onAutoCompleteCompleted: (toastItem) =>
|
||||
logDebug('Toast ${toastItem.id} auto complete completed'),
|
||||
onDismissed: (toastItem) => logDebug('Toast ${toastItem.id} dismissed'),
|
||||
),
|
||||
);
|
||||
// _showToast(context, message, Colors.red);
|
||||
}
|
||||
|
||||
static void showInfoToast(BuildContext context, String message) {
|
||||
toastification.show(
|
||||
context: context,
|
||||
type: ToastificationType.info,
|
||||
style: ToastificationStyle.flatColored,
|
||||
autoCloseDuration: const Duration(seconds: 2),
|
||||
title: Text(message),
|
||||
// you can also use RichText widget for title and description parameters
|
||||
// description: RichText(
|
||||
// text: const TextSpan(text: 'This is a sample toast message. ')),
|
||||
alignment: Alignment.topRight,
|
||||
direction: TextDirection.ltr,
|
||||
animationDuration: const Duration(milliseconds: 100),
|
||||
animationBuilder: (context, animation, alignment, child) {
|
||||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.info),
|
||||
primaryColor: Colors.lightBlue,
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.black,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x07000000),
|
||||
blurRadius: 16,
|
||||
offset: Offset(0, 16),
|
||||
spreadRadius: 0,
|
||||
)
|
||||
],
|
||||
showProgressBar: true,
|
||||
closeButtonShowType: CloseButtonShowType.onHover,
|
||||
closeOnClick: false,
|
||||
pauseOnHover: true,
|
||||
dragToClose: true,
|
||||
applyBlurEffect: true,
|
||||
callbacks: ToastificationCallbacks(
|
||||
onTap: (toastItem) => logDebug('Toast ${toastItem.id} tapped'),
|
||||
onCloseButtonTap: (toastItem) =>
|
||||
logDebug('Toast ${toastItem.id} close button tapped'),
|
||||
onAutoCompleteCompleted: (toastItem) =>
|
||||
logDebug('Toast ${toastItem.id} auto complete completed'),
|
||||
onDismissed: (toastItem) => logDebug('Toast ${toastItem.id} dismissed'),
|
||||
),
|
||||
);
|
||||
// _showToast(context, message, Colors.red);
|
||||
}
|
||||
|
||||
static void _showToast(BuildContext context, String message, Color color) {
|
||||
// Fluttertoast.showToast(
|
||||
// msg: message,
|
||||
// toastLength: Toast.LENGTH_SHORT,
|
||||
// gravity: ToastGravity.TOP,
|
||||
// timeInSecForIosWeb: 5,
|
||||
// backgroundColor: color,
|
||||
// textColor: Colors.white,
|
||||
// fontSize: 16.0,
|
||||
// );
|
||||
}
|
||||
}
|
||||
@ -1,400 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../config/environment.dart';
|
||||
import '../service/token_storage_service.dart';
|
||||
|
||||
class NhanceTopBar extends StatefulWidget implements PreferredSizeWidget {
|
||||
const NhanceTopBar({super.key});
|
||||
|
||||
@override
|
||||
Size get preferredSize => const Size.fromHeight(64);
|
||||
|
||||
@override
|
||||
State<NhanceTopBar> createState() => _NhanceTopBarState();
|
||||
}
|
||||
|
||||
class _NhanceTopBarState extends State<NhanceTopBar> {
|
||||
final tokenStorage = TokenStorageService();
|
||||
|
||||
List<Map<String, dynamic>> branches = [];
|
||||
Map<String, dynamic>? selectedBranch;
|
||||
|
||||
/// client_id → logo URL
|
||||
final Map<String, String> _logoByClientId = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadBranches();
|
||||
}
|
||||
|
||||
void _loadBranches() {
|
||||
branches = tokenStorage.getCombinedBranches();
|
||||
selectedBranch = tokenStorage.getSelectedBranch();
|
||||
setState(() {});
|
||||
_loadBranchLogos();
|
||||
}
|
||||
|
||||
String _clientKey(Map<String, dynamic> branch) {
|
||||
return (branch['client_id'] ?? branch['id'] ?? '').toString();
|
||||
}
|
||||
|
||||
String? _logoFromBranch(Map<String, dynamic> branch) {
|
||||
final candidates = [
|
||||
branch['client_logo'],
|
||||
branch['logo'],
|
||||
branch['clientLogo'],
|
||||
];
|
||||
for (final value in candidates) {
|
||||
final url = value?.toString().trim() ?? '';
|
||||
if (url.isNotEmpty) return url;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, dynamic>? _decodeBranchToken(String token) {
|
||||
try {
|
||||
final parts = token.split('.');
|
||||
if (parts.length != 3) return null;
|
||||
|
||||
final payload = base64Url.normalize(parts[1]);
|
||||
final decoded = utf8.decode(base64Url.decode(payload));
|
||||
final parsed = json.decode(decoded);
|
||||
if (parsed is Map<String, dynamic>) return parsed;
|
||||
if (parsed is Map) return Map<String, dynamic>.from(parsed);
|
||||
return null;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Read post/pre client + branch ids from selected branch JWT payload.
|
||||
Map<String, String>? _clientDetailsParamsFromBranch(
|
||||
Map<String, dynamic> branch,
|
||||
) {
|
||||
final token = branch['token']?.toString() ??
|
||||
tokenStorage.getCurrentToken() ??
|
||||
'';
|
||||
if (token.isEmpty) return null;
|
||||
|
||||
final decoded = _decodeBranchToken(token);
|
||||
if (decoded == null) return null;
|
||||
|
||||
final postClientId = decoded['post_client_id']?.toString().trim() ?? '';
|
||||
final postBranchId = decoded['post_branch_id']?.toString().trim() ?? '';
|
||||
final preClientId = decoded['pre_client_id']?.toString().trim() ?? '';
|
||||
final preBranchId = decoded['pre_branch_id']?.toString().trim() ?? '';
|
||||
|
||||
if (postClientId.isEmpty &&
|
||||
postBranchId.isEmpty &&
|
||||
preClientId.isEmpty &&
|
||||
preBranchId.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
'post_client_id': postClientId,
|
||||
'post_branch_id': postBranchId,
|
||||
'pre_client_id': preClientId,
|
||||
'pre_branch_id': preBranchId,
|
||||
};
|
||||
}
|
||||
|
||||
Future<void> _loadBranchLogos() async {
|
||||
final uniqueBranches = <String, Map<String, dynamic>>{};
|
||||
for (final branch in branches) {
|
||||
final key = _clientKey(branch);
|
||||
if (key.isEmpty) continue;
|
||||
uniqueBranches.putIfAbsent(key, () => branch);
|
||||
}
|
||||
|
||||
for (final entry in uniqueBranches.entries) {
|
||||
final key = entry.key;
|
||||
if (_logoByClientId.containsKey(key)) continue;
|
||||
|
||||
final fromBranch = _logoFromBranch(entry.value);
|
||||
if (fromBranch != null) {
|
||||
if (!mounted) return;
|
||||
setState(() => _logoByClientId[key] = fromBranch);
|
||||
continue;
|
||||
}
|
||||
|
||||
final logoUrl = await _fetchClientLogoUrl(entry.value);
|
||||
if (!mounted) return;
|
||||
if (logoUrl != null && logoUrl.isNotEmpty) {
|
||||
setState(() => _logoByClientId[key] = logoUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> _fetchClientLogoUrl(Map<String, dynamic> branch) async {
|
||||
try {
|
||||
final token = branch['token']?.toString() ??
|
||||
tokenStorage.getCurrentToken() ??
|
||||
'';
|
||||
if (token.isEmpty) return null;
|
||||
|
||||
final params = _clientDetailsParamsFromBranch(branch);
|
||||
if (params == null) return null;
|
||||
|
||||
final url = Uri.parse('${Environment.apiUrl}getClientDetails').replace(
|
||||
queryParameters: params,
|
||||
);
|
||||
|
||||
final response = await http.get(
|
||||
url,
|
||||
headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
'APP-SIGNATURE':
|
||||
'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode != 200) return null;
|
||||
final data = jsonDecode(response.body);
|
||||
if (data is! Map || data['status'] != 'success') return null;
|
||||
|
||||
final logo = data['data']?['client']?['client_logo']?.toString().trim();
|
||||
if (logo == null || logo.isEmpty) return null;
|
||||
return logo;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> _getBranchSwitchRoute() async {
|
||||
final postRaw = await tokenStorage.readValue('empAllowed_modules');
|
||||
final postModules = postRaw != null && postRaw.isNotEmpty
|
||||
? List<int>.from(jsonDecode(postRaw))
|
||||
: <int>[];
|
||||
|
||||
if (postModules.isNotEmpty && postModules.contains(5)) {
|
||||
return 'claimsOverviewDashboard';
|
||||
}
|
||||
return 'policies';
|
||||
}
|
||||
|
||||
Future<void> _onBranchSelected(Map<String, dynamic> branch) async {
|
||||
final tokenStorage = TokenStorageService();
|
||||
|
||||
await tokenStorage.resetSessionAndSwitchBranch(branch);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
_loadBranches();
|
||||
|
||||
final route = await _getBranchSwitchRoute();
|
||||
|
||||
if (!mounted) return;
|
||||
Navigator.pushReplacementNamed(context, route);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedLogo = selectedBranch == null
|
||||
? null
|
||||
: _logoByClientId[_clientKey(selectedBranch!)];
|
||||
|
||||
return AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
backgroundColor: const Color(0xFFBFEFEF),
|
||||
elevation: 0,
|
||||
title: Row(
|
||||
children: [
|
||||
Image.asset('assets/nhance_client_logo.png', height: 32),
|
||||
const Spacer(),
|
||||
if (selectedBranch != null)
|
||||
_BranchPopup(
|
||||
clientName: selectedBranch!['client_name']?.toString() ?? '',
|
||||
branchName: selectedBranch!['branch_name']?.toString() ?? '',
|
||||
selectedLogoUrl: selectedLogo,
|
||||
branches: branches,
|
||||
logoByClientId: _logoByClientId,
|
||||
clientKeyBuilder: _clientKey,
|
||||
onSelected: _onBranchSelected,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _BranchPopup extends StatelessWidget {
|
||||
final String clientName;
|
||||
final String branchName;
|
||||
final String? selectedLogoUrl;
|
||||
final List<Map<String, dynamic>> branches;
|
||||
final Map<String, String> logoByClientId;
|
||||
final String Function(Map<String, dynamic>) clientKeyBuilder;
|
||||
final Function(Map<String, dynamic>) onSelected;
|
||||
|
||||
const _BranchPopup({
|
||||
required this.clientName,
|
||||
required this.branchName,
|
||||
required this.selectedLogoUrl,
|
||||
required this.branches,
|
||||
required this.logoByClientId,
|
||||
required this.clientKeyBuilder,
|
||||
required this.onSelected,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopupMenuButton<Map<String, dynamic>>(
|
||||
tooltip: '',
|
||||
offset: const Offset(0, 48),
|
||||
onSelected: onSelected,
|
||||
itemBuilder: (context) {
|
||||
return branches.map((branch) {
|
||||
final logoUrl = logoByClientId[clientKeyBuilder(branch)];
|
||||
return PopupMenuItem<Map<String, dynamic>>(
|
||||
value: branch,
|
||||
child: Row(
|
||||
children: [
|
||||
_BranchLogo(logoUrl: logoUrl, size: 24),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
branch['client_name']?.toString() ?? '',
|
||||
style: const TextStyle(fontSize: 13),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
const Icon(Icons.location_on, size: 14, color: Colors.grey),
|
||||
const SizedBox(width: 4),
|
||||
Flexible(
|
||||
child: Text(
|
||||
branch['branch_name']?.toString() ?? '',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_BranchLogo(logoUrl: selectedLogoUrl, size: 32),
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
height: 40,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(22),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.08),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
clientName,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.location_on,
|
||||
size: 14,
|
||||
color: Colors.grey,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
branchName,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
const Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
size: 20,
|
||||
color: Colors.orange,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _BranchLogo extends StatelessWidget {
|
||||
final String? logoUrl;
|
||||
final double size;
|
||||
|
||||
const _BranchLogo({
|
||||
required this.logoUrl,
|
||||
required this.size,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final url = logoUrl?.trim() ?? '';
|
||||
if (url.isEmpty) {
|
||||
return _placeholder();
|
||||
}
|
||||
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Image.network(
|
||||
url,
|
||||
width: size,
|
||||
height: size,
|
||||
fit: BoxFit.contain,
|
||||
errorBuilder: (_, __, ___) => _placeholder(),
|
||||
loadingBuilder: (context, child, progress) {
|
||||
if (progress == null) return child;
|
||||
return SizedBox(
|
||||
width: size,
|
||||
height: size,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(4),
|
||||
child: CircularProgressIndicator(strokeWidth: 1.5),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _placeholder() {
|
||||
return Container(
|
||||
width: size,
|
||||
height: size,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFE8F5F5),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(color: const Color(0xFFBFEFEF)),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.business,
|
||||
size: size * 0.6,
|
||||
color: const Color(0xFF00999E),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,797 +0,0 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:nhancepolicy/responsive.dart';
|
||||
import 'package:nhancepolicy/service/api_service.dart';
|
||||
import 'package:nhancepolicy/service/token_storage_service.dart';
|
||||
import 'package:pinput/pinput.dart';
|
||||
import 'dart:async';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:jwt_decode/jwt_decode.dart';
|
||||
|
||||
import '../customAppBar/toastHelper.dart';
|
||||
import '../models/platform_helper_mobile.dart'
|
||||
if (dart.library.html) '../models/platform_helper_other.dart';
|
||||
import 'branch/branch_selection_page.dart';
|
||||
import 'config/environment.dart';
|
||||
import 'package:nhancepolicy/logger.dart';
|
||||
|
||||
// import 'dart:html' as html;
|
||||
|
||||
class MyEmailVerify extends StatefulWidget {
|
||||
final String type; // 'email' or 'mobile'
|
||||
final String value;
|
||||
|
||||
const MyEmailVerify({super.key, required this.type, required this.value});
|
||||
|
||||
@override
|
||||
State<MyEmailVerify> createState() => _MyEmailVerifyState();
|
||||
}
|
||||
|
||||
class _MyEmailVerifyState extends State<MyEmailVerify> {
|
||||
late ApiService apiService;
|
||||
TextEditingController _otpController = TextEditingController();
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
late Timer _timer;
|
||||
int _secondsRemaining = 60;
|
||||
bool _isTimerRunning = false;
|
||||
|
||||
dynamic enrollmentEmpClientBranchId;
|
||||
dynamic enrollmentEmpCodeString;
|
||||
dynamic enrollmentEmpPrimaryId;
|
||||
dynamic enrollmentGpaEmpName;
|
||||
dynamic enrollmentClient_id;
|
||||
dynamic enrollmentHrId;
|
||||
List<dynamic> enrollmentAllowed_modules = [];
|
||||
dynamic enrollmentEmp_status;
|
||||
|
||||
dynamic empCodeString;
|
||||
dynamic empClientBranchId;
|
||||
dynamic empPrimaryId;
|
||||
dynamic gpaEmpName;
|
||||
dynamic empClientId;
|
||||
dynamic empHrId;
|
||||
List<dynamic> empAllowed_modules = [];
|
||||
dynamic _token;
|
||||
dynamic _postToken;
|
||||
dynamic clientName;
|
||||
dynamic clientLogo;
|
||||
dynamic emp_status;
|
||||
dynamic fCMToken;
|
||||
late String verificationId;
|
||||
bool _isLoading = false;
|
||||
|
||||
late String _verificationId;
|
||||
dynamic empMobileNo;
|
||||
dynamic empEmailid;
|
||||
final tokenService = TokenStorageService();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
apiService = ApiService(context);
|
||||
startTimer();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void startTimer() {
|
||||
_isTimerRunning = true;
|
||||
_timer = Timer.periodic(Duration(seconds: 1), (Timer timer) {
|
||||
setState(() {
|
||||
if (_secondsRemaining > 0) {
|
||||
_secondsRemaining--;
|
||||
} else {
|
||||
_isTimerRunning = false;
|
||||
_timer.cancel();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void verifyOTP(String otp) async {
|
||||
try {
|
||||
if(otp.isEmpty){
|
||||
ToastHelper.showErrorToast(context, 'Please enter OTP');
|
||||
return;
|
||||
}
|
||||
|
||||
final Map<String, dynamic> payload = (widget.type == 'mobile')
|
||||
? {'otp': _otpController.text, 'mobile_no': widget.value}
|
||||
: {'otp': _otpController.text, 'email': widget.value};
|
||||
final response = await http.post(
|
||||
Uri.parse(Environment.apiUrl + 'getVerifiedHrData'),
|
||||
body: json.encode(payload),
|
||||
headers: {
|
||||
'APP-SIGNATURE':
|
||||
'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
|
||||
HttpHeaders.contentTypeHeader: 'application/json',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
Map<String, dynamic> data = json.decode(response.body);
|
||||
logDebug('Response data: $data');
|
||||
|
||||
final String mainStatus = data['status'] ?? 'failed';
|
||||
final Map<String, dynamic> postEnrollment1 =
|
||||
data['post_enrollment'] ?? {};
|
||||
|
||||
final String postStatus = postEnrollment1['status'] ?? 'failed';
|
||||
|
||||
/// ❌ BLOCK if BOTH failed
|
||||
if (mainStatus != 'success' && postStatus != 'success') {
|
||||
ToastHelper.showErrorToast(
|
||||
context,
|
||||
postEnrollment1['message'],
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ Safely extract preEnrollmentData
|
||||
List<dynamic> preEnrollmentData = [];
|
||||
if (data['data'] is List) {
|
||||
preEnrollmentData = data['data'];
|
||||
}
|
||||
|
||||
// ✅ Safely extract postEnrollmentData
|
||||
final postEnrollment = data['post_enrollment'] ?? {};
|
||||
List<dynamic> postEnrollmentData = [];
|
||||
if (postEnrollment['data'] is List) {
|
||||
postEnrollmentData = postEnrollment['data'];
|
||||
}
|
||||
|
||||
// Extract enrollment data
|
||||
// List<dynamic> preEnrollmentData = data['data'] ?? [];
|
||||
// Map<String, dynamic> postEnrollment = data['post_enrollment'] ?? {};
|
||||
// List<dynamic> postEnrollmentData = postEnrollment['data'] ?? [];
|
||||
|
||||
// Save to global storage
|
||||
final tokenStorage = TokenStorageService();
|
||||
await tokenStorage.saveEnrollmentData(
|
||||
preEnrollmentData,
|
||||
postEnrollmentData,
|
||||
);
|
||||
|
||||
// Navigate to branch selection
|
||||
// 🔥 Get all valid branches
|
||||
final branches = tokenStorage.getCombinedBranches();
|
||||
|
||||
if (branches.length == 1) {
|
||||
// ✅ ONLY ONE BRANCH → AUTO SELECT
|
||||
final singleBranch = branches.first;
|
||||
|
||||
// Save selected branch
|
||||
await tokenStorage.saveSelectedBranch(singleBranch);
|
||||
|
||||
final decodedToken = tokenStorage.getDecodedToken();
|
||||
final token = tokenStorage.getCurrentToken();
|
||||
|
||||
if (decodedToken == null || token == null) {
|
||||
ToastHelper.showErrorToast(context, 'Invalid token data');
|
||||
return;
|
||||
}
|
||||
|
||||
// Save decoded session values
|
||||
await tokenStorage.saveDecodedSessionData(decodedToken, token);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
// 🚀 DIRECTLY GO TO POLICIES
|
||||
Navigator.pushReplacementNamed(context, 'policies');
|
||||
} else {
|
||||
// 🧭 MULTIPLE BRANCHES → SHOW SELECTION PAGE
|
||||
if (!mounted) return;
|
||||
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const BranchSelectionPage(),
|
||||
),
|
||||
);
|
||||
}
|
||||
// logDebug('data: $data');
|
||||
// _token = data['data'];
|
||||
// String status = data['status'];
|
||||
//
|
||||
// // Directly access the post_enrollment data
|
||||
// Map<String, dynamic> post = data['post_enrollment'];
|
||||
// logDebug('post: $post');
|
||||
//
|
||||
// _postToken = post['data'];
|
||||
// String postStatus = post['status'];
|
||||
//
|
||||
// if (postStatus == 'success') {
|
||||
// setState(() {
|
||||
// _isLoading = false;
|
||||
// });
|
||||
// postSuccessData(post, data);
|
||||
// } else if (status == 'success') {
|
||||
// setState(() {
|
||||
// _isLoading = false;
|
||||
// });
|
||||
// enrollmentSuccessData(data);
|
||||
// } else {
|
||||
// setState(() {
|
||||
// _isLoading = false;
|
||||
// });
|
||||
// ToastHelper.showErrorToast(context, 'Invalid OTP. Please try again');
|
||||
// // Show a Snackbar if the OTP is invalid
|
||||
// logDebug('Invalid OTP. Please try again');
|
||||
// }
|
||||
} else if (response.statusCode == 401) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
await tokenService.clearAll(); // 🔐 clears flutter_secure_storage
|
||||
|
||||
ToastHelper.showErrorToast(context, 'Session Out');
|
||||
if (!context.mounted) return;
|
||||
Navigator.pushNamedAndRemoveUntil(
|
||||
context,
|
||||
'hrLogin',
|
||||
(route) => false,
|
||||
);
|
||||
} else if (response.statusCode == 429) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
Map<String, dynamic> data = json.decode(response.body);
|
||||
if (data.containsKey('error')) {
|
||||
final message = data['error']['message'];
|
||||
ToastHelper.showErrorToast(context, message);
|
||||
} else {
|
||||
final message = data['message'];
|
||||
ToastHelper.showErrorToast(context, message);
|
||||
}
|
||||
} else if (response.statusCode == 403) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
Map<String, dynamic> data = json.decode(response.body);
|
||||
if (data.containsKey('error')) {
|
||||
final message = data['error']['message'];
|
||||
ToastHelper.showErrorToast(context, message);
|
||||
} else {
|
||||
final message = data['message'];
|
||||
ToastHelper.showErrorToast(context, message);
|
||||
}
|
||||
} else if (response.statusCode == 451) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
Map<String, dynamic> data = json.decode(response.body);
|
||||
if (data.containsKey('error')) {
|
||||
final message = data['error']['message'];
|
||||
ToastHelper.showErrorToast(context, message);
|
||||
} else {
|
||||
final message = data['message'];
|
||||
ToastHelper.showErrorToast(context, message);
|
||||
}
|
||||
} else {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
throw Exception('Failed to load data');
|
||||
}
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
logDebug('Error: $e');
|
||||
ToastHelper.showWarningToast(context, 'Something went wrong');
|
||||
// Show a Snackbar if there's an error while verifying OTP
|
||||
logDebug('Failed to verify OTP. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
void postSuccessData(post, data) async {
|
||||
String status = data['status'];
|
||||
String postStatus = post['status'];
|
||||
|
||||
if (postStatus == 'success') {
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
prefs.setString('_postToken', post['data']);
|
||||
|
||||
// Decode the JWT token received from the API response
|
||||
Map<String, dynamic>? decodedToken = Jwt.parseJwt(post['data']);
|
||||
logDebug('postdecodedToken : $decodedToken');
|
||||
empClientBranchId = decodedToken['ref_id'];
|
||||
prefs.setString('empClientBranchId', empClientBranchId);
|
||||
empCodeString = decodedToken['emp_code'].toString();
|
||||
prefs.setString('empCode', empCodeString);
|
||||
empPrimaryId = decodedToken['id'];
|
||||
prefs.setString('empPrimaryId', empPrimaryId);
|
||||
gpaEmpName = decodedToken['name'].toString();
|
||||
prefs.setString('gpaEmpName', gpaEmpName);
|
||||
empClientId = decodedToken['client_id'];
|
||||
prefs.setString('empClientId', empClientId);
|
||||
empHrId = decodedToken['id'];
|
||||
prefs.setString('empHrId', empHrId);
|
||||
empAllowed_modules = decodedToken['allowed_modules'];
|
||||
prefs.setString('empAllowed_modules', jsonEncode(empAllowed_modules));
|
||||
// emp_status = decodedToken['emp_status'];
|
||||
// prefs.setString('emp_status', emp_status);
|
||||
// getClientLogoAndDetails();
|
||||
|
||||
logDebug('Successfully Login');
|
||||
|
||||
// Redirect to another page
|
||||
}
|
||||
|
||||
if (status == 'success') {
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
prefs.setString('enrollToken', data['data']);
|
||||
|
||||
// Decode the JWT token received from the API response
|
||||
Map<String, dynamic>? decodedToken = Jwt.parseJwt(data['data']);
|
||||
logDebug('postenrolldecodedToken : $decodedToken');
|
||||
enrollmentEmpClientBranchId = decodedToken['ref_id'];
|
||||
prefs.setString(
|
||||
'enrollmentEmpClientBranchId', enrollmentEmpClientBranchId);
|
||||
enrollmentEmpCodeString = decodedToken['emp_code'].toString();
|
||||
prefs.setString('enrollmentEmpCodeString', enrollmentEmpCodeString);
|
||||
enrollmentEmpPrimaryId = decodedToken['id'];
|
||||
prefs.setString('enrollmentEmpPrimaryId', enrollmentEmpPrimaryId);
|
||||
enrollmentGpaEmpName = decodedToken['name'].toString();
|
||||
prefs.setString('enrollmentGpaEmpName', enrollmentGpaEmpName);
|
||||
enrollmentClient_id = decodedToken['client_id'];
|
||||
prefs.setString('enrollmentClient_id', enrollmentClient_id);
|
||||
enrollmentHrId = decodedToken['id'];
|
||||
prefs.setString('enrollmentHrId', enrollmentHrId);
|
||||
enrollmentAllowed_modules = decodedToken['allowed_modules'];
|
||||
prefs.setString(
|
||||
'enrollmentAllowed_modules', jsonEncode(enrollmentAllowed_modules));
|
||||
// enrollmentEmp_status = decodedToken['emp_status'];
|
||||
// prefs.setString('enrollmentEmp_status', enrollmentEmp_status);
|
||||
}
|
||||
|
||||
getClientLogoAndDetails(empClientId, empClientBranchId, enrollmentClient_id,
|
||||
enrollmentEmpClientBranchId, post['data']);
|
||||
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
final _postToken = prefs.getString('_postToken');
|
||||
|
||||
if (_postToken != null && _postToken.isNotEmpty) {
|
||||
ToastHelper.showSuccessToast(context, 'Successfully Logged In');
|
||||
// if (emp_status == 'enrolled' || emp_status == 'active') {
|
||||
Navigator.pushReplacementNamed(context, 'policies');
|
||||
// } else {
|
||||
// Navigator.pushReplacementNamed(context, 'empDetails');
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
void enrollmentSuccessData(data) async {
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
prefs.setString('enrollToken', data['data']);
|
||||
|
||||
// Decode the JWT token received from the API response
|
||||
Map<String, dynamic>? decodedToken = Jwt.parseJwt(data['data']);
|
||||
logDebug('enrolldecodedToken : $decodedToken');
|
||||
enrollmentEmpClientBranchId = decodedToken['ref_id'];
|
||||
prefs.setString('enrollmentEmpClientBranchId', enrollmentEmpClientBranchId);
|
||||
enrollmentEmpCodeString = decodedToken['emp_code'].toString();
|
||||
prefs.setString('enrollmentEmpCodeString', enrollmentEmpCodeString);
|
||||
enrollmentEmpPrimaryId = decodedToken['id'];
|
||||
prefs.setString('enrollmentEmpPrimaryId', enrollmentEmpPrimaryId);
|
||||
enrollmentGpaEmpName = decodedToken['name'].toString();
|
||||
prefs.setString('enrollmentGpaEmpName', enrollmentGpaEmpName);
|
||||
enrollmentClient_id = decodedToken['client_id'];
|
||||
prefs.setString('enrollmentClient_id', enrollmentClient_id);
|
||||
enrollmentHrId = decodedToken['id'];
|
||||
logDebug("enrollmentHrId- $enrollmentHrId");
|
||||
prefs.setString('enrollmentHrId', enrollmentHrId);
|
||||
enrollmentAllowed_modules = decodedToken['allowed_modules'];
|
||||
prefs.setString(
|
||||
'enrollmentAllowed_modules', jsonEncode(enrollmentAllowed_modules));
|
||||
// enrollmentEmp_status = decodedToken['emp_status'];
|
||||
// prefs.setString('enrollmentEmp_status', enrollmentEmp_status);
|
||||
getClientLogoAndDetails(empClientId, empClientBranchId, enrollmentClient_id,
|
||||
enrollmentEmpClientBranchId, data['data']);
|
||||
logDebug('Successfully Login');
|
||||
|
||||
// Redirect to another page
|
||||
final enrollToken = prefs.getString('enrollToken');
|
||||
|
||||
if (enrollToken != null && enrollToken.isNotEmpty) {
|
||||
ToastHelper.showSuccessToast(context, 'Successfully Logged In');
|
||||
// if (emp_status == 'enrolled' || emp_status == 'active') {
|
||||
// Navigator.pushReplacementNamed(context, 'home');
|
||||
// } else {
|
||||
Navigator.pushReplacementNamed(context, 'policies');
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
void _resendOTP() {
|
||||
setState(() {
|
||||
_secondsRemaining = 60;
|
||||
_isTimerRunning = true;
|
||||
});
|
||||
startTimer();
|
||||
verifyMobileAndEmailNumber();
|
||||
}
|
||||
|
||||
Future<void> verifyMobileAndEmailNumber() async {
|
||||
try {
|
||||
// var enteredMobileNumber = mobileController.text;
|
||||
final Map<String, dynamic> payload = (widget.type == 'mobile')
|
||||
? {'mobile_number': widget.value}
|
||||
: {'email': widget.value};
|
||||
|
||||
String apiEndpoint = (widget.type == 'mobile')
|
||||
? Environment.apiUrl + 'verifyHrWithMobileNumber'
|
||||
: Environment.apiUrl + 'verifyHrWithEmail';
|
||||
|
||||
final response = await http.post(
|
||||
Uri.parse(apiEndpoint),
|
||||
body: json.encode(payload),
|
||||
headers: {
|
||||
'APP-SIGNATURE':
|
||||
'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
|
||||
HttpHeaders.contentTypeHeader: 'application/json',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
Map<String, dynamic> data = json.decode(response.body);
|
||||
bool userVerification = data['data']['user_verification'];
|
||||
String message = data['data']['message'];
|
||||
if (userVerification) {
|
||||
ToastHelper.showSuccessToast(
|
||||
context, 'Verification code sent to ${widget.value}');
|
||||
} else {
|
||||
ToastHelper.showErrorToast(context, message);
|
||||
logDebug('Invalid mobile number');
|
||||
}
|
||||
} else if (response.statusCode == 403) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
Map<String, dynamic> data = json.decode(response.body);
|
||||
final message = data['message'];
|
||||
ToastHelper.showErrorToast(context, message);
|
||||
} else if (response.statusCode == 451) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
Map<String, dynamic> data = json.decode(response.body);
|
||||
final message = data['error']['message'];
|
||||
ToastHelper.showErrorToast(context, message);
|
||||
} else {
|
||||
ToastHelper.showErrorToast(context, 'Something went wrong');
|
||||
throw Exception('Failed to verify mobile number');
|
||||
}
|
||||
} catch (e) {
|
||||
ToastHelper.showErrorToast(context, 'Something went wrong');
|
||||
logDebug('Error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> getClientLogoAndDetails(empClientId, empClientBranchId,
|
||||
enrollmentClientId, enrollmentClientBranchId, token) async {
|
||||
var url = Uri.parse(Environment.apiUrl +
|
||||
'getClientDetails?post_client_id=$empClientId&post_branch_id=$empClientBranchId&pre_client_id=$enrollmentClientId&pre_branch_id=$enrollmentClientBranchId');
|
||||
try {
|
||||
var response = await http.get(
|
||||
url,
|
||||
headers: {
|
||||
'APP-SIGNATURE':
|
||||
'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
|
||||
'Authorization':
|
||||
'Bearer $token', // Add token to the Authorization header
|
||||
},
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
// logDebug('response.statusCode == 200');
|
||||
Map<String, dynamic> data = json.decode(response.body);
|
||||
// logDebug(data);
|
||||
|
||||
if (data.containsKey('data')) {
|
||||
dynamic clientDetails = data['data'];
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
prefs.setString('clientLogo', clientDetails['client']['client_logo']);
|
||||
prefs.setString('clientName', clientDetails['client']['client_name']);
|
||||
setState(() {
|
||||
// dynamic clientDetails = data['data'];
|
||||
// logDebug(clientDetails);
|
||||
clientName = clientDetails['client']['client_name'];
|
||||
logDebug(clientName);
|
||||
clientLogo = clientDetails['client']['client_logo'];
|
||||
logDebug(clientLogo);
|
||||
});
|
||||
} else {
|
||||
// Handle other status messages if needed
|
||||
// ToastHelper.showErrorToast(
|
||||
// context, 'API request failed with status: ${data['status']}');
|
||||
logDebug('API request failed with status: ${data['status']}');
|
||||
}
|
||||
} else {
|
||||
// Handle other status codes
|
||||
// ToastHelper.showErrorToast(
|
||||
// context, 'Request failed with status: ${response.statusCode}');
|
||||
logDebug('Request failed with status: ${response.statusCode}');
|
||||
}
|
||||
} catch (e) {
|
||||
// Handle exceptions
|
||||
logDebug('Exception occurred: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Size _size = MediaQuery.of(context).size;
|
||||
final defaultPinTheme = PinTheme(
|
||||
width: 56,
|
||||
height: 56,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 20,
|
||||
color: Color.fromRGBO(30, 60, 87, 1),
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Color.fromRGBO(234, 239, 243, 1)),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
);
|
||||
|
||||
final focusedPinTheme = defaultPinTheme.copyDecorationWith(
|
||||
border: Border.all(color: Color.fromRGBO(114, 178, 238, 1)),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
);
|
||||
|
||||
final submittedPinTheme = defaultPinTheme.copyWith(
|
||||
decoration: defaultPinTheme.decoration?.copyWith(
|
||||
color: Color.fromRGBO(234, 239, 243, 1),
|
||||
),
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
width: double.infinity,
|
||||
height: _size.height,
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topRight,
|
||||
end: Alignment.bottomLeft,
|
||||
colors: [
|
||||
Color(0xFF00B6AC),
|
||||
Color(0xFF83E0DE),
|
||||
Color(0xFF01B4A8),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: double.infinity, // ✅ fixed web width
|
||||
height: _size.height, // ✅ fixed web height (IMPORTANT)
|
||||
margin: const EdgeInsets.all(60),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.all(Radius.circular(40)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// ================= LEFT IMAGE =================
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(40),
|
||||
bottomLeft: Radius.circular(40),
|
||||
),
|
||||
child: Image.asset(
|
||||
'assets/hrLogin.png',
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// ================= RIGHT FORM =================
|
||||
Expanded(
|
||||
flex: 7,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/Nhance-Logo-Final 1.png',
|
||||
width: 300,
|
||||
height: 100,
|
||||
),
|
||||
SizedBox(height: 15),
|
||||
Container(
|
||||
margin: Responsive.isDesktop(context)
|
||||
? EdgeInsets.symmetric(horizontal: 150)
|
||||
: EdgeInsets.symmetric(horizontal: 0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"Welcome to Nhance",
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 15),
|
||||
Container(
|
||||
margin: Responsive.isDesktop(context)
|
||||
? EdgeInsets.symmetric(horizontal: 150)
|
||||
: EdgeInsets.symmetric(horizontal: 0),
|
||||
child: RichText(
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
text:
|
||||
"Please enter the one-time usage code sent to your ",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
height: 1.5,
|
||||
color: Color(0xFF000000)),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: widget.type == 'mobile'
|
||||
? 'mobile number ${widget.value}'
|
||||
: 'Email Id ${widget.value}',
|
||||
),
|
||||
TextSpan(
|
||||
text: widget.type == 'mobile'
|
||||
? ' (Change Mobile)'
|
||||
: ' (Change Email)',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Color(
|
||||
0xFFE26728)), // Change color as desired
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
// Navigate to the page where the user can change the phone number
|
||||
Navigator.pushNamed(
|
||||
context, 'hrLogin');
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 15),
|
||||
Container(
|
||||
margin: Responsive.isDesktop(context)
|
||||
? EdgeInsets.symmetric(horizontal: 150)
|
||||
: EdgeInsets.symmetric(horizontal: 0),
|
||||
child: Pinput(
|
||||
length: 6,
|
||||
// defaultPinTheme: defaultPinTheme,
|
||||
// focusedPinTheme: focusedPinTheme,
|
||||
// submittedPinTheme: submittedPinTheme,
|
||||
showCursor: true,
|
||||
controller: _otpController,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 15),
|
||||
Container(
|
||||
margin: Responsive.isDesktop(context)
|
||||
? EdgeInsets.symmetric(horizontal: 150)
|
||||
: EdgeInsets.symmetric(horizontal: 0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment
|
||||
.end, // Align text to the right
|
||||
children: [
|
||||
_isTimerRunning
|
||||
? Text(
|
||||
"Resend OTP in $_secondsRemaining seconds",
|
||||
style: GoogleFonts.poppins(
|
||||
color: Colors.black),
|
||||
)
|
||||
: InkWell(
|
||||
onTap: () {
|
||||
_resendOTP();
|
||||
},
|
||||
child: Text(
|
||||
"Resend OTP",
|
||||
style: GoogleFonts.poppins(
|
||||
color: Colors.blue),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 15),
|
||||
Container(
|
||||
margin: Responsive.isDesktop(context)
|
||||
? EdgeInsets.symmetric(horizontal: 150)
|
||||
: EdgeInsets.symmetric(horizontal: 0),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: 45,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Color(0xFF00989E),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
_formKey.currentState!
|
||||
.save(); // Save form fields before calling verifyOTP
|
||||
verifyOTP(_otpController.text);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
"Submit",
|
||||
style: GoogleFonts.poppins(
|
||||
color: Color(0xFFFFFFFF)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 15),
|
||||
Container(
|
||||
alignment: Alignment.bottomCenter,
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
child: RichText(
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
text: 'By continuing, you agree with our ',
|
||||
style: GoogleFonts.poppins(
|
||||
color: Colors.black,
|
||||
fontSize: 9,
|
||||
),
|
||||
children: <TextSpan>[
|
||||
TextSpan(
|
||||
text: 'privacy policy ',
|
||||
style: GoogleFonts.poppins(
|
||||
color: Color(0xFF00989E),
|
||||
fontSize: 9,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: 'and ',
|
||||
style: GoogleFonts.poppins(
|
||||
color: Colors.black,
|
||||
fontSize: 9,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: 'terms of use',
|
||||
style: GoogleFonts.poppins(
|
||||
color: Color(0xFF00989E),
|
||||
fontSize: 9,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)));
|
||||
}
|
||||
}
|
||||
2453
lib/empDetails.dart
2952
lib/empReview.dart
2145
lib/home.dart
1528
lib/hrHome.dart
663
lib/hrLogin.dart
@ -1,663 +0,0 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:nhancepolicy/customAppBar/toastHelper.dart';
|
||||
import 'package:nhancepolicy/hrVerify.dart';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:nhancepolicy/responsive.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:flutter_animated_button/flutter_animated_button.dart';
|
||||
import 'package:nhancepolicy/service/token_storage_service.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'config/environment.dart';
|
||||
import 'email_verify.dart';
|
||||
import 'package:nhancepolicy/logger.dart';
|
||||
|
||||
class MyHrLogin extends StatefulWidget {
|
||||
const MyHrLogin({Key? key});
|
||||
|
||||
@override
|
||||
State<MyHrLogin> createState() => _MyPhoneState();
|
||||
}
|
||||
|
||||
class _MyPhoneState extends State<MyHrLogin> {
|
||||
final TextEditingController emailMobileController = TextEditingController();
|
||||
// final TextEditingController emailController = TextEditingController();
|
||||
// TextEditingController countryController = TextEditingController();
|
||||
// TextEditingController mobileController = TextEditingController();
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
bool _isCheckingToken = false;
|
||||
dynamic empMobileNo;
|
||||
dynamic empEmailid;
|
||||
late String _verificationId;
|
||||
int? _resendToken;
|
||||
bool _isLoading = false;
|
||||
bool isEmailFieldVisible = false;
|
||||
final tokenService = TokenStorageService();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
logDebug('vndbbcbdskbvkjs1');
|
||||
// countryController.text = "+91";
|
||||
super.initState();
|
||||
clearLocalStorageWhenStarts('initState');
|
||||
}
|
||||
|
||||
clearLocalStorageWhenStarts(fromData) async {
|
||||
logDebug(fromData);
|
||||
logDebug('Clearing secure storage on app start');
|
||||
|
||||
await tokenService.clearAll();
|
||||
|
||||
logDebug('Secure Storage Cleared');
|
||||
}
|
||||
|
||||
void toggleField() {
|
||||
setState(() {
|
||||
isEmailFieldVisible = !isEmailFieldVisible;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> verifyMobileAndEmailNumber() async {
|
||||
await tokenService.clearAll();
|
||||
logDebug('verifyMobileAndEmailNumber :- Local Storage Clear');
|
||||
try {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
String input = emailMobileController.text.trim();
|
||||
|
||||
bool isEmail = RegExp(r'^[^@]+@[^@]+\.[^@]+$').hasMatch(input);
|
||||
bool isMobile = RegExp(r'^[0-9]{10}$').hasMatch(input);
|
||||
|
||||
if (isEmail) {
|
||||
setState(() {
|
||||
isEmailFieldVisible = true;
|
||||
});
|
||||
logDebug("User entered Email: $input");
|
||||
} else if (isMobile) {
|
||||
isEmailFieldVisible = false;
|
||||
logDebug("User entered Mobile: $input");
|
||||
}
|
||||
|
||||
// Determine the API and the payload based on the visible field
|
||||
String apiEndpoint = isEmailFieldVisible
|
||||
? Environment.apiUrl + 'verifyHrWithEmail'
|
||||
: Environment.apiUrl + 'verifyHrWithMobileNumber';
|
||||
|
||||
Map<String, dynamic> payload = isEmailFieldVisible
|
||||
? {'email': emailMobileController.text}
|
||||
: {'mobile_number': emailMobileController.text};
|
||||
|
||||
// var enteredMobileNumber = mobileController.text;
|
||||
final response = await http.post(
|
||||
Uri.parse(apiEndpoint),
|
||||
body: json.encode(payload),
|
||||
headers: {
|
||||
'APP-SIGNATURE':
|
||||
'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
|
||||
HttpHeaders.contentTypeHeader: 'application/json',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
Map<String, dynamic> data = json.decode(response.body);
|
||||
bool userVerification = data['data']['user_verification'];
|
||||
String message = data['data']['message'];
|
||||
if (userVerification) {
|
||||
if (isEmailFieldVisible) {
|
||||
logDebug('isEmailFieldVisible $isEmailFieldVisible');
|
||||
await tokenService.writeValue(
|
||||
'empEmail',
|
||||
emailMobileController.text,
|
||||
);
|
||||
|
||||
// logDebug('${emailMobileController.text}');
|
||||
// final message = 'Verification code sent to ${emailMobileController.text}';
|
||||
ToastHelper.showSuccessToast(context,
|
||||
'Verification code sent to ${emailMobileController.text}');
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => MyEmailVerify(
|
||||
type: 'email',
|
||||
value: emailMobileController.text
|
||||
.trim(), // Pass the phone number
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// final message = 'Verification code sent to ${emailMobileController.text}';
|
||||
ToastHelper.showSuccessToast(context,
|
||||
'Verification code sent to ${emailMobileController.text}');
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => MyEmailVerify(
|
||||
type: 'mobile',
|
||||
value: emailMobileController.text
|
||||
.trim(), // Pass the phone number
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ToastHelper.showSuccessToast(context, message);
|
||||
} else {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
ToastHelper.showErrorToast(context, message);
|
||||
logDebug('Invalid mobile number');
|
||||
}
|
||||
} else if (response.statusCode == 429) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
Map<String, dynamic> data = json.decode(response.body);
|
||||
if (data.containsKey('error')) {
|
||||
final message = data['error']['message'];
|
||||
ToastHelper.showErrorToast(context, message);
|
||||
} else {
|
||||
final message = data['message'];
|
||||
ToastHelper.showErrorToast(context, message);
|
||||
}
|
||||
} else if (response.statusCode == 403) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
Map<String, dynamic> data = json.decode(response.body);
|
||||
if (data.containsKey('error')) {
|
||||
final message = data['error']['message'];
|
||||
ToastHelper.showErrorToast(context, message);
|
||||
} else {
|
||||
final message = data['message'];
|
||||
ToastHelper.showErrorToast(context, message);
|
||||
}
|
||||
} else if (response.statusCode == 451) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
Map<String, dynamic> data = json.decode(response.body);
|
||||
if (data.containsKey('error')) {
|
||||
final message = data['error']['message'];
|
||||
ToastHelper.showErrorToast(context, message);
|
||||
} else {
|
||||
final message = data['message'];
|
||||
ToastHelper.showErrorToast(context, message);
|
||||
}
|
||||
} else {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
Map<String, dynamic> data = json.decode(response.body);
|
||||
final message = data['message'];
|
||||
ToastHelper.showErrorToast(context, message);
|
||||
throw Exception('Failed to verify mobile number');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
ToastHelper.showErrorToast(context, 'Something went wrong');
|
||||
logDebug('Error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Future<void> _verifyPhoneNumber() async {
|
||||
// var enteredMobileNumber = mobileController.text;
|
||||
// var countryCode = countryController.text;
|
||||
// logDebug('${countryCode + enteredMobileNumber}');
|
||||
// await _auth.verifyPhoneNumber(
|
||||
// phoneNumber: '${countryCode + enteredMobileNumber}',
|
||||
// timeout: const Duration(seconds: 60),
|
||||
// verificationCompleted: (PhoneAuthCredential credential) async {
|
||||
// await _auth.signInWithCredential(credential);
|
||||
// // ToastHelper.showSuccessToast(context, 'Verified Successfully!');
|
||||
// // setState(() {
|
||||
// // _isLoading = false;
|
||||
// // });
|
||||
// },
|
||||
// verificationFailed: (FirebaseAuthException e) {
|
||||
// logDebug('Verification Failed: ${e.code} - ${e.message}');
|
||||
// String errorMessage;
|
||||
// if (e.code == 'invalid-app-credential') {
|
||||
// errorMessage = 'Invalid Credential. Please try again.';
|
||||
// } else if (e.code == 'invalid-phone-number') {
|
||||
// errorMessage = 'The provided phone number is not valid.';
|
||||
// } else if (e.code == 'too-many-requests') {
|
||||
// errorMessage = 'Too many requests. Try again later.';
|
||||
// } else {
|
||||
// errorMessage = 'Verification Failed: ${e.message}';
|
||||
// }
|
||||
// ScaffoldMessenger.of(context).showSnackBar(
|
||||
// SnackBar(
|
||||
// content: Text(errorMessage),
|
||||
// duration: Duration(seconds: 2),
|
||||
// ),
|
||||
// );
|
||||
//
|
||||
// ToastHelper.showSuccessToast(context, errorMessage);
|
||||
// setState(() {
|
||||
// _isLoading = false;
|
||||
// });
|
||||
// },
|
||||
// codeSent: (String verificationId, int? resendToken) async {
|
||||
// setState(() {
|
||||
// _verificationId = verificationId;
|
||||
// _resendToken = resendToken;
|
||||
// });
|
||||
//
|
||||
// // When getting the verificationId
|
||||
// SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
// prefs.setString('verificationId', _verificationId);
|
||||
//
|
||||
// ToastHelper.showSuccessToast(
|
||||
// context, 'Verification code sent to ${enteredMobileNumber}');
|
||||
//
|
||||
// Navigator.push(
|
||||
// context,
|
||||
// MaterialPageRoute(
|
||||
// builder: (context) => MyHrVerify(
|
||||
// verificationId: _verificationId,
|
||||
// mobileNumber: enteredMobileNumber,
|
||||
// resendToken: _resendToken,
|
||||
// onResendCode: _resendCode, // Pass the phone number
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// setState(() {
|
||||
// _isLoading = false;
|
||||
// });
|
||||
// },
|
||||
// codeAutoRetrievalTimeout: (String verificationId) async {
|
||||
// setState(() {
|
||||
// _verificationId = verificationId;
|
||||
// });
|
||||
// final prefs = await SharedPreferences.getInstance();
|
||||
// await prefs.setString('verificationId', verificationId);
|
||||
// ToastHelper.showSuccessToast(context, 'Code auto-retrieval timed out.');
|
||||
// setState(() {
|
||||
// _isLoading = false;
|
||||
// });
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// void _resendCode(String mobileNumber, int? resendToken) async {
|
||||
// await _auth.verifyPhoneNumber(
|
||||
// phoneNumber: '${countryController.text + mobileNumber}',
|
||||
// timeout: const Duration(seconds: 60),
|
||||
// forceResendingToken: resendToken,
|
||||
// verificationCompleted: (PhoneAuthCredential credential) async {
|
||||
// await _auth.signInWithCredential(credential);
|
||||
// },
|
||||
// verificationFailed: (FirebaseAuthException e) {
|
||||
// if (e.code == 'invalid-phone-number') {
|
||||
// logDebug('The provided phone number is not valid.');
|
||||
// }
|
||||
// },
|
||||
// codeSent: (String verificationId, int? resendToken) async {
|
||||
// final prefs = await SharedPreferences.getInstance();
|
||||
// await prefs.setString('verificationId', verificationId);
|
||||
// setState(() {
|
||||
// _verificationId = verificationId;
|
||||
// _resendToken = resendToken;
|
||||
// });
|
||||
// ToastHelper.showSuccessToast(
|
||||
// context, 'Verification code resent to ${mobileNumber}');
|
||||
// Navigator.push(
|
||||
// context,
|
||||
// MaterialPageRoute(
|
||||
// builder: (context) => MyHrVerify(
|
||||
// verificationId: _verificationId,
|
||||
// mobileNumber: mobileNumber,
|
||||
// resendToken: _resendToken,
|
||||
// onResendCode: _resendCode,
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// codeAutoRetrievalTimeout: (String verificationId) async {
|
||||
// final prefs = await SharedPreferences.getInstance();
|
||||
// await prefs.setString('verificationId', verificationId);
|
||||
// setState(() {
|
||||
// _verificationId = verificationId;
|
||||
// });
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Size _size = MediaQuery.of(context).size;
|
||||
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
width: double.infinity,
|
||||
height: _size.height,
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topRight,
|
||||
end: Alignment.bottomLeft,
|
||||
colors: [
|
||||
Color(0xFF00B6AC),
|
||||
Color(0xFF83E0DE),
|
||||
Color(0xFF01B4A8),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: double.infinity, // ✅ fixed web width
|
||||
height: _size.height, // ✅ fixed web height (IMPORTANT)
|
||||
margin: const EdgeInsets.all(60),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.all(Radius.circular(40)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// ================= LEFT IMAGE =================
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(40),
|
||||
bottomLeft: Radius.circular(40),
|
||||
),
|
||||
child: Image.asset(
|
||||
'assets/hrLogin.png',
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// ================= RIGHT FORM =================
|
||||
Expanded(
|
||||
flex: 7,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/Nhance-Logo-Final 1.png',
|
||||
width: 300,
|
||||
height: 100,
|
||||
),
|
||||
SizedBox(height: 25),
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 150),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Login with your ${isEmailFieldVisible ? 'email' : 'mobile number'} and OTP to review and enroll for exciting health benefits for you and your family",
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
color: Color(0xFF000000)),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Container(
|
||||
height: 55,
|
||||
margin:
|
||||
const EdgeInsets.symmetric(horizontal: 150),
|
||||
decoration: BoxDecoration(
|
||||
border:
|
||||
Border.all(width: 1, color: Colors.grey),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: TextFormField(
|
||||
controller: emailMobileController,
|
||||
keyboardType: TextInputType.text,
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: "Email / Mobile Number ",
|
||||
contentPadding:
|
||||
EdgeInsets.symmetric(horizontal: 10),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null ||
|
||||
value.trim().isEmpty) {
|
||||
return "Please enter email or mobile number";
|
||||
}
|
||||
|
||||
String input = value.trim();
|
||||
|
||||
// ❌ Reject all spaces
|
||||
if (input.contains(' ')) {
|
||||
return "No spaces allowed";
|
||||
}
|
||||
|
||||
final emailRegex =
|
||||
RegExp(r'^[^@]+@[^@]+\.[^@]+$');
|
||||
final mobileRegex =
|
||||
RegExp(r'^[0-9]{10}$');
|
||||
|
||||
bool isEmailFormat =
|
||||
emailRegex.hasMatch(input);
|
||||
bool isMobileFormat =
|
||||
mobileRegex.hasMatch(input);
|
||||
|
||||
// ---------------------------
|
||||
// 🛑 MOBILE VALIDATION
|
||||
// ---------------------------
|
||||
if (RegExp(r'^[0-9]+$').hasMatch(input)) {
|
||||
if (input.length != 10) {
|
||||
return "Mobile number must be exactly 10 digits";
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// 🛑 EMAIL VALIDATION
|
||||
// ---------------------------
|
||||
|
||||
// Reject anything that has '@' but is NOT a valid email format
|
||||
if (input.contains('@') &&
|
||||
!isEmailFormat) {
|
||||
return "Enter a valid email address";
|
||||
}
|
||||
|
||||
// Reject email with extra digits at the end
|
||||
if (input.contains('@') &&
|
||||
RegExp(r'\d+$').hasMatch(input)) {
|
||||
return "Email cannot contain extra numbers";
|
||||
}
|
||||
|
||||
// Reject email+mobile combination
|
||||
if (input.contains('@') &&
|
||||
RegExp(r'\d{10}$').hasMatch(input)) {
|
||||
return "Enter only email OR mobile number";
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// 🛑 MIXED CONTENT (letters + digits but NOT email)
|
||||
// ---------------------------
|
||||
bool hasLetters =
|
||||
RegExp(r'[A-Za-z]').hasMatch(input);
|
||||
bool hasDigits =
|
||||
RegExp(r'[0-9]').hasMatch(input);
|
||||
|
||||
if ((hasLetters && hasDigits) &&
|
||||
!input.contains('@')) {
|
||||
return "Enter only email OR 10-digit mobile number";
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// 🟢 FINAL CHECK
|
||||
// ---------------------------
|
||||
if (!isEmailFormat && !isMobileFormat) {
|
||||
return "Enter a valid email or 10-digit mobile number";
|
||||
}
|
||||
|
||||
return null;
|
||||
}),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 150),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: 45,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Color(0xFF00989E),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
onPressed: _isLoading
|
||||
? null
|
||||
: verifyMobileAndEmailNumber,
|
||||
child: _isLoading
|
||||
? const CircularProgressIndicator(
|
||||
valueColor:
|
||||
AlwaysStoppedAnimation<Color>(
|
||||
Color(0xFF00989E),
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
"Login with Email / Mobile OTP",
|
||||
style: GoogleFonts.poppins(
|
||||
color: const Color(0xFFFFFFFF),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
alignment: Alignment.bottomCenter,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 8),
|
||||
child: RichText(
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
text: 'By continuing, you agree with our ',
|
||||
style: GoogleFonts.poppins(
|
||||
color: Colors.black,
|
||||
fontSize: 9,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'privacy policy ',
|
||||
style: GoogleFonts.poppins(
|
||||
color: const Color(0xFFE26828),
|
||||
fontSize: 9,
|
||||
// decoration: TextDecoration.underline,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () async {
|
||||
final url = Uri.parse(
|
||||
'https://nhanceindia.in/privacy-policy/');
|
||||
if (await canLaunchUrl(url)) {
|
||||
await launchUrl(url,
|
||||
mode: LaunchMode
|
||||
.externalApplication);
|
||||
}
|
||||
},
|
||||
),
|
||||
TextSpan(
|
||||
text: 'and ',
|
||||
style: GoogleFonts.poppins(
|
||||
color: Colors.black,
|
||||
fontSize: 9,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: 'terms of use',
|
||||
style: GoogleFonts.poppins(
|
||||
color: const Color(0xFFE26828),
|
||||
fontSize: 9,
|
||||
// decoration: TextDecoration.underline,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () async {
|
||||
final url = Uri.parse(
|
||||
'https://nhanceindia.in/privacy-policy/');
|
||||
if (await canLaunchUrl(url)) {
|
||||
await launchUrl(url,
|
||||
mode: LaunchMode
|
||||
.externalApplication);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// Container(
|
||||
// width: double
|
||||
// .infinity, // Make the footer full width
|
||||
// child: Container(
|
||||
// alignment: Alignment.bottomCenter,
|
||||
// padding:
|
||||
// EdgeInsets.symmetric(vertical: 8),
|
||||
// child: RichText(
|
||||
// textAlign: TextAlign.center,
|
||||
// text: TextSpan(
|
||||
// text:
|
||||
// 'By continuing, you agree with our ',
|
||||
// style: GoogleFonts.poppins(
|
||||
// color: Colors.black,
|
||||
// fontSize: 9,
|
||||
// ),
|
||||
// children: <TextSpan>[
|
||||
// TextSpan(
|
||||
// text: 'privacy policy ',
|
||||
// style: GoogleFonts.poppins(
|
||||
// color: Color(0xFFE26828),
|
||||
// fontSize: 9,
|
||||
// ),
|
||||
// ),
|
||||
// TextSpan(
|
||||
// text: 'and ',
|
||||
// style: GoogleFonts.poppins(
|
||||
// color: Colors.black,
|
||||
// fontSize: 9,
|
||||
// ),
|
||||
// ),
|
||||
// TextSpan(
|
||||
// text: 'terms of use',
|
||||
// style: GoogleFonts.poppins(
|
||||
// color: Color(0xFFE26828),
|
||||
// fontSize: 9,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)));
|
||||
}
|
||||
}
|
||||
@ -1,930 +0,0 @@
|
||||
// import 'package:firebase_auth/firebase_auth.dart';
|
||||
// import 'package:flutter/material.dart';
|
||||
// import 'package:google_fonts/google_fonts.dart';
|
||||
// import 'package:nhancepolicy/customAppBar/toastHelper.dart';
|
||||
// import 'package:nhancepolicy/models/environment.dart';
|
||||
// import 'package:nhancepolicy/service/api_service.dart';
|
||||
// import 'package:nhancepolicy/service/token_storage_service.dart';
|
||||
// import 'package:pinput/pinput.dart';
|
||||
// import 'dart:async';
|
||||
// import 'package:flutter/gestures.dart';
|
||||
// import 'package:http/http.dart' as http;
|
||||
// import 'dart:convert';
|
||||
// import 'dart:io';
|
||||
// import 'package:shared_preferences/shared_preferences.dart';
|
||||
// import 'package:nhancepolicy/responsive.dart';
|
||||
// import 'package:jwt_decode/jwt_decode.dart';
|
||||
//
|
||||
// import 'branch/branch_selection_page.dart';
|
||||
//
|
||||
// class MyHrVerify extends StatefulWidget {
|
||||
// final String verificationId;
|
||||
// final String mobileNumber;
|
||||
// final int? resendToken;
|
||||
// final Function(String, int?) onResendCode;
|
||||
//
|
||||
// const MyHrVerify({
|
||||
// Key? key,
|
||||
// required this.verificationId,
|
||||
// required this.mobileNumber,
|
||||
// required this.resendToken,
|
||||
// required this.onResendCode,
|
||||
// }) : super(key: key);
|
||||
//
|
||||
// @override
|
||||
// State<MyHrVerify> createState() => _MyVerifyState();
|
||||
// }
|
||||
//
|
||||
// class _MyVerifyState extends State<MyHrVerify> {
|
||||
// TextEditingController countryController = TextEditingController();
|
||||
// late ApiService apiService;
|
||||
// TextEditingController _otpController = TextEditingController();
|
||||
// final _formKey = GlobalKey<FormState>();
|
||||
// late Timer _timer;
|
||||
// int _secondsRemaining = 60;
|
||||
// bool _isTimerRunning = false;
|
||||
//
|
||||
// dynamic enrollmentEmpClientBranchId;
|
||||
// dynamic enrollmentEmpCodeString;
|
||||
// dynamic enrollmentEmpPrimaryId;
|
||||
// dynamic enrollmentGpaEmpName;
|
||||
// dynamic enrollmentClient_id;
|
||||
// dynamic enrollmentHrId;
|
||||
// List<dynamic> enrollmentAllowed_modules = [];
|
||||
// dynamic enrollmentEmp_status;
|
||||
//
|
||||
// dynamic empClientBranchId;
|
||||
// dynamic empCodeString;
|
||||
// dynamic empPrimaryId;
|
||||
// dynamic gpaEmpName;
|
||||
// dynamic client_id;
|
||||
// dynamic empHrId;
|
||||
// List<dynamic> empAllowed_modules = [];
|
||||
// dynamic emp_status;
|
||||
//
|
||||
// dynamic _token;
|
||||
// dynamic _postToken;
|
||||
// dynamic clientName;
|
||||
// dynamic clientLogo;
|
||||
//
|
||||
// dynamic fCMToken;
|
||||
// late String verificationId;
|
||||
// late String mobileNumber;
|
||||
// final FirebaseAuth _auth = FirebaseAuth.instance;
|
||||
// bool _isLoading = false;
|
||||
// dynamic empMobileNo;
|
||||
// dynamic empEmailid;
|
||||
//
|
||||
// late String _verificationId;
|
||||
//
|
||||
// @override
|
||||
// void initState() {
|
||||
// super.initState();
|
||||
// countryController.text = "+91";
|
||||
// apiService = ApiService(context);
|
||||
// // checkTokenAvailability();
|
||||
// verificationId = widget.verificationId;
|
||||
// mobileNumber = widget.mobileNumber;
|
||||
// logDebug('Received verificationId: $verificationId');
|
||||
// logDebug('Received mobileNumber: $mobileNumber');
|
||||
// if (verificationId.isEmpty) {
|
||||
// // Handle the case where verificationId is not provided
|
||||
// Navigator.pop(context);
|
||||
// }
|
||||
// startTimer();
|
||||
// // Equivalent to componentDidMount
|
||||
// _auth.authStateChanges().listen((User? user) {
|
||||
// if (user != null) {
|
||||
// // If user is already signed in, log them out
|
||||
// logOutFirebase();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// void logOutFirebase() async {
|
||||
// await _auth.signOut();
|
||||
// logDebug('User signed out');
|
||||
// }
|
||||
//
|
||||
// @override
|
||||
// void dispose() {
|
||||
// _timer.cancel();
|
||||
// super.dispose();
|
||||
// }
|
||||
//
|
||||
// void startTimer() {
|
||||
// _isTimerRunning = true;
|
||||
// _timer = Timer.periodic(Duration(seconds: 1), (Timer timer) {
|
||||
// setState(() {
|
||||
// if (_secondsRemaining > 0) {
|
||||
// _secondsRemaining--;
|
||||
// } else {
|
||||
// _isTimerRunning = false;
|
||||
// _timer.cancel();
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// void generateToken(bool otpVerifyStatus) async {
|
||||
// try {
|
||||
// final response = await http.post(
|
||||
// Uri.parse(Environment.apiUrl + 'getVerifiedHrData'),
|
||||
// body: json.encode(
|
||||
// {'mobile_no': mobileNumber, 'otp_verification': otpVerifyStatus}),
|
||||
// headers: {
|
||||
// HttpHeaders.contentTypeHeader: 'application/json',
|
||||
// },
|
||||
// );
|
||||
//
|
||||
// if (response.statusCode == 200) {
|
||||
// setState(() {
|
||||
// _isLoading = false;
|
||||
// });
|
||||
// Map<String, dynamic> data = json.decode(response.body);
|
||||
//
|
||||
// logDebug('Response data: $data');
|
||||
//
|
||||
// // Extract enrollment data
|
||||
// List<dynamic> preEnrollmentData = data['data'] ?? [];
|
||||
// Map<String, dynamic> postEnrollment = data['post_enrollment'] ?? {};
|
||||
// List<dynamic> postEnrollmentData = postEnrollment['data'] ?? [];
|
||||
//
|
||||
// // Save to global storage
|
||||
// final tokenStorage = TokenStorageService();
|
||||
// await tokenStorage.saveEnrollmentData(
|
||||
// preEnrollmentData,
|
||||
// postEnrollmentData,
|
||||
// );
|
||||
//
|
||||
// // Navigate to branch selection
|
||||
// Navigator.pushReplacement(
|
||||
// context,
|
||||
// MaterialPageRoute(
|
||||
// builder: (context) => BranchSelectionPage(),
|
||||
// ),
|
||||
// );
|
||||
//
|
||||
// // logDebug('data: $data');
|
||||
// // _token = data['data'];
|
||||
// // String status = data['status'];
|
||||
// //
|
||||
// // // Directly access the post_enrollment data
|
||||
// // Map<String, dynamic> post = data['post_enrollment'];
|
||||
// // logDebug('post: $post');
|
||||
// //
|
||||
// // _postToken = post['data'];
|
||||
// // String postStatus = post['status'];
|
||||
// //
|
||||
// // if (postStatus == 'success') {
|
||||
// // setState(() {
|
||||
// // _isLoading = false;
|
||||
// // });
|
||||
// // postSuccessData(post, data);
|
||||
// // } else if (status == 'success') {
|
||||
// // setState(() {
|
||||
// // _isLoading = false;
|
||||
// // });
|
||||
// // enrollmentSuccessData(data);
|
||||
// // } else {
|
||||
// // setState(() {
|
||||
// // _isLoading = false;
|
||||
// // });
|
||||
// // ToastHelper.showErrorToast(context, 'Invalid OTP. Please try again');
|
||||
// // // Show a Snackbar if the OTP is invalid
|
||||
// // logDebug('Invalid OTP. Please try again');
|
||||
// // }
|
||||
// } else {
|
||||
// setState(() {
|
||||
// _isLoading = false;
|
||||
// });
|
||||
// ToastHelper.showWarningToast(context, 'Something went wrong');
|
||||
// throw Exception('Failed to verify OTP');
|
||||
// }
|
||||
// } catch (e) {
|
||||
// setState(() {
|
||||
// _isLoading = false;
|
||||
// });
|
||||
// logDebug('Error: $e');
|
||||
// ToastHelper.showWarningToast(context, 'Something went wrong');
|
||||
// // Show a Snackbar if there's an error while verifying OTP
|
||||
// logDebug('Failed to verify OTP. Please try again.');
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// void postSuccessData(post, data) async {
|
||||
// String status = data['status'];
|
||||
// String postStatus = post['status'];
|
||||
//
|
||||
// if (postStatus == 'success') {
|
||||
// final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
// prefs.setString('_postToken', post['data']);
|
||||
//
|
||||
// // Decode the JWT token received from the API response
|
||||
// Map<String, dynamic>? decodedToken = Jwt.parseJwt(post['data']);
|
||||
// logDebug('postdecodedToken : $decodedToken');
|
||||
// empClientBranchId = decodedToken['ref_id'];
|
||||
// prefs.setString('empClientBranchId', empClientBranchId);
|
||||
// empCodeString = decodedToken['emp_code'].toString();
|
||||
// prefs.setString('empCode', empCodeString);
|
||||
// empPrimaryId = decodedToken['id'];
|
||||
// prefs.setString('empPrimaryId', empPrimaryId);
|
||||
// gpaEmpName = decodedToken['name'].toString();
|
||||
// prefs.setString('gpaEmpName', gpaEmpName);
|
||||
// client_id = decodedToken['client_id'];
|
||||
// prefs.setString('client_id', client_id);
|
||||
// empHrId = decodedToken['id'];
|
||||
// prefs.setString('empHrId', empHrId);
|
||||
// empAllowed_modules = decodedToken['allowed_modules'];
|
||||
// prefs.setString('empAllowed_modules', jsonEncode(empAllowed_modules));
|
||||
// emp_status = decodedToken['emp_status'];
|
||||
// prefs.setString('emp_status', emp_status);
|
||||
// }
|
||||
//
|
||||
// if (status == 'success') {
|
||||
// final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
// prefs.setString('enrollToken', data['data']);
|
||||
//
|
||||
// // Decode the JWT token received from the API response
|
||||
// Map<String, dynamic>? decodedToken = Jwt.parseJwt(data['data']);
|
||||
// logDebug('decodedToken : $decodedToken');
|
||||
// enrollmentEmpClientBranchId = decodedToken['ref_id'];
|
||||
// prefs.setString(
|
||||
// 'enrollmentEmpClientBranchId', enrollmentEmpClientBranchId);
|
||||
// enrollmentEmpCodeString = decodedToken['emp_code'].toString();
|
||||
// prefs.setString('enrollmentEmpCodeString', enrollmentEmpCodeString);
|
||||
// enrollmentEmpPrimaryId = decodedToken['id'];
|
||||
// prefs.setString('enrollmentEmpPrimaryId', enrollmentEmpPrimaryId);
|
||||
// enrollmentGpaEmpName = decodedToken['name'].toString();
|
||||
// prefs.setString('enrollmentGpaEmpName', enrollmentGpaEmpName);
|
||||
// enrollmentClient_id = decodedToken['client_id'];
|
||||
// prefs.setString('enrollmentClient_id', enrollmentClient_id);
|
||||
//
|
||||
// enrollmentHrId = decodedToken['id'];
|
||||
// prefs.setString('enrollmentHrId', enrollmentHrId);
|
||||
// enrollmentAllowed_modules = decodedToken['allowed_modules'];
|
||||
// prefs.setString(
|
||||
// 'enrollmentAllowed_modules', jsonEncode(enrollmentAllowed_modules));
|
||||
// // enrollmentEmp_status = decodedToken['emp_status'];
|
||||
// // prefs.setString('enrollmentEmp_status', enrollmentEmp_status);
|
||||
// }
|
||||
//
|
||||
// final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
// final _postToken = prefs.getString('_postToken');
|
||||
//
|
||||
// if (_postToken != null && _postToken.isNotEmpty) {
|
||||
// ToastHelper.showSuccessToast(context, 'Successfully Login');
|
||||
// // if (emp_status == 'enrolled' || emp_status == 'active') {
|
||||
// Navigator.pushReplacementNamed(context, 'hrDashboard');
|
||||
// // } else {
|
||||
// // Navigator.pushReplacementNamed(context, 'empDetails');
|
||||
// // }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// void enrollmentSuccessData(data) async {
|
||||
// final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
// prefs.setString('enrollToken', data['data']);
|
||||
//
|
||||
// // Decode the JWT token received from the API response
|
||||
// Map<String, dynamic>? decodedToken = Jwt.parseJwt(data['data']);
|
||||
// logDebug('enrolldecodedToken : $decodedToken');
|
||||
// enrollmentEmpClientBranchId = decodedToken['ref_id'];
|
||||
// prefs.setString('enrollmentEmpClientBranchId', enrollmentEmpClientBranchId);
|
||||
// enrollmentEmpCodeString = decodedToken['emp_code'].toString();
|
||||
// prefs.setString('enrollmentEmpCodeString', enrollmentEmpCodeString);
|
||||
// enrollmentEmpPrimaryId = decodedToken['id'];
|
||||
// prefs.setString('enrollmentEmpPrimaryId', enrollmentEmpPrimaryId);
|
||||
// enrollmentGpaEmpName = decodedToken['name'].toString();
|
||||
// prefs.setString('enrollmentGpaEmpName', enrollmentGpaEmpName);
|
||||
// enrollmentClient_id = decodedToken['client_id'];
|
||||
// prefs.setString('enrollmentClient_id', enrollmentClient_id);
|
||||
//
|
||||
// enrollmentHrId = decodedToken['id'];
|
||||
// prefs.setString('enrollmentHrId', enrollmentHrId);
|
||||
// enrollmentAllowed_modules = decodedToken['allowed_modules'];
|
||||
// prefs.setString(
|
||||
// 'enrollmentAllowed_modules', jsonEncode(enrollmentAllowed_modules));
|
||||
// // enrollmentEmp_status = decodedToken['emp_status'];
|
||||
// // prefs.setString('enrollmentEmp_status', enrollmentEmp_status);
|
||||
//
|
||||
// logDebug('Successfully Login');
|
||||
//
|
||||
// // Redirect to another page
|
||||
// final enrollToken = prefs.getString('enrollToken');
|
||||
//
|
||||
// if (enrollToken != null && enrollToken.isNotEmpty) {
|
||||
// ToastHelper.showSuccessToast(context, 'Successfully Login');
|
||||
// // if (emp_status == 'enrolled' || emp_status == 'active') {
|
||||
// // Navigator.pushReplacementNamed(context, 'home');
|
||||
// // } else {
|
||||
// Navigator.pushReplacementNamed(context, 'hrDashboard');
|
||||
// // }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// void verifyOTP(String otp) async {
|
||||
// try {
|
||||
// // setState(() {
|
||||
// // _isLoading = true;
|
||||
// // });
|
||||
// // Later, in verify screen:
|
||||
// SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
// dynamic verificationId = prefs.getString('verificationId');
|
||||
//
|
||||
// final credential = PhoneAuthProvider.credential(
|
||||
// verificationId: verificationId, // Make sure this is up-to-date
|
||||
// smsCode: otp,
|
||||
// );
|
||||
//
|
||||
// // final userCredential = await FirebaseAuth.instance.signInWithCredential(credential);
|
||||
// // await FirebaseAuth.instance.signInWithCredential(credential);
|
||||
//
|
||||
// // if (userCredential.user != null) {
|
||||
// bool otpVerifyStatus = true;
|
||||
// generateToken(otpVerifyStatus);
|
||||
// // } else {
|
||||
// // ToastHelper.showErrorToast(context, 'Invalid OTP. Please try again.');
|
||||
// // }
|
||||
//
|
||||
// // await FirebaseAuth.instance.signInWithCredential(credential).then((userCredential) async {
|
||||
// // if (userCredential.user != null) {
|
||||
// // bool otpVerifyStatus = true;
|
||||
// // generateToken(otpVerifyStatus);
|
||||
// // } else {
|
||||
// // setState(() {
|
||||
// // _isLoading = false;
|
||||
// // });
|
||||
// // ToastHelper.showErrorToast(context, 'Invalid OTP. Please try again');
|
||||
// // }
|
||||
// // });
|
||||
// } on FirebaseAuthException catch (e) {
|
||||
// if (e.code == 'code-expired') {
|
||||
// ScaffoldMessenger.of(context).showSnackBar(
|
||||
// SnackBar(
|
||||
// content: Text('OTP expired. Please request a new one.'),
|
||||
// duration: Duration(seconds: 2),
|
||||
// ),
|
||||
// );
|
||||
// ToastHelper.showErrorToast(
|
||||
// context, 'OTP expired. Please request a new one.');
|
||||
// } else if (e.code == 'invalid-verification-code') {
|
||||
// ScaffoldMessenger.of(context).showSnackBar(
|
||||
// SnackBar(
|
||||
// content: Text('Invalid OTP entered. Please try again.'),
|
||||
// duration: Duration(seconds: 2),
|
||||
// ),
|
||||
// );
|
||||
// ToastHelper.showErrorToast(
|
||||
// context, 'Invalid OTP entered. Please try again.');
|
||||
// } else if (e.code == 'session-expired') {
|
||||
// ScaffoldMessenger.of(context).showSnackBar(
|
||||
// SnackBar(
|
||||
// content: Text('Session expired. Try restarting the verification.'),
|
||||
// duration: Duration(seconds: 2),
|
||||
// ),
|
||||
// );
|
||||
// ToastHelper.showErrorToast(
|
||||
// context, 'Session expired. Try restarting the verification.');
|
||||
// } else {
|
||||
// ScaffoldMessenger.of(context).showSnackBar(
|
||||
// SnackBar(
|
||||
// content: Text('Error: ${e.message}'),
|
||||
// duration: Duration(seconds: 2),
|
||||
// ),
|
||||
// );
|
||||
// ToastHelper.showErrorToast(context, 'Error: ${e.message}');
|
||||
// }
|
||||
// } catch (e) {
|
||||
// // setState(() {
|
||||
// // _isLoading = false;
|
||||
// // });
|
||||
// logDebug('Error: $e');
|
||||
// ToastHelper.showErrorToast(
|
||||
// context, 'Failed to verify OTP. Please try again.');
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// void _resendOTP() {
|
||||
// setState(() {
|
||||
// _secondsRemaining = 60;
|
||||
// _isTimerRunning = true;
|
||||
// });
|
||||
// startTimer();
|
||||
// widget.onResendCode(widget.mobileNumber, widget.resendToken);
|
||||
// }
|
||||
//
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// // Retrieve the passed mobile number value
|
||||
// // final String mobileNumber =
|
||||
// // ModalRoute.of(context)!.settings.arguments as String;
|
||||
// Size _size = MediaQuery.of(context).size;
|
||||
// EdgeInsets marginInsets = EdgeInsets.zero;
|
||||
// if (Responsive.isDesktop(context)) {
|
||||
// marginInsets = const EdgeInsets.only(
|
||||
// left: 0,
|
||||
// right: 0,
|
||||
// bottom: 0,
|
||||
// top: 0,
|
||||
// );
|
||||
// } else if (Responsive.isMobile(context)) {
|
||||
// marginInsets = const EdgeInsets.only(
|
||||
// left: 25, // Example value for mobile
|
||||
// right: 25, // Example value for mobile
|
||||
// bottom: 0, // Example value for mobile
|
||||
// top: 0, // Example value for mobile
|
||||
// );
|
||||
// } else if (Responsive.isTablet(context)) {
|
||||
// marginInsets = const EdgeInsets.only(
|
||||
// left: 25, // Example value for mobile
|
||||
// right: 25, // Example value for mobile
|
||||
// bottom: 0, // Example value for mobile
|
||||
// top: 0, // Example value for mobile
|
||||
// );
|
||||
// }
|
||||
// final defaultPinTheme = PinTheme(
|
||||
// width: 56,
|
||||
// height: 56,
|
||||
// textStyle: TextStyle(
|
||||
// fontSize: 20,
|
||||
// color: Color.fromRGBO(30, 60, 87, 1),
|
||||
// fontWeight: FontWeight.w600,
|
||||
// ),
|
||||
// decoration: BoxDecoration(
|
||||
// border: Border.all(color: Color.fromRGBO(234, 239, 243, 1)),
|
||||
// borderRadius: BorderRadius.circular(20),
|
||||
// ),
|
||||
// );
|
||||
//
|
||||
// final focusedPinTheme = defaultPinTheme.copyDecorationWith(
|
||||
// border: Border.all(color: Color.fromRGBO(114, 178, 238, 1)),
|
||||
// borderRadius: BorderRadius.circular(8),
|
||||
// );
|
||||
//
|
||||
// final submittedPinTheme = defaultPinTheme.copyWith(
|
||||
// decoration: defaultPinTheme.decoration?.copyWith(
|
||||
// color: Color.fromRGBO(234, 239, 243, 1),
|
||||
// ),
|
||||
// );
|
||||
//
|
||||
// return Scaffold(
|
||||
// body: SingleChildScrollView(
|
||||
// keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
||||
// child: Container(
|
||||
// height: _size.height,
|
||||
// color: Colors.white,
|
||||
// child: Stack(
|
||||
// children: [
|
||||
// Visibility(
|
||||
// visible: _size.width <= 1100,
|
||||
// child: ClipRRect(
|
||||
// borderRadius: BorderRadius.only(
|
||||
// bottomLeft: Radius.circular(30),
|
||||
// bottomRight: Radius.circular(30),
|
||||
// ),
|
||||
// child: Container(
|
||||
// height: _size.height / 3,
|
||||
// width: double.infinity,
|
||||
// color: Color(0xFF00989E),
|
||||
// child: Stack(
|
||||
// children: [
|
||||
// Column(
|
||||
// children: [
|
||||
// SizedBox(
|
||||
// height: _size.height /
|
||||
// 6.4), // Adjust the spacing between the rows
|
||||
// Row(
|
||||
// mainAxisAlignment: MainAxisAlignment
|
||||
// .center, // Align to the center
|
||||
// children: [
|
||||
// Expanded(
|
||||
// flex: Responsive.isDesktop(context) ? 10 : 12,
|
||||
// child: Align(
|
||||
// alignment: Responsive.isDesktop(context)
|
||||
// ? Alignment.centerLeft
|
||||
// : Alignment.bottomCenter,
|
||||
// child: Image.asset(
|
||||
// _size.width <= 1100
|
||||
// ? 'assets/mobileViewLogo.png'
|
||||
// : 'assets/Nhance-Logo-Final 1.png',
|
||||
// width: 150,
|
||||
// height: 150,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// if (!Responsive.isMobile(context) &&
|
||||
// !Responsive.isTablet(context))
|
||||
// Expanded(
|
||||
// flex: 2,
|
||||
// child: MouseRegion(
|
||||
// cursor: SystemMouseCursors.click,
|
||||
// child: GestureDetector(
|
||||
// onTap: () {
|
||||
// // Add your navigation logic here
|
||||
// // For example, you can use Navigator.push to navigate to another page
|
||||
// Navigator.pushNamed(
|
||||
// context, 'hrLogin');
|
||||
// },
|
||||
// child: Row(
|
||||
// mainAxisAlignment: MainAxisAlignment
|
||||
// .end, // Align to the end (right)
|
||||
// children: [
|
||||
// Text(
|
||||
// 'HR Login',
|
||||
// style: TextStyle(
|
||||
// color: Color(
|
||||
// 0xFF000000), // Text color
|
||||
// // Add other text styles as needed
|
||||
// ),
|
||||
// ),
|
||||
// SizedBox(width: 5),
|
||||
// Icon(
|
||||
// Icons
|
||||
// .east, // Icon for customer login
|
||||
// color: Colors
|
||||
// .black, // Adjust color as needed
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// Container(
|
||||
// margin: marginInsets,
|
||||
// alignment: Alignment.bottomCenter,
|
||||
// child: SingleChildScrollView(
|
||||
// child: Form(
|
||||
// key: _formKey,
|
||||
// child: Column(
|
||||
// children: [
|
||||
// Row(
|
||||
// children: [
|
||||
// if (_size.width > 1100)
|
||||
// Expanded(
|
||||
// flex: _size.width < 1100 ? 6 : 12,
|
||||
// child: LayoutBuilder(
|
||||
// builder: (BuildContext context,
|
||||
// BoxConstraints constraints) {
|
||||
// if (constraints.maxWidth > 600) {
|
||||
// return Image.asset(
|
||||
// 'assets/hrLogin.jpg',
|
||||
// height: _size.height,
|
||||
// fit: BoxFit.cover,
|
||||
// );
|
||||
// } else {
|
||||
// return SizedBox();
|
||||
// }
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
// Expanded(
|
||||
// flex: _size.width < 1100 ? 6 : 12,
|
||||
// child: Container(
|
||||
// margin: _size.width > 1100
|
||||
// ? EdgeInsets.only(left: 20, right: 20)
|
||||
// : EdgeInsets.only(left: 0, right: 0),
|
||||
// child: Column(
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
// children: [
|
||||
// if (!Responsive.isMobile(context) &&
|
||||
// !Responsive.isTablet(context))
|
||||
// Row(
|
||||
// children: [
|
||||
// Expanded(
|
||||
// flex: 8,
|
||||
// child: Align(
|
||||
// alignment: Alignment.center, // Align to the start
|
||||
// child: _size.width <= 1100
|
||||
// ? Image.asset(
|
||||
// 'assets/Nhance-Logo-Final-mobile.png',
|
||||
// width: 150,
|
||||
// height: 70,
|
||||
// )
|
||||
// : _size.width > 1100
|
||||
// ? Image.asset(
|
||||
// 'assets/Nhance-Logo-Final 1.png',
|
||||
// width: 150,
|
||||
// height: 70,
|
||||
// )
|
||||
// : Image.asset(
|
||||
// 'assets/Nhance-Logo-Final 1.png',
|
||||
// width: 150,
|
||||
// height: 70,
|
||||
// ),
|
||||
// )),
|
||||
// ],
|
||||
// ),
|
||||
// SizedBox(height: 80),
|
||||
// Container(
|
||||
// margin: Responsive.isDesktop(context)
|
||||
// ? EdgeInsets.symmetric(
|
||||
// horizontal: 150)
|
||||
// : EdgeInsets.symmetric(horizontal: 0),
|
||||
// child: Row(
|
||||
// mainAxisAlignment:
|
||||
// MainAxisAlignment.center,
|
||||
// children: [
|
||||
// Text(
|
||||
// "Welcome to Nhance",
|
||||
// style: GoogleFonts.poppins(
|
||||
// fontSize: 16,
|
||||
// fontWeight: FontWeight.bold,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// SizedBox(height: 10),
|
||||
// Container(
|
||||
// margin: Responsive.isDesktop(context)
|
||||
// ? EdgeInsets.symmetric(
|
||||
// horizontal: 150)
|
||||
// : EdgeInsets.symmetric(horizontal: 0),
|
||||
// child: RichText(
|
||||
// textAlign: TextAlign.center,
|
||||
// text: TextSpan(
|
||||
// text:
|
||||
// "Please enter the one-time usage code sent to your mobile number $mobileNumber",
|
||||
// style: GoogleFonts.poppins(
|
||||
// fontSize: 12,
|
||||
// height: 1.5,
|
||||
// color: Color(0xFF000000)),
|
||||
// children: [
|
||||
// TextSpan(
|
||||
// text: " (Change Number)",
|
||||
// style: GoogleFonts.poppins(
|
||||
// fontSize: 12,
|
||||
// color: Color(
|
||||
// 0xFFE26728)), // Change color as desired
|
||||
// recognizer: TapGestureRecognizer()
|
||||
// ..onTap = () {
|
||||
// // Navigate to the page where the user can change the phone number
|
||||
// Navigator.pushNamed(
|
||||
// context, 'hrLogin');
|
||||
// },
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// SizedBox(height: 15),
|
||||
// Container(
|
||||
// margin: Responsive.isDesktop(context)
|
||||
// ? EdgeInsets.symmetric(
|
||||
// horizontal: 150)
|
||||
// : EdgeInsets.symmetric(horizontal: 0),
|
||||
// child: Pinput(
|
||||
// length: 6,
|
||||
// // defaultPinTheme: defaultPinTheme,
|
||||
// // focusedPinTheme: focusedPinTheme,
|
||||
// // submittedPinTheme: submittedPinTheme,
|
||||
// showCursor: true,
|
||||
// controller: _otpController,
|
||||
// ),
|
||||
// ),
|
||||
// SizedBox(height: 10),
|
||||
// Container(
|
||||
// margin: Responsive.isDesktop(context)
|
||||
// ? EdgeInsets.symmetric(
|
||||
// horizontal: 150)
|
||||
// : EdgeInsets.symmetric(horizontal: 0),
|
||||
// child: Row(
|
||||
// mainAxisAlignment: MainAxisAlignment
|
||||
// .end, // Align text to the right
|
||||
// children: [
|
||||
// _isTimerRunning
|
||||
// ? Text(
|
||||
// "Resend OTP in $_secondsRemaining seconds",
|
||||
// style: GoogleFonts.poppins(
|
||||
// color: Colors.black),
|
||||
// )
|
||||
// : InkWell(
|
||||
// onTap: () {
|
||||
// _resendOTP();
|
||||
// },
|
||||
// child: Text(
|
||||
// "Resend OTP",
|
||||
// style: GoogleFonts.poppins(
|
||||
// color: Colors.blue),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// SizedBox(height: 10),
|
||||
// Container(
|
||||
// margin: Responsive.isDesktop(context)
|
||||
// ? EdgeInsets.symmetric(
|
||||
// horizontal: 150)
|
||||
// : EdgeInsets.symmetric(horizontal: 0),
|
||||
// child: SizedBox(
|
||||
// width: double.infinity,
|
||||
// height: 45,
|
||||
// child: ElevatedButton(
|
||||
// style: ElevatedButton.styleFrom(
|
||||
// backgroundColor: Color(0xFF00989E),
|
||||
// shape: RoundedRectangleBorder(
|
||||
// borderRadius:
|
||||
// BorderRadius.circular(10),
|
||||
// ),
|
||||
// ),
|
||||
// onPressed: () {
|
||||
// if (_formKey.currentState!
|
||||
// .validate()) {
|
||||
// _formKey.currentState!
|
||||
// .save(); // Save form fields before calling verifyOTP
|
||||
// verifyOTP(_otpController.text);
|
||||
// }
|
||||
// },
|
||||
// child: Text(
|
||||
// "Submit",
|
||||
// style: GoogleFonts.poppins(
|
||||
// color: Color(0xFFFFFFFF)),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// SizedBox(
|
||||
// height: _size.width <= 1100 ? 0 : 0,
|
||||
// ),
|
||||
// // _size.width > 1100
|
||||
// // ? Container(
|
||||
// // margin: EdgeInsets.symmetric(
|
||||
// // horizontal: 150),
|
||||
// // child: Column(
|
||||
// // children: [
|
||||
// // SizedBox(height: 30),
|
||||
// // Text(
|
||||
// // "Benefits of Login",
|
||||
// // style: GoogleFonts.poppins(
|
||||
// // fontSize: 20,
|
||||
// // fontWeight: FontWeight.bold,
|
||||
// // ),
|
||||
// // ),
|
||||
// // SizedBox(height: 15),
|
||||
// // ],
|
||||
// // ))
|
||||
// // : SizedBox(),
|
||||
// // _size.width > 1100
|
||||
// // ? Container(
|
||||
// // margin: EdgeInsets.symmetric(
|
||||
// // horizontal: 150),
|
||||
// // child: Row(
|
||||
// // mainAxisAlignment:
|
||||
// // MainAxisAlignment.center,
|
||||
// // children: [
|
||||
// // Expanded(
|
||||
// // flex: 6,
|
||||
// // child: Container(
|
||||
// // padding:
|
||||
// // EdgeInsets.symmetric(
|
||||
// // vertical: 8),
|
||||
// // child: Row(
|
||||
// // mainAxisAlignment:
|
||||
// // MainAxisAlignment
|
||||
// // .center,
|
||||
// // children: [
|
||||
// // Expanded(
|
||||
// // child: Container(
|
||||
// // padding: EdgeInsets
|
||||
// // .symmetric(
|
||||
// // vertical:
|
||||
// // 12),
|
||||
// // decoration:
|
||||
// // BoxDecoration(
|
||||
// // border: Border(
|
||||
// // right:
|
||||
// // BorderSide(
|
||||
// // width: 1,
|
||||
// // color: Colors
|
||||
// // .black,
|
||||
// // ),
|
||||
// // ),
|
||||
// // ),
|
||||
// // child: Column(
|
||||
// // children: [
|
||||
// // Icon(
|
||||
// // Icons
|
||||
// // .policy,
|
||||
// // color: Color(
|
||||
// // 0xFFE26728)),
|
||||
// // SizedBox(
|
||||
// // height: 10),
|
||||
// // Text(
|
||||
// // "View Policy"),
|
||||
// // ],
|
||||
// // ),
|
||||
// // ),
|
||||
// // ),
|
||||
// // Expanded(
|
||||
// // child: Container(
|
||||
// // padding: EdgeInsets
|
||||
// // .symmetric(
|
||||
// // vertical:
|
||||
// // 12),
|
||||
// // child: Column(
|
||||
// // children: [
|
||||
// // Icon(Icons.edit,
|
||||
// // color: Color(
|
||||
// // 0xFFE26728)),
|
||||
// // SizedBox(
|
||||
// // height: 10),
|
||||
// // Text(
|
||||
// // "Manage Claims"),
|
||||
// // ],
|
||||
// // ),
|
||||
// // ),
|
||||
// // ),
|
||||
// // ],
|
||||
// // ),
|
||||
// // ),
|
||||
// // ),
|
||||
// // ],
|
||||
// // ),
|
||||
// // )
|
||||
// // : SizedBox(
|
||||
// // height:
|
||||
// // Responsive.isDesktop(context)
|
||||
// // ? _size.height * 0.1
|
||||
// // : _size.height * 0.2,
|
||||
// // ),
|
||||
// SizedBox(
|
||||
// height: Responsive.isDesktop(context)
|
||||
// ? _size.height * 0.3
|
||||
// : _size.height * 0.2,
|
||||
// ),
|
||||
// // SizedBox(
|
||||
// // height: _size.height * 0.1,
|
||||
// // ),
|
||||
// Align(
|
||||
// alignment: Alignment.bottomCenter,
|
||||
// child: Container(
|
||||
// width: double
|
||||
// .infinity, // Make the footer full width
|
||||
// child: Container(
|
||||
// alignment: Alignment.bottomCenter,
|
||||
// padding:
|
||||
// EdgeInsets.symmetric(vertical: 8),
|
||||
// child: RichText(
|
||||
// textAlign: TextAlign.center,
|
||||
// text: TextSpan(
|
||||
// text:
|
||||
// 'By continuing, you agree with our ',
|
||||
// style: GoogleFonts.poppins(
|
||||
// color: Colors.black,
|
||||
// fontSize: 9,
|
||||
// ),
|
||||
// children: <TextSpan>[
|
||||
// TextSpan(
|
||||
// text: 'privacy policy ',
|
||||
// style: GoogleFonts.poppins(
|
||||
// color: Color(0xFF00989E),
|
||||
// fontSize: 9,
|
||||
// ),
|
||||
// ),
|
||||
// TextSpan(
|
||||
// text: 'and ',
|
||||
// style: GoogleFonts.poppins(
|
||||
// color: Colors.black,
|
||||
// fontSize: 9,
|
||||
// ),
|
||||
// ),
|
||||
// TextSpan(
|
||||
// text: 'terms of use',
|
||||
// style: GoogleFonts.poppins(
|
||||
// color: Color(0xFF00989E),
|
||||
// fontSize: 9,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// )),
|
||||
// ));
|
||||
// }
|
||||
// }
|
||||
111
lib/logger.dart
@ -1,111 +0,0 @@
|
||||
import 'dart:developer' as developer;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:nhancepolicy/config/environment.dart';
|
||||
|
||||
String _formatMessage(
|
||||
String message, {
|
||||
String? className,
|
||||
String? methodName,
|
||||
}) {
|
||||
final timestamp = DateTime.now().toIso8601String();
|
||||
final contextParts = <String>[];
|
||||
if (className != null && className.isNotEmpty) {
|
||||
contextParts.add(className);
|
||||
}
|
||||
if (methodName != null && methodName.isNotEmpty) {
|
||||
contextParts.add(methodName);
|
||||
}
|
||||
final context = contextParts.isEmpty ? '' : '[${contextParts.join('.')}] ';
|
||||
return '[$timestamp] $context$message';
|
||||
}
|
||||
|
||||
void _log(
|
||||
String level,
|
||||
String message, {
|
||||
String tag = 'APP',
|
||||
Object? error,
|
||||
StackTrace? stackTrace,
|
||||
String? className,
|
||||
String? methodName,
|
||||
}) {
|
||||
final shouldLog = kDebugMode || Environment.flavor == Flavor.dev;
|
||||
if (!shouldLog) {
|
||||
return;
|
||||
}
|
||||
final formatted =
|
||||
_formatMessage(message, className: className, methodName: methodName);
|
||||
final channel = '$tag:$level';
|
||||
|
||||
developer.log(
|
||||
formatted,
|
||||
name: channel,
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
debugPrint('[$channel] $formatted');
|
||||
}
|
||||
|
||||
void logDebug(
|
||||
Object? message, {
|
||||
String tag = 'APP',
|
||||
String? className,
|
||||
String? methodName,
|
||||
}) {
|
||||
_log(
|
||||
'DEBUG',
|
||||
'${message ?? 'null'}',
|
||||
tag: tag,
|
||||
className: className,
|
||||
methodName: methodName,
|
||||
);
|
||||
}
|
||||
|
||||
void logInfo(
|
||||
Object? message, {
|
||||
String tag = 'APP',
|
||||
String? className,
|
||||
String? methodName,
|
||||
}) {
|
||||
_log(
|
||||
'INFO',
|
||||
'${message ?? 'null'}',
|
||||
tag: tag,
|
||||
className: className,
|
||||
methodName: methodName,
|
||||
);
|
||||
}
|
||||
|
||||
void logWarning(
|
||||
Object? message, {
|
||||
String tag = 'APP',
|
||||
String? className,
|
||||
String? methodName,
|
||||
}) {
|
||||
_log(
|
||||
'WARN',
|
||||
'${message ?? 'null'}',
|
||||
tag: tag,
|
||||
className: className,
|
||||
methodName: methodName,
|
||||
);
|
||||
}
|
||||
|
||||
void logError(
|
||||
Object? message, {
|
||||
String tag = 'APP',
|
||||
Object? error,
|
||||
StackTrace? stackTrace,
|
||||
String? className,
|
||||
String? methodName,
|
||||
}) {
|
||||
_log(
|
||||
'ERROR',
|
||||
'${message ?? 'null'}',
|
||||
tag: tag,
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
className: className,
|
||||
methodName: methodName,
|
||||
);
|
||||
}
|
||||