Commit c2698f96 authored by Sai Srinivas's avatar Sai Srinivas
Browse files

First app commit - Pulse

parent 820e895a
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.
}
}
class CommonResponse {
String? error;
String? sessionId;
String? staffId;
String? message;
CommonResponse({this.error, this.sessionId, this.staffId, this.message});
CommonResponse.fromJson(Map<String, dynamic> json) {
error = json['error'];
sessionId = json['session_id'];
staffId = json['staff_id'];
message = json['message'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['error'] = this.error;
data['session_id'] = this.sessionId;
data['staff_id'] = this.staffId;
data['message'] = this.message;
return data;
}
}
class ProfileResponse {
String? error;
StaffDetails? staffDetails;
String? sessionExists;
String? message;
ProfileResponse(
{this.error, this.staffDetails, this.sessionExists, this.message});
ProfileResponse.fromJson(Map<String, dynamic> json) {
error = json['error'];
staffDetails = json['staff_details'] != null
? new StaffDetails.fromJson(json['staff_details'])
: null;
sessionExists = json['session_exists'];
message = json['message'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['error'] = this.error;
if (this.staffDetails != null) {
data['staff_details'] = this.staffDetails!.toJson();
}
data['session_exists'] = this.sessionExists;
data['message'] = this.message;
return data;
}
}
class StaffDetails {
String? email;
String? firstname;
String? lastname;
String? smallProfilePic;
String? profilePic;
StaffDetails(
{this.email,
this.firstname,
this.lastname,
this.smallProfilePic,
this.profilePic});
StaffDetails.fromJson(Map<String, dynamic> json) {
email = json['email'];
firstname = json['firstname'];
lastname = json['lastname'];
smallProfilePic = json['small_profile_pic'];
profilePic = json['profile_pic'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['email'] = this.email;
data['firstname'] = this.firstname;
data['lastname'] = this.lastname;
data['small_profile_pic'] = this.smallProfilePic;
data['profile_pic'] = this.profilePic;
return data;
}
}
class WebErpResponse {
final String? html;
WebErpResponse({this.html});
// Simple factory that just stores the HTML
factory WebErpResponse.fromJson(dynamic data) {
return WebErpResponse(html: data is String ? data : null);
}
@override
String toString() {
return "WebErpResponse(html: ${html != null ? '${html!.length} characters' : 'null'})";
}
// Helper method to check if HTML contains specific content
bool contains(String text) {
return html?.toLowerCase().contains(text.toLowerCase()) ?? false;
}
// Check if it's an error response
bool get isError => html?.startsWith("Error:") ?? false;
// Check if session is valid (based on HTML content)
bool get isSessionValid {
if (html == null) return false;
return !html!.toLowerCase().contains('login');
}
}
\ No newline at end of file
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:pulse/Models/profileResponse.dart';
import '../Services/api_calling.dart';
class ProfileProvider extends ChangeNotifier {
bool _isLoading = false;
ProfileResponse? _profileResponse;
String? _errorMessage;
// Getters
bool get isLoading => _isLoading;
ProfileResponse? get profileResponse => _profileResponse;
String? get errorMessage => _errorMessage;
// Convenient getters
String? get error => _profileResponse?.error;
String? get message => _profileResponse?.message;
String? get sessionExists => _profileResponse?.sessionExists;
StaffDetails? get staffDetails => _profileResponse?.staffDetails;
String? get email => _profileResponse?.staffDetails?.email;
String? get firstname => _profileResponse?.staffDetails?.firstname;
String? get lastname => _profileResponse?.staffDetails?.lastname;
String? get smallProfilePic => _profileResponse?.staffDetails?.smallProfilePic;
String? get profilePic => _profileResponse?.staffDetails?.profilePic;
/// Fetch profile API
Future<void> fetchProfile({
required BuildContext context,
required String csrfToken,
required String sessionId,
required String staffId,
String isApp = "1",
}) async {
try {
_isLoading = true;
_errorMessage = null;
_profileResponse = null;
notifyListeners();
final data = await ApiService.fetchProfile(
csrfToken: csrfToken,
sessionId: sessionId,
staffId: staffId,
isApp: isApp,
);
if (data != null) {
_profileResponse = data;
if (_profileResponse?.error == "0") {
debugPrint("✅ Profile fetched: ${_profileResponse?.toJson()}");
} else {
_errorMessage = _profileResponse?.message ?? "Failed to fetch profile";
debugPrint("⚠️ Profile error: $_errorMessage");
}
} else {
_errorMessage = "No response from server";
debugPrint("⚠️ $_errorMessage");
}
} catch (e) {
_errorMessage = "❌ Exception: $e";
debugPrint(_errorMessage);
} finally {
_isLoading = false;
notifyListeners();
}
}
/// Reset state
void reset() {
_isLoading = false;
_profileResponse = null;
_errorMessage = null;
notifyListeners();
}
}
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:pulse/Models/commonResponse.dart';
import '../Services/api_calling.dart';
class AuthProvider extends ChangeNotifier {
bool _isLoading = false;
CommonResponse? _loginResponse;
String? _errorMessage;
bool _isLoggedIn = false;
// Existing Getters
bool get isLoading => _isLoading;
CommonResponse? get loginResponse => _loginResponse;
String? get errorMessage => _errorMessage;
bool get isLoggedIn => _isLoggedIn;
// New Getters for convenience
String? get sessionId => _loginResponse?.sessionId;
String? get staffId => _loginResponse?.staffId;
String? get message => _loginResponse?.message;
String? get error => _loginResponse?.error;
/// Login API call
Future<void> loginUser(
BuildContext context, {
required String csrfToken,
required String email,
required String password,
}) async {
try {
_isLoading = true;
_errorMessage = null;
_loginResponse = null;
notifyListeners();
final data = await ApiService.login(
csrfToken: csrfToken,
email: email,
password: password,
);
if (data != null) {
_loginResponse = data;
if (_loginResponse?.error == "0") {
_isLoggedIn = true;
debugPrint("✅ Login successful: ${_loginResponse?.toJson()}");
} else {
_isLoggedIn = false;
_errorMessage = _loginResponse?.message ?? "Login failed";
debugPrint("⚠️ Login failed: $_errorMessage");
}
} else {
_errorMessage = "No response from server";
debugPrint("⚠️ $_errorMessage");
}
} catch (e) {
_isLoggedIn = false;
_errorMessage = "❌ Exception: $e";
debugPrint(_errorMessage);
} finally {
_isLoading = false;
notifyListeners();
}
}
/// Logout API call
Future<void> logoutUser({
required String csrfToken,
required String sessionId,
required String staffId,
}) async {
try {
_isLoading = true;
_errorMessage = null;
notifyListeners();
final res = await ApiService.logoutApi(
csrfToken: csrfToken,
sessionId: sessionId,
staffId: staffId,
);
if (res != null) {
if (res.error == "0") {
_isLoggedIn = false;
_loginResponse = null; // clear user session
debugPrint("✅ Logout successful: ${res.toJson()}");
} else {
_errorMessage = res.message ?? "Logout failed";
debugPrint("⚠️ Logout failed: $_errorMessage");
}
} else {
_errorMessage = "No response from server";
debugPrint("⚠️ $_errorMessage");
}
} catch (e) {
_errorMessage = "❌ Exception: $e";
debugPrint(_errorMessage);
} finally {
_isLoading = false;
notifyListeners();
}
}
/// Reset state if needed
void reset() {
_isLoading = false;
_loginResponse = null;
_errorMessage = null;
_isLoggedIn = false;
notifyListeners();
}
}
import 'package:flutter/material.dart';
class ThemeProvider extends ChangeNotifier {
bool _isDark = false;
bool get isDark => _isDark;
void toggleTheme() {
_isDark = !_isDark;
notifyListeners();
}
}
import 'package:flutter/foundation.dart';
import 'package:pulse/Models/webErpResponse.dart';
import 'package:pulse/Services/api_calling.dart';
class WebErpProvider extends ChangeNotifier {
WebErpResponse? _response;
bool _isLoading = false;
String? _errorMessage;
WebErpResponse? get response => _response;
bool get isLoading => _isLoading;
String? get errorMessage => _errorMessage;
// Helper getters for common checks
bool get hasHtmlContent => _response?.html != null;
String? get htmlContent => _response?.html;
bool get isSessionValid => _response?.isSessionValid ?? false;
/// Call API and update state
Future<void> fetchWebViewErp({
required String csrfToken,
required String sessionId,
required String staffId,
String isApp = "1",
}) async {
_isLoading = true;
_errorMessage = null;
notifyListeners();
try {
final res = await ApiService.fetchWebErpApi(
csrfToken: csrfToken,
sessionId: sessionId,
staffId: staffId,
isApp: isApp,
);
if (res != null) {
_response = res;
debugPrint("✅ HTML response received: ${res.html?.length ?? 0} characters");
// Log what type of page we received
if (res.html != null) {
if (res.html!.toLowerCase().contains('login')) {
debugPrint("🔐 Login page detected");
} else if (res.html!.toLowerCase().contains('dashboard')) {
debugPrint("📊 Dashboard page detected");
} else if (res.html!.toLowerCase().contains('admin')) {
debugPrint("⚙️ Admin page detected");
}
}
} else {
_errorMessage = "No response from server";
debugPrint("❌ Null response from API");
}
} catch (e) {
_errorMessage = "Error: $e";
debugPrint("❌ API Error: $e");
} finally {
_isLoading = false;
notifyListeners();
}
}
/// Clear the response and error
void clear() {
_response = null;
_errorMessage = null;
notifyListeners();
}
/// Check if HTML contains specific text
bool htmlContains(String text) {
return _response?.html?.toLowerCase().contains(text.toLowerCase()) ?? false;
}
}
\ No newline at end of file
// import 'package:flutter/material.dart';
// import 'package:pulse/Screens/home_screen.dart';
// import 'package:webview_flutter/webview_flutter.dart';
//
//
// class WebErpScreen extends StatefulWidget {
// final String staffId;
// final String sessionId;
//
// const WebErpScreen({
// super.key,
// required this.staffId,
// required this.sessionId,
// });
//
// @override
// State<WebErpScreen> createState() => _WebErpScreenState();
// }
//
// class _WebErpScreenState extends State<WebErpScreen> {
// late final WebViewController _controller;
//
// @override
// void initState() {
// super.initState();
//
// _controller = WebViewController()
// ..setJavaScriptMode(JavaScriptMode.unrestricted)
// ..setNavigationDelegate(
// NavigationDelegate(
// onPageStarted: (url) {
// debugPrint("Loading: $url");
// },
// onPageFinished: (url) {
// debugPrint("Finished: $url");
// },
// ),
// )
// ..loadRequest(
// Uri.parse(
// "https://pulse.webgrid.in/app/authentication/web_erp?staff_id=${widget.staffId}&session_id=${widget.sessionId}",
// ),
// headers: {
// "Cookie": "session_id=${widget.sessionId}",
// },
// );
// }
//
// Future<bool> _handleWillPop() async {
// if (await _controller.canGoBack()) {
// _controller.goBack();
// return false; // don’t pop screen
// }
// return true; // pop screen
// }
//
// @override
// Widget build(BuildContext context) {
// return WillPopScope(
// onWillPop: _handleWillPop,
// child: Scaffold(
// appBar: AppBar(
// automaticallyImplyLeading: false,
// backgroundColor: AppColors.backgroundGradient2,
// title: Row(
// children: [
// InkResponse(
// onTap: () async {
// if (await _controller.canGoBack()) {
// _controller.goBack();
// } else {
// Navigator.pop(context, true);
// }
// },
// child: const Icon(
// Icons.arrow_back,
// color: Colors.white,
// size: 30,
// ),
// ),
// const SizedBox(width: 15),
// Text(
// "Web ERP",
// style: TextStyle(
// fontSize: 20,
// fontWeight: FontWeight.bold,
// color: AppColors.textPrimary,
// ),
// ),
// ],
// ),
// ),
// backgroundColor: const Color(0xFF1e293b),
// body: SafeArea(
// top: true,
// child: WebViewWidget(controller: _controller),
// ),
// ),
// );
// }
// }
This diff is collapsed.
// forgot_password_screen.dart
import 'package:flutter/material.dart';
class ForgotPasswordScreen extends StatelessWidget {
const ForgotPasswordScreen({super.key});
@override
Widget build(BuildContext context) {
final emailController = TextEditingController();
return Scaffold(
backgroundColor: const Color(0xFFF5F8FC),
body: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset("assets/images/pulse_logo.png", height: 82),
const SizedBox(height: 20),
const Text(
"Forgot Password",
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
const SizedBox(height: 20),
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 6,
offset: Offset(0, 2),
)
],
),
child: Column(
children: [
TextFormField(
controller: emailController,
decoration: InputDecoration(
labelText: "Email Address",
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
),
),
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
height: 45,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2563EB),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
),
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content:
Text("Password reset link sent to email")),
);
},
child: const Text(
"Confirm",
style: TextStyle(color: Colors.white),
),
),
)
],
),
),
],
),
),
),
);
}
}
This diff is collapsed.
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:pulse/Screens/authScreen/LoginScreen.dart';
import 'package:pulse/Screens/home_screen.dart';
import 'package:pulse/utils/AppColors.dart';
import 'package:pulse/utils/SharedpreferencesService.dart';
class ProfileScreen extends StatefulWidget {
final String sessionId;
final String staffId;
final String csrfToken;
final String userName;
final String userEmail;
final String profileImageUrl;
const ProfileScreen({
Key? key,
required this.sessionId,
required this.staffId,
required this.csrfToken,
this.userName = "User Name",
this.userEmail = "user@example.com",
this.profileImageUrl = "",
}) : super(key: key);
@override
State<ProfileScreen> createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.primaryColor,
body: SafeArea(
child: Column(
children: [
// Custom App Bar
_buildCustomAppBar(),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
children: [
// Profile Header Section
_buildProfileHeader(),
const SizedBox(height: 40),
// Profile Information Cards
_buildProfileInfoCards(),
const SizedBox(height: 40),
],
),
),
),
// Logout Button
_buildLogoutButton(),
],
),
),
);
}
Widget _buildCustomAppBar() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: AppColors.primaryColor,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Row(
children: [
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white.withOpacity(0.1),
),
child: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.pop(context),
),
),
const SizedBox(width: 16),
const Text(
"Profile",
style: TextStyle(
fontSize: 20,
fontFamily: 'JakartaRegular',
fontWeight: FontWeight.w700,
color: Colors.white,
),
),
],
),
);
}
Widget _buildProfileHeader() {
return Column(
children: [
// Profile Picture with Decorative Ring
Stack(
alignment: Alignment.center,
children: [
// Outer Ring
Container(
width: 148,
height: 148,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(28),
shape: BoxShape.rectangle,
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.white.withOpacity(0.3),
Colors.white.withOpacity(0.1),
],
),
),
),
// Profile Image Container
Container(
width: 140,
height: 140,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25),
color: Colors.white.withOpacity(0.1),
border: Border.all(
color: Colors.white.withOpacity(0.4),
width: 3,
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 15,
offset: const Offset(0, 6),
),
],
),
child: widget.profileImageUrl.isNotEmpty
? ClipRRect(
borderRadius: BorderRadius.circular(22),
child: CachedNetworkImage(
imageUrl: widget.profileImageUrl,
width: 140,
height: 140,
fit: BoxFit.cover,
placeholder: (context, url) => Container(
decoration: BoxDecoration(
color: AppColors.cardColor,
borderRadius: BorderRadius.circular(22),
),
child: const Center(
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
),
),
errorWidget: (context, url, error) => Container(
decoration: BoxDecoration(
color: AppColors.cardColor,
borderRadius: BorderRadius.circular(22),
),
child: const Center(
child: Icon(Icons.person, color: Colors.white, size: 50),
),
),
),
)
: Center(
child: Icon(
Icons.person,
size: 60,
color: Colors.white.withOpacity(0.7),
),
),
),
],
),
const SizedBox(height: 24),
// User Name with Welcome Text
Column(
children: [
Text(
"Welcome Back,",
style: TextStyle(
fontSize: 16,
fontFamily: 'JakartaRegular',
color: Colors.white.withOpacity(0.8),
),
),
const SizedBox(height: 4),
Text(
widget.userName,
style: const TextStyle(
fontSize: 24,
fontFamily: 'JakartaRegular',
fontWeight: FontWeight.w700,
color: Colors.white,
),
textAlign: TextAlign.center,
),
],
),
],
);
}
Widget _buildProfileInfoCards() {
return Column(
children: [
// Personal Information Card
Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.08),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: Colors.white.withOpacity(0.1),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Section Title
Row(
children: [
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
shape: BoxShape.circle,
),
child: const Icon(
Icons.info,
color: Colors.white,
size: 18,
),
),
const SizedBox(width: 12),
const Text(
"Personal Information",
style: TextStyle(
fontSize: 16,
fontFamily: 'JakartaRegular',
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
],
),
const SizedBox(height: 20),
// Information Items
_buildInfoItem(
icon: Icons.person_outline,
label: "Full Name",
value: widget.userName,
),
const SizedBox(height: 16),
_buildInfoItem(
icon: Icons.email_outlined,
label: "Email Address",
value: widget.userEmail,
),
const SizedBox(height: 16),
// _buildInfoItem(
// icon: Icons.badge_outlined,
// label: "Staff ID",
// value: widget.staffId,
// ),
],
),
),
],
);
}
Widget _buildInfoItem({
required IconData icon,
required String label,
required String value,
}) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.05),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
icon,
color: Colors.white.withOpacity(0.7),
size: 20,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 12,
fontFamily: 'JakartaRegular',
color: Colors.white.withOpacity(0.6),
),
),
const SizedBox(height: 4),
Text(
value,
style: const TextStyle(
fontSize: 16,
fontFamily: 'JakartaRegular',
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
],
),
),
],
);
}
Widget _buildLogoutButton() {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: AppColors.primaryColor,
border: Border(
top: BorderSide(
color: Colors.white.withOpacity(0.1),
),
),
),
child: ElevatedButton(
onPressed: () {
_showLogoutConfirmationDialog();
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.redAccent.withOpacity(0.9),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
side: BorderSide(
color: Colors.white.withOpacity(0.2),
),
),
elevation: 0,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.logout_rounded,
color: Colors.white.withOpacity(0.9),
size: 22,
),
const SizedBox(width: 12),
Text(
"Logout",
style: TextStyle(
fontSize: 16,
fontFamily: 'JakartaRegular',
fontWeight: FontWeight.w600,
color: Colors.white.withOpacity(0.9),
),
),
],
),
),
);
}
void _showLogoutConfirmationDialog() {
showDialog(
context: context,
builder: (BuildContext context) {
return Dialog(
backgroundColor: Colors.transparent,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Icon
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFFFEF2F2),
shape: BoxShape.circle,
),
child: const Icon(
Icons.logout_rounded,
color: Color(0xFFEF4444),
size: 32,
),
),
const SizedBox(height: 16),
// Title
const Text(
"Logout Confirmation",
style: TextStyle(
fontSize: 18,
fontFamily: 'JakartaRegular',
fontWeight: FontWeight.w700,
color: Colors.black,
),
),
const SizedBox(height: 8),
// Message
Text(
"Are you sure you want to logout?",
style: TextStyle(
fontSize: 14,
fontFamily: 'JakartaRegular',
color: Colors.grey.shade600,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
// Buttons
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () => Navigator.pop(context),
style: OutlinedButton.styleFrom(
foregroundColor: Colors.grey.shade700,
side: BorderSide(color: Colors.grey.shade400),
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: const Text("Cancel"),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton(
onPressed: () async {
Navigator.pop(context); // Close dialog first
await _performLogout();
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFEF4444),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: const Text("Logout"),
),
),
],
),
],
),
),
),
);
},
);
}
Future<void> _performLogout() async {
// Show loading indicator
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => Dialog(
backgroundColor: Colors.transparent,
child: Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15),
),
child: const Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(AppColors.primaryColor),
),
SizedBox(height: 16),
Text(
"Logging out...",
style: TextStyle(
fontFamily: 'JakartaRegular',
fontWeight: FontWeight.w500,
),
),
],
),
),
),
);
try {
// Your logout logic here
final prefs = SharedPreferencesService.instance;
await prefs.remove("email");
await prefs.remove("password");
await prefs.remove("sessionId");
await prefs.remove("staffId");
// Navigate to LoginScreen and remove all previous routes
if (mounted) {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => const LoginScreen()),
(route) => false,
);
}
} catch (e) {
if (mounted) {
Navigator.pop(context); // Close loading indicator
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Logout failed: $e'),
backgroundColor: Colors.red,
),
);
}
}
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:pulse/Screens/home_screen.dart';
import 'package:pulse/utils/SharedpreferencesService.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SettingsScreen extends StatefulWidget {
const SettingsScreen({Key? key}) : super(key: key);
@override
State<SettingsScreen> createState() => _SettingsScreenState();
}
class _SettingsScreenState extends State<SettingsScreen> {
final prefs = SharedPreferencesService.instance;
bool _autoLoginEnabled = false;
@override
void initState() {
super.initState();
_loadAutoLoginPreference();
}
Future<void> _loadAutoLoginPreference() async {
final remember = await prefs.getBool("remember");
setState(() {
_autoLoginEnabled = remember ?? false; // default to false if null
});
}
Future<void> _updateAutoLoginPreference(bool value) async {
await prefs.saveBool("remember", value);
setState(() {
_autoLoginEnabled = value;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: AppColors.backgroundGradient1,
title: Row(
children: [
InkResponse(
onTap: () => Navigator.pop(context, true),
child: Icon(
Icons.arrow_back,
color: Colors.white,
size: 35,
),
),
const SizedBox(width: 15),
Text(
"Settings",
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
),
),
],
),
),
body: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [
AppColors.backgroundGradient1,
AppColors.backgroundGradientMid,
AppColors.backgroundGradientBottom,
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Preference",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.normal,
color: AppColors.textSecondary,
),
),
const SizedBox(height: 20),
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: AppColors.cardColor,
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
"Auto Login",
style: TextStyle(
fontSize: 16,
color: AppColors.textPrimary,
),
),
Switch(
value: _autoLoginEnabled,
onChanged: _updateAutoLoginPreference,
activeColor: AppColors.accentColor,
),
],
),
),
],
),
),
);
}
}
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:pulse/Models/commonResponse.dart';
import 'package:pulse/Models/profileResponse.dart';
import 'package:pulse/Models/webErpResponse.dart';
class ApiService {
static const String baseUrl = "https://pulse.webgrid.in/app/";
static const String loginUrl = "${baseUrl}Authentication/login";
static const String logoutUrl = "${baseUrl}Home/logout";
static const String profileUrl = "${baseUrl}Home/profile_details";
static const String webErpUrl = "https://pulse.webgrid.in/app/Home/web_erp";
/// Reusable POST wrapper
static Future<http.Response?> post(
Map<String, String> data,
String url, [
Map<String, String> headers = const {},
]) async {
try {
final response = await http.post(
Uri.parse(url),
body: data,
headers: headers,
);
if (response.statusCode == 200) {
return response;
} else {
debugPrint("❌ Failed POST: $url${response.statusCode}");
return null;
}
} catch (e) {
debugPrint("❌ Exception in POST($url): $e");
return null;
}
}
/// Login API
static Future<CommonResponse?> login({
required String csrfToken,
required String email,
required String password,
String isApp = "1",
}) async {
try {
Map<String, String> data = {
"csrf_token_name": csrfToken,
"email": email,
"password": password,
"is_app": isApp,
};
final res = await post(data, loginUrl);
if (res != null) {
return CommonResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("⚠️ Null Response in login()");
return null;
}
} catch (e) {
debugPrint("❌ API Error (login): $e");
return null;
}
}
/// Logout API
static Future<CommonResponse?> logoutApi({
required String csrfToken,
required String sessionId,
required String staffId,
String isApp = "1",
}) async {
try {
Map<String, String> data = {
"csrf_token_name": csrfToken,
"session_id": sessionId,
"staff_id": staffId,
"is_app": isApp,
};
final res = await post(data, logoutUrl);
if (res != null) {
return CommonResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("⚠️ Null Response in logoutApi()");
return null;
}
} catch (e) {
debugPrint("❌ API Error (logout): $e");
return null;
}
}
/// Profile API
static Future<ProfileResponse?> fetchProfile({
required String csrfToken,
required String sessionId,
required String staffId,
String isApp = "1",
}) async {
try {
Map<String, String> data = {
"csrf_token_name": csrfToken,
"session_id": sessionId,
"staff_id": staffId,
"is_app": isApp,
};
final res = await post(data, profileUrl);
if (res != null) {
return ProfileResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("⚠️ Null Response in profile()");
return null;
}
} catch (e) {
debugPrint("❌ API Error (profile): $e");
return null;
}
}
static Future<WebErpResponse?> fetchWebErpApi({
required String csrfToken,
required String sessionId,
required String staffId,
String isApp = "1",
}) async {
debugPrint("=== Web API Called ===");
try {
Map<String, String> data = {
"csrf_token_name": csrfToken,
"session_id": sessionId,
"staff_id": staffId,
"is_app": isApp,
};
debugPrint("URL: $webErpUrl");
debugPrint("Data: $data");
var client = http.Client();
final response = await client.post(
Uri.parse(webErpUrl),
body: data,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'Mozilla/5.0',
},
).timeout(Duration(seconds: 30));
debugPrint("Initial Status: ${response.statusCode}");
http.Response finalResponse = response;
// Handle redirect
if (response.statusCode == 302) {
debugPrint("Redirect detected");
final location = response.headers['location'];
if (location != null) {
debugPrint("Redirecting to: $location");
// Follow the redirect
finalResponse = await client.get(
Uri.parse(location),
headers: {
'User-Agent': 'Mozilla/5.0',
},
);
debugPrint("Redirect Status: ${finalResponse.statusCode}");
debugPrint("HTML Response Length: ${finalResponse.body.length}");
}
}
client.close();
// Return the HTML response as-is, no JSON decoding
return WebErpResponse(html: finalResponse.body);
} catch (e) {
debugPrint("Error in fetchWebErpApi: $e");
return WebErpResponse(html: "Error: $e");
}
}
}
import 'dart:async';
import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:pulse/Notifier/auth_provider.dart';
import 'package:pulse/Screens/authScreen/LoginScreen.dart';
import 'package:pulse/Screens/home_screen.dart';
import 'package:pulse/utils/SharedpreferencesService.dart';
import 'package:pulse/utils/customSnackBar.dart';
import 'package:flutter_svg/svg.dart';
class SplashScreen extends StatefulWidget {
const SplashScreen({super.key});
@override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> with SingleTickerProviderStateMixin {
final _prefs = SharedPreferencesService.instance;
double _opacity = 0.0;
double _scale = 0.8;
double _progressValue = 0.0;
late AnimationController _controller;
late Animation<double> _animation;
Timer? _connectivityTimer;
bool _progressCheckCompleted = false;
bool _hasInternet = true;
@override
void initState() {
super.initState();
// Initialize connectivity check
_initConnectivity();
// Animation controller for pulse effect
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat(reverse: true);
_animation = Tween<double>(begin: 0.95, end: 1.05).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
// Start fade-in and scale animation
Future.delayed(const Duration(milliseconds: 200), () {
setState(() {
_opacity = 1.0;
_scale = 1.0;
});
});
// Simulate progress animation
_simulateProgress();
}
Future<void> _initConnectivity() async {
try {
// Initial connectivity check
await _checkConnectivity();
// Use periodic checks instead of stream
_connectivityTimer = Timer.periodic(const Duration(seconds: 3), (timer) {
_checkConnectivity();
});
} catch (e) {
debugPrint("Connectivity initialization error: $e");
_updateConnectionStatus(false);
}
}
Future<void> _checkConnectivity() async {
try {
// Method 1: Using connectivity_plus
final connectivity = Connectivity();
final results = await connectivity.checkConnectivity();
final hasInternet = results.any((result) => result != ConnectivityResult.none);
// Method 2: Fallback with socket test
if (hasInternet) {
try {
final result = await InternetAddress.lookup('google.com');
final socketCheck = result.isNotEmpty && result[0].rawAddress.isNotEmpty;
_updateConnectionStatus(socketCheck);
} catch (e) {
_updateConnectionStatus(false);
}
} else {
_updateConnectionStatus(false);
}
} catch (e) {
debugPrint("Connectivity check error: $e");
_updateConnectionStatus(false);
}
}
void _updateConnectionStatus(bool hasInternet) {
if (mounted) {
setState(() {
_hasInternet = hasInternet;
});
}
if (!hasInternet) {
_showNoInternetSnackbar();
} else {
// Dismiss the warning snackbar if internet is restored
ScaffoldMessenger.of(context).hideCurrentSnackBar();
if (!_progressCheckCompleted) {
_startLoginCheck();
}
}
}
void _showNoInternetSnackbar() {
if (mounted) {
WidgetsBinding.instance.addPostFrameCallback((_) {
// Hide any existing snackbar
ScaffoldMessenger.of(context).hideCurrentSnackBar();
// Show custom snackbar with all required params
CustomSnackBar.show(
context: context,
title: "Connection Error",
message: "No internet connection! Please connect to the internet.",
icon: Icons.wifi_off,
backgroundColor: Colors.redAccent,
);
});
}
}
void _simulateProgress() {
Timer.periodic(const Duration(milliseconds: 30), (timer) {
if (_progressValue < 1.0) {
setState(() {
_progressValue += 0.02;
});
} else {
timer.cancel();
if (_hasInternet && !_progressCheckCompleted) {
_startLoginCheck();
}
}
});
}
void _startLoginCheck() {
if (_progressCheckCompleted) return;
_progressCheckCompleted = true;
_checkLoginStatus();
}
Future<void> _checkLoginStatus() async {
await Future.delayed(const Duration(seconds: 3));
final sessionId = await _prefs.getString("sessionId");
final staffId = await _prefs.getString("staffId");
debugPrint("✅ Direct session restore $sessionId");
debugPrint("✅ Direct staff restore $staffId");
if (sessionId != null && staffId != null && sessionId.isNotEmpty) {
// Direct session restore
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => HomeScreen(
sessionId: sessionId,
staffId: staffId,
),
),
);
return;
}
final email = await _prefs.getString("email");
final password = await _prefs.getString("password");
if (email != null && password != null) {
final loginProvider = Provider.of<AuthProvider>(context, listen: false);
await loginProvider.loginUser(
context,
csrfToken: "2fb317d960000fe130fb0e99cee97aa9",
email: email,
password: password,
);
if (loginProvider.isLoggedIn) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => HomeScreen(
sessionId: loginProvider.sessionId!,
staffId: loginProvider.staffId.toString(),
),
),
);
return;
}
}
// Default → Login screen
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const LoginScreen()),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFF1e293b), // Theme color applied
body: Stack(
children: [
// Background gradient overlay
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
const Color(0xFF1e293b).withOpacity(0.9),
const Color(0xFF1e293b).withOpacity(0.95),
const Color(0xFF1e293b),
],
stops: const [0.0, 0.5, 1.0],
),
),
),
// Subtle background pattern
Opacity(
opacity: 0.03,
child: Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/images/pulse_logo.png"),
repeat: ImageRepeat.repeat,
scale: 8.0,
),
),
),
),
Center(
child: AnimatedOpacity(
opacity: _opacity,
duration: const Duration(seconds: 1),
curve: Curves.easeInOut,
child: AnimatedScale(
scale: _scale,
duration: const Duration(seconds: 1),
curve: Curves.easeOutBack,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Pulse animation container
AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.scale(
scale: _animation.value,
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: Colors.white.withOpacity(0.1),
shape: BoxShape.rectangle,
boxShadow: [
BoxShadow(
color: Colors.white.withOpacity(0.1),
blurRadius: 20,
spreadRadius: 5,
),
],
),
child: Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: const Color(0xFF1e293b),
shape: BoxShape.rectangle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
child: SvgPicture.asset(
"assets/svg/pulse_logo_ic.svg",
height: 75,
//filterQuality: FilterQuality.high,
),
),
),
);
},
),
const SizedBox(height: 40),
// App name with gradient text
ShaderMask(
shaderCallback: (bounds) {
return LinearGradient(
colors: [
Colors.white,
Colors.white.withOpacity(0.8),
],
).createShader(bounds);
},
child: Text(
"PULSE",
style: TextStyle(
fontSize: 36,
fontWeight: FontWeight.w800,
letterSpacing: 2.0,
color: Colors.white,
),
),
),
const SizedBox(height: 8),
// Tagline
Text(
"Enterprise Resource Planning",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w300,
color: Colors.white.withOpacity(0.7),
letterSpacing: 1.2,
),
),
const SizedBox(height: 50),
// Animated progress bar
Container(
width: 200,
height: 4,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(2),
),
child: Stack(
children: [
// Background
Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(2),
),
),
// Progress
AnimatedContainer(
duration: const Duration(milliseconds: 200),
width: 200 * _progressValue,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.white,
Colors.white.withOpacity(0.8),
],
),
borderRadius: BorderRadius.circular(2),
boxShadow: [
BoxShadow(
color: Colors.white.withOpacity(0.5),
blurRadius: 4,
spreadRadius: 1,
),
],
),
),
],
),
),
const SizedBox(height: 20),
// Loading text with dots animation
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Loading",
style: TextStyle(
color: Colors.white.withOpacity(0.8),
fontSize: 14,
),
),
SizedBox(width: 4),
_buildLoadingDots(),
],
),
const SizedBox(height: 30),
// Version info
Text(
"Version 1.0.0",
style: TextStyle(
color: Colors.white.withOpacity(0.5),
fontSize: 12,
),
),
],
),
),
),
),
// Bottom company info
Positioned(
bottom: 30,
left: 0,
right: 0,
child: Column(
children: [
Text(
"Powered by",
style: TextStyle(
color: Colors.white.withOpacity(0.4),
fontSize: 12,
),
),
const SizedBox(height: 4),
Text(
"Avantech Web Grid",
style: TextStyle(
color: Colors.white.withOpacity(0.6),
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
],
),
),
],
),
);
}
Widget _buildLoadingDots() {
return TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: const Duration(milliseconds: 1500),
builder: (context, value, child) {
int visibleDots = (value * 3).ceil();
return Row(
children: List.generate(3, (index) {
return Opacity(
opacity: index < visibleDots ? 1.0 : 0.3,
child: Text(
".",
style: TextStyle(
color: Colors.white.withOpacity(0.8),
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
);
}),
);
},
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:provider/provider.dart';
import 'package:pulse/Notifier/ProfileProvider.dart';
import 'package:pulse/Notifier/webProvider.dart';
import 'package:pulse/SplashScreen.dart';
import 'Notifier/auth_provider.dart';
import 'Notifier/theme_provider.dart';
import 'Screens/authScreen/LoginScreen.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
InAppWebViewController.setWebContentsDebuggingEnabled(true);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<AuthProvider>(create: (_) => AuthProvider(),),
ChangeNotifierProvider<ThemeProvider>(create: (_) => ThemeProvider(),),
ChangeNotifierProvider<ProfileProvider>(create: (_) => ProfileProvider(),),
ChangeNotifierProvider<WebErpProvider>(create: (_) => WebErpProvider(),),
],
child: Consumer<ThemeProvider>(
builder: (context, themeProvider, child) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Web Grid Pulse',
theme: themeProvider.isDark
? ThemeData.dark()
: ThemeData.light().copyWith(
scaffoldBackgroundColor: const Color(0xFFF5F8FC),
),
home: const SplashScreen(),
);
},
),
);
}
}
import 'dart:ui';
import 'package:flutter/material.dart';
class AppColors {
static const Color primaryColor = Color(0xFF1e293b);
static const Color secondaryColor = Color(0xFF2563EB);
static const Color accentColor = Color(0xFF3B82F6);
static const Color backgroundGradient1 = Color(0xFF0F172A);
static const Color backgroundGradientMid = Color(0xFF344867);
static const Color backgroundGradientBottom = Color(0xFF6f8ab6);
static const Color cardColor = Color(0xFF334155);
static const Color textPrimary = Colors.white;
static const Color textSecondary = Color(0xFF94A3B8);
}
import 'package:shared_preferences/shared_preferences.dart';
class SharedPreferencesService {
// Singleton
SharedPreferencesService._privateConstructor();
static final SharedPreferencesService instance =
SharedPreferencesService._privateConstructor();
/// Save String
Future<bool> saveString(String key, String value) async {
final prefs = await SharedPreferences.getInstance();
return prefs.setString(key, value);
}
/// Save Int
Future<bool> saveInt(String key, int value) async {
final prefs = await SharedPreferences.getInstance();
return prefs.setInt(key, value);
}
/// Save Bool
Future<bool> saveBool(String key, bool value) async {
final prefs = await SharedPreferences.getInstance();
return prefs.setBool(key, value);
}
/// Save Double
Future<bool> saveDouble(String key, double value) async {
final prefs = await SharedPreferences.getInstance();
return prefs.setDouble(key, value);
}
/// Get String
Future<String?> getString(String key) async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(key);
}
/// Get Int
Future<int?> getInt(String key) async {
final prefs = await SharedPreferences.getInstance();
return prefs.getInt(key);
}
/// Get Bool
Future<bool?> getBool(String key) async {
final prefs = await SharedPreferences.getInstance();
return prefs.getBool(key);
}
/// Get Double
Future<double?> getDouble(String key) async {
final prefs = await SharedPreferences.getInstance();
return prefs.getDouble(key);
}
/// Remove a key
Future<bool> remove(String key) async {
final prefs = await SharedPreferences.getInstance();
return prefs.remove(key);
}
/// Clear all preferences
Future<bool> clearPreferences() async {
final prefs = await SharedPreferences.getInstance();
return prefs.clear();
}
}
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment