diff --git a/lib/Widgets/job_widget.dart b/lib/Widgets/job_widget.dart index 30d28a3..e02b962 100644 --- a/lib/Widgets/job_widget.dart +++ b/lib/Widgets/job_widget.dart @@ -1,6 +1,7 @@ // ignore_for_file: use_build_context_synchronously import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:cpd_ss23/jobs/job_details.dart'; import 'package:cpd_ss23/services/global_methods.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; @@ -84,7 +85,15 @@ class _JobWidgetState extends State { elevation: 8, margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), child: ListTile( - onTap: () {}, + onTap: () { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => JobDetailsScreen( + uploadedBy: widget.uploadedBy, + jobID: widget.jobId, + ))); + }, onLongPress: () { _deleteDialog(); }, diff --git a/lib/jobs/job_details.dart b/lib/jobs/job_details.dart new file mode 100644 index 0000000..57bf069 --- /dev/null +++ b/lib/jobs/job_details.dart @@ -0,0 +1,599 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:cpd_ss23/jobs/jobs_screen.dart'; +import 'package:cpd_ss23/services/global_methods.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +class JobDetailsScreen extends StatefulWidget { + final String uploadedBy; + final String jobID; + + const JobDetailsScreen({required this.uploadedBy, required this.jobID}); + + @override + State createState() => _JobDetailsScreenState(); +} + +class _JobDetailsScreenState extends State { + final FirebaseAuth _auth = FirebaseAuth.instance; + + final TextEditingController _commentController = TextEditingController(); + bool _isCommenting = false; + String? authorName; + String? userImageUrl; + String? jobCategory; + String? jobDescription; + String? jobTitle; + bool? recruitment; + Timestamp? postedDateTimeStamp; + Timestamp? deadlineDateTimeStamp; + String? postedDate; + String? deadlineDate; + String? locationCompany = ""; + String? emailCompany = ""; + int applicants = 0; + bool isDeadlineAvailable = false; + + void getJobData() async { + final DocumentSnapshot userDoc = await FirebaseFirestore.instance + .collection("users") + .doc(widget.uploadedBy) + .get(); + if (userDoc == null) { + return; + } else { + setState(() { + authorName = userDoc.get("name"); + userImageUrl = userDoc.get("userImage"); + }); + } + final DocumentSnapshot jobDatabase = await FirebaseFirestore.instance + .collection("jobs") + .doc(widget.jobID) + .get(); + if (jobDatabase == null) { + return; + } else { + setState(() { + jobTitle = jobDatabase.get("jobTitle"); + jobDescription = jobDatabase.get("jobDescription"); + recruitment = jobDatabase.get("recruitment"); + emailCompany = jobDatabase.get("email"); + locationCompany = jobDatabase.get("location"); + applicants = jobDatabase.get("applicants"); + postedDateTimeStamp = jobDatabase.get("createdAt"); + deadlineDateTimeStamp = jobDatabase.get("deadlineDateTimeStamp"); + deadlineDate = jobDatabase.get("deadlineDate"); + var postDate = postedDateTimeStamp!.toDate(); + postedDate = "${postDate.year}-${postDate.month}-${postDate.day}"; + }); + var date = deadlineDateTimeStamp!.toDate(); + isDeadlineAvailable = date.isAfter(DateTime.now()); + } + } + + @override + void initState() { + super.initState(); + getJobData(); + } + + Widget dividerWidget() { + return const Column( + children: [ + SizedBox( + height: 10, + ), + Divider( + thickness: 1, + color: Colors.grey, + ), + SizedBox( + height: 10, + ), + ], + ); + } + + applyForJob() { + //TODO Mail senden funktioniert noch nicht + final Uri params = Uri( + scheme: "mailto", + path: emailCompany, + query: + "subject=Applying for $jobTitle&body=Hello, please attach Resume CV file", + ); + final url = params.toString(); + launchUrlString(url); + addNewApplicants(); + } + + void addNewApplicants() async { + var docRef = + FirebaseFirestore.instance.collection("jobs").doc(widget.jobID); + docRef.update({ + "applicants": applicants + 1, + }); + + Navigator.pop(context); + } + + @override + Widget build(BuildContext context) { + return Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: [Colors.cyan, Colors.white60], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: [0.2, 0.9], + )), + child: Scaffold( + backgroundColor: Colors.transparent, + appBar: AppBar( + leading: IconButton( + icon: const Icon( + Icons.close, + size: 40, + color: Colors.white, + ), + onPressed: () { + Navigator.pushReplacement( + context, MaterialPageRoute(builder: (context) => JobScreen())); + }, + )), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(4.0), + child: Card( + color: Colors.black54, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 4), + child: Text( + jobTitle == null ? "" : jobTitle!, + maxLines: 3, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 30, + ), + ), + ), + const SizedBox( + height: 20, + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + height: 60, + width: 60, + decoration: BoxDecoration( + border: Border.all( + width: 3, + color: Colors.grey, + ), + shape: BoxShape.rectangle, + image: DecorationImage( + image: NetworkImage( + userImageUrl == null + ? "https://img.myloview.de/bilder/avatar-icon-vector-male-user-person-profile-symbol-in-flat-color-glyph-pictogram-illustration-700-163956247.jpg" + : userImageUrl!, + ), + fit: BoxFit.fill, + )), + ), + Padding( + padding: const EdgeInsets.only(left: 10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + authorName == null ? "" : authorName!, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + color: Colors.white, + ), + ), + const SizedBox( + height: 5, + ), + Text( + locationCompany!, + style: const TextStyle(color: Colors.grey), + ) + ], + ), + ) + ], + ), + dividerWidget(), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + applicants.toString(), + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 18, + ), + ), + const SizedBox( + width: 6, + ), + const Text( + "Applicants", + style: TextStyle(color: Colors.grey), + ), + const SizedBox( + width: 10, + ), + const Icon( + Icons.how_to_reg_sharp, + color: Colors.grey, + ) + ], + ), + FirebaseAuth.instance.currentUser!.uid != + widget.uploadedBy + ? Container() + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + dividerWidget(), + const Text( + "Recruitment", + style: TextStyle( + fontSize: 18, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox( + height: 5, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton( + onPressed: () { + User? user = _auth.currentUser; + final _uid = user!.uid; + if (_uid == widget.uploadedBy) { + try { + FirebaseFirestore.instance + .collection("jobs") + .doc(widget.jobID) + .update( + {"recruitment": true}); + } catch (error) { + GlobalMethod.showErrorDialog( + error: + "Action cannot be performed", + ctx: context); + } + } else { + GlobalMethod.showErrorDialog( + error: + "You cannot perform this action", + ctx: context); + } + getJobData(); + }, + child: const Text( + "ON", + style: TextStyle( + fontStyle: FontStyle.italic, + color: Colors.black, + fontSize: 18, + fontWeight: FontWeight.normal, + ), + ), + ), + Opacity( + opacity: recruitment == true ? 1 : 0, + child: const Icon( + Icons.check_box, + color: Colors.green, + ), + ), + const SizedBox( + width: 40, + ), + TextButton( + onPressed: () { + User? user = _auth.currentUser; + final _uid = user!.uid; + if (_uid == widget.uploadedBy) { + try { + FirebaseFirestore.instance + .collection("jobs") + .doc(widget.jobID) + .update( + {"recruitment": false}); + } catch (error) { + GlobalMethod.showErrorDialog( + error: + "Action cannot be performed", + ctx: context); + } + } else { + GlobalMethod.showErrorDialog( + error: + "You cannot perform this action", + ctx: context); + } + getJobData(); + }, + child: const Text( + "OFF", + style: TextStyle( + fontStyle: FontStyle.italic, + color: Colors.black, + fontSize: 18, + fontWeight: FontWeight.normal, + ), + ), + ), + Opacity( + opacity: recruitment == false ? 1 : 0, + child: const Icon( + Icons.check_box, + color: Colors.red, + ), + ), + ], + ), + ], + ), + dividerWidget(), + const Text( + "Job Description", + style: TextStyle( + fontSize: 18, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox( + height: 10, + ), + Text( + jobDescription == null ? "" : jobDescription!, + textAlign: TextAlign.justify, + style: const TextStyle( + fontSize: 14, + color: Colors.grey, + ), + ), + dividerWidget(), + ], + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.all(4.0), + child: Card( + color: Colors.black54, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 10, + ), + Center( + child: Text( + isDeadlineAvailable + ? "Actively Recruiting, Send CV/Resume:" + : "Deadline Passed away.", + style: TextStyle( + color: isDeadlineAvailable + ? Colors.green + : Colors.red, + fontWeight: FontWeight.normal, + fontSize: 16, + ), + ), + ), + const SizedBox( + height: 6, + ), + Center( + child: MaterialButton( + onPressed: () { + applyForJob(); + }, + color: Colors.blueAccent, + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(13), + ), + child: const Padding( + padding: EdgeInsets.symmetric(vertical: 14), + child: Text( + "Easy Apply Now", + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ), + ), + ), + dividerWidget(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + "Uploaded on:", + style: TextStyle( + color: Colors.white, + ), + ), + Text( + postedDate == null ? "" : postedDate!, + style: const TextStyle( + color: Colors.grey, + fontWeight: FontWeight.bold, + fontSize: 15, + ), + ), + ], + ), + const SizedBox( + height: 12, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + "Deadline:", + style: TextStyle( + color: Colors.white, + ), + ), + Text( + deadlineDate == null ? "" : deadlineDate!, + style: const TextStyle( + color: Colors.grey, + fontWeight: FontWeight.bold, + fontSize: 15, + ), + ), + ], + ), + dividerWidget(), + ], + ), + ), + ), + ), + Padding( + padding: EdgeInsets.all(4.0), + child: Card( + color: Colors.black54, + child: Padding( + padding: EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AnimatedSwitcher( + duration: Duration( + milliseconds: 500, + ), + child: _isCommenting + ? Row( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Flexible( + flex: 3, + child: TextField( + controller: _commentController, + style: const TextStyle( + color: Colors.white, + ), + maxLength: 200, + keyboardType: TextInputType.text, + maxLines: 6, + decoration: InputDecoration( + filled: true, + fillColor: Theme.of(context) + .scaffoldBackgroundColor, + enabledBorder: + const UnderlineInputBorder( + borderSide: BorderSide( + color: Colors.white), + ), + focusedBorder: + const OutlineInputBorder( + borderSide: BorderSide( + color: Colors.pink), + )), + ), + ), + Flexible( + child: Column( + children: [ + Padding( + padding: + const EdgeInsets.symmetric( + horizontal: 8), + child: MaterialButton( + onPressed: () {}, + color: Colors.blueAccent, + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(8), + ), + child: const Text( + "Post", + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ), + ), + TextButton( + onPressed: () {}, + child: const Text("Cancel"), + ) + ], + ), + ), + ], + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton( + onPressed: () {}, + icon: const Icon( + Icons.add_comment, + color: Colors.blueAccent, + size: 40, + ), + ), + const SizedBox( + width: 10, + ), + IconButton( + onPressed: () {}, + icon: const Icon( + Icons.arrow_drop_down_circle, + color: Colors.blueAccent, + size: 40, + ), + ), + ], + )), + ], + ), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 67a5ce4..6a99c0a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,39 +1,17 @@ name: cpd_ss23 description: A new Flutter project. -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. + publish_to: 'none' # Remove this line if you wish to publish to pub.dev -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -# In Windows, build-name is used as the major, minor, and patch parts -# of the product and file versions while build-number is used as the build suffix. version: 1.0.0+1 environment: sdk: '>=3.0.0-378.0.dev <4.0.0' -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. dependencies: flutter: sdk: flutter - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 cached_network_image: ^3.2.3 image_picker: ^0.8.7+3 @@ -54,25 +32,12 @@ dev_dependencies: flutter_test: sdk: flutter - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. flutter_lints: ^2.0.0 -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. flutter: - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true - # To add assets to your application, add an assets section, like this: assets: - assets/images/