282 lines
8.5 KiB
Dart
282 lines
8.5 KiB
Dart
|
|
import 'package:flutter/material.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'dart:convert';
|
|
|
|
import 'src/calendar_screen.dart';
|
|
import 'src/education_screen.dart';
|
|
import 'src/resume_screen.dart';
|
|
import 'src/settings_screen.dart';
|
|
|
|
void main() {
|
|
runApp(const MyApp());
|
|
}
|
|
|
|
// Color Palette
|
|
const Color darkJungleGreen = Color(0xFF000505);
|
|
const Color englishViolet = Color(0xFF3B3355);
|
|
const Color slateGray = Color(0xFF5D5D81);
|
|
const Color lightSteelBlue = Color(0xFFBFCDE0);
|
|
const Color ghostWhite = Color(0xFFFEFCFD);
|
|
|
|
class MyApp extends StatefulWidget {
|
|
const MyApp({Key? key}) : super(key: key);
|
|
|
|
@override
|
|
State<MyApp> createState() => _MyAppState();
|
|
}
|
|
|
|
class _MyAppState extends State<MyApp> {
|
|
bool _isDarkMode = false;
|
|
|
|
void _toggleTheme(bool isDark) {
|
|
setState(() {
|
|
_isDarkMode = isDark;
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MaterialApp(
|
|
title: 'Connect',
|
|
theme: ThemeData(
|
|
brightness: Brightness.light,
|
|
primaryColor: englishViolet,
|
|
scaffoldBackgroundColor: ghostWhite,
|
|
colorScheme: const ColorScheme.light(
|
|
primary: englishViolet,
|
|
secondary: slateGray,
|
|
surface: ghostWhite,
|
|
background: ghostWhite,
|
|
onPrimary: ghostWhite,
|
|
onSecondary: darkJungleGreen,
|
|
onSurface: darkJungleGreen,
|
|
onBackground: darkJungleGreen,
|
|
error: Colors.red,
|
|
onError: ghostWhite,
|
|
),
|
|
appBarTheme: const AppBarTheme(
|
|
backgroundColor: englishViolet,
|
|
foregroundColor: ghostWhite,
|
|
titleTextStyle: TextStyle(fontFamily: 'Poppins', fontSize: 20, fontWeight: FontWeight.bold),
|
|
),
|
|
textTheme: const TextTheme(
|
|
bodyLarge: TextStyle(color: darkJungleGreen),
|
|
titleLarge: TextStyle(color: darkJungleGreen, fontWeight: FontWeight.bold),
|
|
),
|
|
floatingActionButtonTheme: const FloatingActionButtonThemeData(
|
|
backgroundColor: englishViolet,
|
|
foregroundColor: ghostWhite,
|
|
),
|
|
),
|
|
darkTheme: ThemeData(
|
|
brightness: Brightness.dark,
|
|
primaryColor: lightSteelBlue,
|
|
scaffoldBackgroundColor: darkJungleGreen,
|
|
colorScheme: const ColorScheme.dark(
|
|
primary: lightSteelBlue,
|
|
secondary: slateGray,
|
|
surface: darkJungleGreen,
|
|
background: darkJungleGreen,
|
|
onPrimary: darkJungleGreen,
|
|
onSecondary: ghostWhite,
|
|
onSurface: ghostWhite,
|
|
onBackground: ghostWhite,
|
|
error: Colors.red,
|
|
onError: ghostWhite,
|
|
),
|
|
appBarTheme: const AppBarTheme(
|
|
backgroundColor: slateGray,
|
|
foregroundColor: ghostWhite,
|
|
titleTextStyle: TextStyle(fontFamily: 'Poppins', fontSize: 20, fontWeight: FontWeight.bold),
|
|
),
|
|
textTheme: const TextTheme(
|
|
bodyLarge: TextStyle(color: ghostWhite),
|
|
titleLarge: TextStyle(color: ghostWhite, fontWeight: FontWeight.bold),
|
|
),
|
|
floatingActionButtonTheme: const FloatingActionButtonThemeData(
|
|
backgroundColor: lightSteelBlue,
|
|
foregroundColor: darkJungleGreen,
|
|
),
|
|
),
|
|
themeMode: _isDarkMode ? ThemeMode.dark : ThemeMode.light,
|
|
home: JobListingScreen(isDarkMode: _isDarkMode, onThemeChanged: _toggleTheme),
|
|
);
|
|
}
|
|
}
|
|
|
|
class JobListingScreen extends StatefulWidget {
|
|
final bool isDarkMode;
|
|
final ValueChanged<bool> onThemeChanged;
|
|
|
|
const JobListingScreen({
|
|
Key? key,
|
|
required this.isDarkMode,
|
|
required this.onThemeChanged,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
_JobListingScreenState createState() => _JobListingScreenState();
|
|
}
|
|
|
|
class _JobListingScreenState extends State<JobListingScreen> {
|
|
List<dynamic> jobs = [];
|
|
bool isLoading = true;
|
|
String? error;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
fetchJobs();
|
|
}
|
|
|
|
Future<void> fetchJobs() async {
|
|
// IMPORTANT: Replace with your actual IP/domain in a real environment.
|
|
// 10.0.2.2 is the special alias for the host machine's localhost in the Android emulator.
|
|
const String apiUrl = 'http://10.0.2.2/api.php';
|
|
try {
|
|
final response = await http.get(Uri.parse(apiUrl));
|
|
if (response.statusCode == 200) {
|
|
final result = json.decode(response.body);
|
|
if (result['success'] == true) {
|
|
setState(() {
|
|
jobs = result['data'];
|
|
isLoading = false;
|
|
});
|
|
} else {
|
|
setState(() {
|
|
error = result['error'] ?? 'An unknown error occurred.';
|
|
isLoading = false;
|
|
});
|
|
}
|
|
} else {
|
|
setState(() {
|
|
error = 'Failed to load jobs. Status code: ${response.statusCode}';
|
|
isLoading = false;
|
|
});
|
|
}
|
|
} catch (e) {
|
|
setState(() {
|
|
error = 'Failed to connect to the server. Please check your network connection and the API endpoint. Details: $e';
|
|
isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Connect'),
|
|
),
|
|
drawer: Drawer( // Vertical navigation drawer
|
|
child: ListView(
|
|
padding: EdgeInsets.zero,
|
|
children: [
|
|
const DrawerHeader(
|
|
decoration: BoxDecoration(
|
|
color: englishViolet,
|
|
),
|
|
child: Text(
|
|
'Connect',
|
|
style: TextStyle(color: Colors.white, fontSize: 24),
|
|
),
|
|
),
|
|
ListTile(
|
|
leading: const Icon(Icons.school),
|
|
title: const Text('Education/Information'),
|
|
onTap: () {
|
|
Navigator.pop(context);
|
|
Navigator.push(context, MaterialPageRoute(builder: (context) => const EducationScreen()));
|
|
},
|
|
),
|
|
ListTile(
|
|
leading: const Icon(Icons.description),
|
|
title: const Text('Resume/Job Letter'),
|
|
onTap: () {
|
|
Navigator.pop(context);
|
|
Navigator.push(context, MaterialPageRoute(builder: (context) => const ResumeScreen()));
|
|
},
|
|
),
|
|
ListTile(
|
|
leading: const Icon(Icons.calendar_today),
|
|
title: const Text('Calendar'),
|
|
onTap: () {
|
|
Navigator.pop(context);
|
|
Navigator.push(context, MaterialPageRoute(builder: (context) => const CalendarScreen()));
|
|
},
|
|
),
|
|
ListTile(
|
|
leading: const Icon(Icons.settings),
|
|
title: const Text('Settings'),
|
|
onTap: () {
|
|
Navigator.pop(context);
|
|
Navigator.push(context, MaterialPageRoute(builder: (context) => SettingsScreen(
|
|
isDarkMode: widget.isDarkMode,
|
|
onThemeChanged: widget.onThemeChanged,
|
|
)));
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
body: buildBody(),
|
|
floatingActionButton: FloatingActionButton(
|
|
onPressed: () {
|
|
// AI Chatbot action
|
|
},
|
|
child: const Icon(Icons.chat),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget buildBody() {
|
|
if (isLoading) {
|
|
return const Center(child: CircularProgressIndicator());
|
|
}
|
|
|
|
if (error != null) {
|
|
return Center(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Text(
|
|
'Error: $error',
|
|
textAlign: TextAlign.center,
|
|
style: const TextStyle(color: Colors.red, fontSize: 16),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (jobs.isEmpty) {
|
|
return const Center(child: Text('No jobs found.'));
|
|
}
|
|
|
|
return ListView.builder(
|
|
itemCount: jobs.length,
|
|
itemBuilder: (context, index) {
|
|
final job = jobs[index];
|
|
return Card(
|
|
margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
|
elevation: 4,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
|
child: ListTile(
|
|
title: Text(job['title'] ?? 'No Title', style: Theme.of(context).textTheme.titleLarge),
|
|
subtitle: Text(
|
|
'${job['company'] ?? 'No Company'} - ${job['location'] ?? 'No Location'}',
|
|
style: Theme.of(context).textTheme.bodyLarge,
|
|
),
|
|
trailing: Text(
|
|
job['salary_range'] ?? '',
|
|
style: TextStyle(color: Theme.of(context).colorScheme.primary),
|
|
),
|
|
onTap: () {
|
|
// Show job details
|
|
},
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|