Major Job Listings refactor

This commit is contained in:
2024-06-16 14:04:12 -05:00
parent 9076765aae
commit 32e3cc574c
11 changed files with 2147 additions and 956 deletions
+217 -148
View File
@@ -1,6 +1,8 @@
import 'package:fbla_ui/api_logic.dart';
import 'package:fbla_ui/main.dart';
import 'package:fbla_ui/pages/create_edit_business.dart';
import 'package:fbla_ui/pages/create_edit_listing.dart';
import 'package:fbla_ui/pages/listing_detail.dart';
import 'package:fbla_ui/pages/signin_page.dart';
import 'package:fbla_ui/shared.dart';
import 'package:flutter/material.dart';
@@ -111,167 +113,132 @@ class _CreateBusinessDetailState extends State<BusinessDetail> {
return ListView(
children: [
// Title, logo, desc, website
Card(
clipBehavior: Clip.antiAlias,
child: Column(
children: [
ListTile(
title: Text(business.name,
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 24, fontWeight: FontWeight.bold)),
subtitle: Text(
business.description,
textAlign: TextAlign.left,
),
leading: ClipRRect(
borderRadius: BorderRadius.circular(6.0),
child: Image.network('$apiAddress/logos/${business.id}',
width: 48,
height: 48, errorBuilder: (BuildContext context,
Object exception, StackTrace? stackTrace) {
return getIconFromJobType(widget.clickFromType, 48,
Theme.of(context).colorScheme.onSurface);
}),
),
),
ListTile(
leading: const Icon(Icons.link),
title: const Text('Website'),
subtitle: Text(business.website!,
style: const TextStyle(color: Colors.blue)),
onTap: () {
launchUrl(Uri.parse('https://${business.website}'));
},
),
],
),
),
// Available positions
Card(
child: Padding(
padding: const EdgeInsets.only(left: 16.0, top: 8.0),
child:
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
const Text(
'Available Postitions',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
// Container(
// height: 400,
// width: 300,
ListView(
scrollDirection: Axis.vertical,
shrinkWrap: true,
children: [
ListTile(
title: Text('Postition 1'),
leading: Icon(Icons.work),
onTap: () {
// launchUrl(Uri.parse(''));
},
),
ListTile(
title: Text('Postition 2'),
leading: Icon(Icons.work),
onTap: () {
// launchUrl(Uri.parse(''));
},
),
ListTile(
title: Text('Postition 3'),
leading: Icon(Icons.work),
onTap: () {
// launchUrl(Uri.parse(''));
},
),
],
),
]),
),
),
// Contact info
Visibility(
visible:
(business.contactEmail != null || business.contactPhone != null),
Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Card(
clipBehavior: Clip.antiAlias,
child: Column(
children: [
Row(
children: [
Padding(
padding: const EdgeInsets.only(left: 16.0, top: 8.0),
child: Text(
business.contactName ?? 'Contact ${business.name}',
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 20, fontWeight: FontWeight.bold),
),
),
],
),
Visibility(
visible: business.contactPhone != null,
child: ListTile(
leading: Icon(Icons.phone),
title: Text(business.contactPhone!),
// maybe replace ! with ?? ''. same is true for below
onTap: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor:
Theme.of(context).colorScheme.background,
title: Text(business.contactName!.isEmpty
? 'Contact ${business.name}?'
: 'Contact ${business.contactName}'),
content: Text(business.contactName!.isEmpty
? 'Would you like to call or text ${business.name}?'
: 'Would you like to call or text ${business.contactName}?'),
actions: [
TextButton(
child: const Text('Text'),
onPressed: () {
launchUrl(Uri.parse(
'sms:${business.contactPhone}'));
Navigator.of(context).pop();
}),
TextButton(
child: const Text('Call'),
onPressed: () async {
launchUrl(Uri.parse(
'tel:${business.contactPhone}'));
Navigator.of(context).pop();
}),
],
);
});
},
ListTile(
title: Text(business.name,
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 24, fontWeight: FontWeight.bold)),
subtitle: Text(
business.description,
textAlign: TextAlign.left,
),
leading: ClipRRect(
borderRadius: BorderRadius.circular(6.0),
child: Image.network('$apiAddress/logos/${business.id}',
width: 48,
height: 48, errorBuilder: (BuildContext context,
Object exception, StackTrace? stackTrace) {
return getIconFromJobType(widget.clickFromType, 48,
Theme.of(context).colorScheme.onSurface);
}),
),
),
Visibility(
visible: business.contactEmail != null,
child: ListTile(
leading: const Icon(Icons.email),
title: Text(business.contactEmail!),
onTap: () {
launchUrl(Uri.parse('mailto:${business.contactEmail}'));
},
),
ListTile(
leading: const Icon(Icons.link),
title: const Text('Website'),
subtitle: Text(
business.website
.replaceAll('https://', '')
.replaceAll('http://', '')
.replaceAll('www.', ''),
style: const TextStyle(color: Colors.blue)),
onTap: () {
launchUrl(Uri.parse(business.website));
},
),
],
),
),
),
// Available positions
Card(
clipBehavior: Clip.antiAlias,
child:
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Padding(
padding: const EdgeInsets.only(left: 16, top: 4),
child: _GetListingsTitle(business)),
_JobList(business: business)
]),
),
// Contact info
Card(
clipBehavior: Clip.antiAlias,
child: Column(
children: [
Row(
children: [
Padding(
padding: const EdgeInsets.only(left: 16.0, top: 8.0),
child: Text(
business.contactName!,
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 20, fontWeight: FontWeight.bold),
),
),
],
),
Visibility(
visible: business.contactPhone != null,
child: ListTile(
leading: const Icon(Icons.phone),
title: Text(business.contactPhone!),
// maybe replace ! with ?? ''. same is true for below
onTap: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor:
Theme.of(context).colorScheme.surface,
title: Text('Contact ${business.contactName}'),
content: Text(
'Would you like to call or text ${business.contactName}?'),
actions: [
TextButton(
child: const Text('Text'),
onPressed: () {
launchUrl(Uri.parse(
'sms:${business.contactPhone}'));
Navigator.of(context).pop();
}),
TextButton(
child: const Text('Call'),
onPressed: () async {
launchUrl(Uri.parse(
'tel:${business.contactPhone}'));
Navigator.of(context).pop();
}),
],
);
});
},
),
),
ListTile(
leading: const Icon(Icons.email),
title: Text(business.contactEmail),
onTap: () {
launchUrl(Uri.parse('mailto:${business.contactEmail}'));
},
),
],
),
),
// Location
Visibility(
child: Card(
clipBehavior: Clip.antiAlias,
child: ListTile(
leading: const Icon(Icons.location_on),
title: Text(business.locationName!),
title: Text(business.locationName),
subtitle: Text(business.locationAddress!),
onTap: () {
launchUrl(Uri.parse(Uri.encodeFull(
@@ -282,12 +249,12 @@ class _CreateBusinessDetailState extends State<BusinessDetail> {
),
// Notes
Visibility(
visible: business.notes != null,
visible: business.notes != null && business.notes != '',
child: Card(
child: ListTile(
leading: const Icon(Icons.notes),
title: const Text(
'Additional Notes:',
'Additional Notes',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
subtitle: Text(business.notes!),
@@ -318,7 +285,7 @@ class _CreateBusinessDetailState extends State<BusinessDetail> {
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Theme.of(context).colorScheme.background,
backgroundColor: Theme.of(context).colorScheme.surface,
title: const Text('Are You Sure?'),
content:
Text('This will permanently delete ${business.name}.'),
@@ -332,7 +299,7 @@ class _CreateBusinessDetailState extends State<BusinessDetail> {
child: const Text('Yes'),
onPressed: () async {
String? deleteResult =
await deleteBusiness(business.id, jwt);
await deleteBusiness(business.id);
if (deleteResult != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
@@ -356,3 +323,105 @@ class _CreateBusinessDetailState extends State<BusinessDetail> {
return null;
}
}
class _JobList extends StatelessWidget {
final Business business;
const _JobList({required this.business});
@override
Widget build(BuildContext context) {
List<_JobListItem> listItems = [];
for (JobListing listing in business.listings!) {
listItems.add(_JobListItem(
jobListing: listing,
fromBusiness: business,
));
}
return ListView(
shrinkWrap: true,
children: listItems,
);
}
}
class _JobListItem extends StatelessWidget {
final JobListing jobListing;
final Business fromBusiness;
const _JobListItem({required this.jobListing, required this.fromBusiness});
@override
Widget build(BuildContext context) {
return ListTile(
leading: getIconFromJobType(
jobListing.type, 24, Theme.of(context).colorScheme.onSurface),
title: Text(jobListing.name),
subtitle: Text(
jobListing.description,
style: const TextStyle(overflow: TextOverflow.ellipsis),
),
trailing: _getEditIcon(context, fromBusiness, jobListing),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => JobListingDetail(
listing: jobListing,
fromBusiness: fromBusiness,
)));
},
);
}
Widget? _getEditIcon(
BuildContext context, Business fromBusiness, JobListing inputListing) {
if (loggedIn) {
return IconButton(
icon: const Icon(
Icons.edit,
),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => CreateEditJobListing(
inputBusiness: fromBusiness,
inputJobListing: inputListing,
)));
},
);
}
return null;
}
}
class _GetListingsTitle extends StatelessWidget {
final Business fromBusiness;
const _GetListingsTitle(this.fromBusiness);
@override
Widget build(BuildContext context) {
if (!loggedIn) {
return const Text('Available Postitions',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold));
} else {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Available Postitions',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
Padding(
padding: const EdgeInsets.only(right: 24.0),
child: IconButton(
icon: const Icon(Icons.add),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => CreateEditJobListing(
inputBusiness: fromBusiness,
)));
},
),
)
],
);
}
}
}
+10 -52
View File
@@ -101,9 +101,9 @@ class _CreateEditBusinessState extends State<CreateEditBusiness> {
// business.contactName = 'Contact ${business.name}';
// }
if (widget.inputBusiness != null) {
result = await editBusiness(business, jwt);
result = await editBusiness(business);
} else {
result = await createBusiness(business, jwt);
result = await createBusiness(business);
}
setState(() {
_isLoading = false;
@@ -159,7 +159,7 @@ class _CreateEditBusinessState extends State<CreateEditBusiness> {
return getIconFromJobType(
widget.clickFromType ?? JobType.other,
48,
Theme.of(context).colorScheme.onBackground);
Theme.of(context).colorScheme.onSurface);
}),
),
),
@@ -202,13 +202,13 @@ class _CreateEditBusinessState extends State<CreateEditBusiness> {
AutovalidateMode.onUserInteraction,
keyboardType: TextInputType.url,
onChanged: (inputUrl) {
setState(() {
business.website = Uri.encodeFull(inputUrl
.toLowerCase()
.replaceAll('https://', '')
.replaceAll('http://', '')
.replaceAll('www.', ''));
});
business.website = Uri.encodeFull(inputUrl);
if (!business.website.contains('http://') &&
!business.website
.contains('https://')) {
business.website =
'https://${business.website}';
}
},
onTapOutside: (PointerDownEvent event) {
FocusScope.of(context).unfocus();
@@ -362,48 +362,6 @@ class _CreateEditBusinessState extends State<CreateEditBusiness> {
),
),
// Business Type Dropdown
// Padding(
// padding: const EdgeInsets.only(
// left: 8.0, right: 8.0, bottom: 8.0),
// child: Row(
// mainAxisAlignment:
// MainAxisAlignment.spaceBetween,
// children: [
// const Text('Type of Business',
// style: TextStyle(fontSize: 16)),
// DropdownMenu<BusinessType>(
// initialSelection: business.type,
// controller: businessTypeController,
// label: const Text('Business Type'),
// dropdownMenuEntries: const [
// DropdownMenuEntry(
// value: BusinessType.food,
// label: 'Food Related'),
// DropdownMenuEntry(
// value: BusinessType.shop,
// label: 'Shop'),
// DropdownMenuEntry(
// value: BusinessType.outdoors,
// label: 'Outdoors'),
// DropdownMenuEntry(
// value: BusinessType.manufacturing,
// label: 'Manufacturing'),
// DropdownMenuEntry(
// value: BusinessType.entertainment,
// label: 'Entertainment'),
// DropdownMenuEntry(
// value: BusinessType.other,
// label: 'Other'),
// ],
// onSelected: (inputType) {
// business.type = inputType!;
// },
// ),
// ],
// ),
// ),
Padding(
padding: const EdgeInsets.only(
left: 8.0, right: 8.0, bottom: 8.0),
+431
View File
@@ -0,0 +1,431 @@
import 'package:fbla_ui/api_logic.dart';
import 'package:fbla_ui/main.dart';
import 'package:fbla_ui/shared.dart';
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
class CreateEditJobListing extends StatefulWidget {
final JobListing? inputJobListing;
final Business inputBusiness;
const CreateEditJobListing(
{super.key, this.inputJobListing, required this.inputBusiness});
@override
State<CreateEditJobListing> createState() => _CreateEditJobListingState();
}
class _CreateEditJobListingState extends State<CreateEditJobListing> {
late Future getBusinessNameMapping;
late TextEditingController _nameController;
late TextEditingController _descriptionController;
late TextEditingController _wageController;
late TextEditingController _linkController;
List nameMapping = [];
String? businessErrorText;
JobListing listing = JobListing(
id: null,
businessId: null,
name: 'Job Listing',
description: 'Add details about the business below.',
type: JobType.other,
wage: null,
link: null);
bool _isLoading = false;
@override
void initState() {
super.initState();
if (widget.inputJobListing != null) {
listing = JobListing.copy(widget.inputJobListing!);
_nameController = TextEditingController(text: listing.name);
_descriptionController = TextEditingController(text: listing.description);
} else {
_nameController = TextEditingController();
_descriptionController = TextEditingController();
}
_wageController = TextEditingController(text: listing.wage);
_linkController = TextEditingController(text: listing.link);
getBusinessNameMapping = fetchBusinessNames();
}
final formKey = GlobalKey<FormState>();
final TextEditingController jobTypeController = TextEditingController();
final TextEditingController businessController = TextEditingController();
@override
Widget build(BuildContext context) {
listing.businessId = widget.inputBusiness.id;
return PopScope(
canPop: !_isLoading,
onPopInvoked: _handlePop,
child: Form(
key: formKey,
child: Scaffold(
appBar: AppBar(
title: (widget.inputJobListing != null)
? Text('Edit ${widget.inputJobListing?.name}', maxLines: 1)
: const Text('Add New Job Listing'),
),
floatingActionButton: FloatingActionButton(
child: _isLoading
? const Padding(
padding: EdgeInsets.all(16.0),
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 3.0,
),
)
: const Icon(Icons.save),
onPressed: () async {
if (formKey.currentState!.validate()) {
formKey.currentState?.save();
setState(() {
_isLoading = true;
});
String? result;
if (widget.inputJobListing != null) {
result = await editListing(listing);
} else {
result = await createListing(listing);
}
setState(() {
_isLoading = false;
});
if (result != null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
width: 400,
behavior: SnackBarBehavior.floating,
content: Text(result)));
} else {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const MainApp()));
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('Check field inputs!'),
width: 200,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
),
);
}
},
),
body: FutureBuilder(
future: getBusinessNameMapping,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData) {
if (snapshot.data.runtimeType == String) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(children: [
Center(
child: Text(snapshot.data,
textAlign: TextAlign.center)),
Padding(
padding: const EdgeInsets.all(8.0),
child: FilledButton(
child: const Text('Retry'),
onPressed: () async {
var refreshedData = fetchBusinessNames();
await refreshedData;
setState(() {
getBusinessNameMapping = refreshedData;
});
},
),
),
]),
);
}
nameMapping = snapshot.data;
return ListView(
children: [
Center(
child: SizedBox(
width: 1000,
child: Column(
children: [
ListTile(
title: Text(listing.name,
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold)),
subtitle: Text(
listing.description,
textAlign: TextAlign.left,
),
leading: ClipRRect(
borderRadius: BorderRadius.circular(6.0),
child: Image.network(
width: 48,
height: 48,
listing.businessId != null
? '$apiAddress/logos/${listing.businessId}'
: '',
errorBuilder: (BuildContext context,
Object exception,
StackTrace? stackTrace) {
return getIconFromJobType(
listing.type,
48,
Theme.of(context)
.colorScheme
.onSurface);
}),
),
),
// Business Type Dropdown
Card(
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(
left: 8.0,
right: 8.0,
bottom: 8.0,
top: 8),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
const Text('Type of Job',
style:
TextStyle(fontSize: 16)),
DropdownMenu<JobType>(
initialSelection: listing.type,
controller: jobTypeController,
label: const Text('Job Type'),
dropdownMenuEntries: [
for (JobType type
in JobType.values)
DropdownMenuEntry(
value: type,
label:
getNameFromJobType(
type))
],
onSelected: (inputType) {
setState(() {
listing.type = inputType!;
});
},
),
],
),
),
Padding(
padding: const EdgeInsets.only(
left: 8.0,
right: 8.0,
bottom: 8.0,
top: 8),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
const Text(
'Business that has the job',
style:
TextStyle(fontSize: 16)),
DropdownMenu<int>(
initialSelection:
widget.inputBusiness.id,
controller: businessController,
label: const Text('Business'),
dropdownMenuEntries: [
for (Map<String, dynamic> map
in nameMapping)
DropdownMenuEntry(
value: map['id']!,
label: map['name'])
],
onSelected: (inputType) {
setState(() {
listing.businessId =
inputType!;
});
},
),
],
),
),
Padding(
padding: const EdgeInsets.only(
left: 8.0, right: 8.0),
child: TextFormField(
controller: _nameController,
autovalidateMode: AutovalidateMode
.onUserInteraction,
maxLength: 30,
onChanged: (inputName) {
setState(() {
listing.name = inputName;
});
},
onTapOutside:
(PointerDownEvent event) {
FocusScope.of(context).unfocus();
},
decoration: const InputDecoration(
labelText:
'Job Listing Name (required)',
),
validator: (value) {
if (value != null &&
value.isEmpty) {
return 'Name is required';
}
return null;
},
),
),
Padding(
padding: const EdgeInsets.only(
left: 8.0, right: 8.0),
child: TextFormField(
controller: _descriptionController,
autovalidateMode: AutovalidateMode
.onUserInteraction,
maxLength: 500,
maxLines: null,
onChanged: (inputDesc) {
setState(() {
listing.description = inputDesc;
});
},
onTapOutside:
(PointerDownEvent event) {
FocusScope.of(context).unfocus();
},
decoration: const InputDecoration(
labelText:
'Job Listing Description (required)',
),
validator: (value) {
if (value != null &&
value.isEmpty) {
return 'Description is required';
}
return null;
},
),
),
Padding(
padding: const EdgeInsets.only(
left: 8.0,
right: 8.0,
bottom: 8.0),
child: TextFormField(
controller: _wageController,
onChanged: (input) {
setState(() {
listing.wage = input;
});
},
onTapOutside:
(PointerDownEvent event) {
FocusScope.of(context).unfocus();
},
decoration: const InputDecoration(
labelText: 'Wage Information',
),
),
),
Padding(
padding: const EdgeInsets.only(
left: 8.0,
right: 8.0,
bottom: 8.0),
child: TextFormField(
controller: _linkController,
autovalidateMode: AutovalidateMode
.onUserInteraction,
keyboardType: TextInputType.url,
onChanged: (inputUrl) {
if (listing.link != null &&
listing.link != '') {
listing.link =
Uri.encodeFull(inputUrl);
if (!listing.link!
.contains('http://') &&
!listing.link!
.contains('https://')) {
listing.link =
'https://${listing.link}';
}
}
},
onTapOutside:
(PointerDownEvent event) {
FocusScope.of(context).unfocus();
},
decoration: const InputDecoration(
labelText:
'Additional Information Link',
),
),
),
],
),
),
const SizedBox(
height: 75,
)
],
),
),
),
],
);
} else if (snapshot.hasError) {
return Padding(
padding: const EdgeInsets.only(left: 16.0, right: 16.0),
child: Text(
'Error when loading data! Error: ${snapshot.error}'),
);
}
} else if (snapshot.connectionState ==
ConnectionState.waiting) {
return Container(
padding: const EdgeInsets.all(8.0),
alignment: Alignment.center,
child: const SizedBox(
width: 75,
height: 75,
child: RiveAnimation.asset(
'assets/mdev_triangle_loading.riv'),
),
);
}
return const Padding(
padding: EdgeInsets.only(left: 16.0, right: 16.0),
child: Text('Error when loading data!'),
);
}),
)),
);
}
void _handlePop(bool didPop) {
if (!didPop) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
width: 400,
behavior: SnackBarBehavior.floating,
content: Text('Please wait for it to save.'),
),
);
}
}
}
File diff suppressed because it is too large Load Diff
+221
View File
@@ -0,0 +1,221 @@
import 'package:fbla_ui/api_logic.dart';
import 'package:fbla_ui/main.dart';
import 'package:fbla_ui/pages/create_edit_listing.dart';
import 'package:fbla_ui/pages/signin_page.dart';
import 'package:fbla_ui/shared.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class JobListingDetail extends StatefulWidget {
final JobListing listing;
final Business fromBusiness;
const JobListingDetail(
{super.key, required this.listing, required this.fromBusiness});
@override
State<JobListingDetail> createState() => _CreateBusinessDetailState();
}
class _CreateBusinessDetailState extends State<JobListingDetail> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.listing.name),
actions: _getActions(widget.listing, widget.fromBusiness),
),
body: _detailBody(widget.listing),
);
}
ListView _detailBody(JobListing listing) {
return ListView(
children: [
// Title, logo, desc, website
Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Card(
clipBehavior: Clip.antiAlias,
child: Column(
children: [
ListTile(
title: Text(listing.name,
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 24, fontWeight: FontWeight.bold)),
subtitle: Text(
listing.description,
textAlign: TextAlign.left,
),
leading: ClipRRect(
borderRadius: BorderRadius.circular(6.0),
child: Image.network(
'$apiAddress/logos/${listing.businessId}',
width: 48,
height: 48, errorBuilder: (BuildContext context,
Object exception, StackTrace? stackTrace) {
return getIconFromJobType(listing.type, 48,
Theme.of(context).colorScheme.onSurface);
}),
),
),
Visibility(
visible: listing.link != null && listing.link != '',
child: ListTile(
leading: const Icon(Icons.link),
title: const Text('More Information'),
subtitle: Text(
listing.link!
.replaceAll('https://', '')
.replaceAll('http://', '')
.replaceAll('www.', ''),
style: const TextStyle(color: Colors.blue)),
onTap: () {
launchUrl(Uri.parse(listing.link!));
},
),
),
],
),
),
),
// Wage
Visibility(
visible: listing.wage != null && listing.wage != '',
child: Card(
child: ListTile(
leading: const Icon(Icons.attach_money),
subtitle: Text(listing.wage!),
title: const Text('Wage Information'),
),
),
),
Card(
clipBehavior: Clip.antiAlias,
child: Column(
children: [
Row(
children: [
Padding(
padding: const EdgeInsets.only(left: 16.0, top: 8.0),
child: Text(
widget.fromBusiness.contactName!,
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 20, fontWeight: FontWeight.bold),
),
),
],
),
Visibility(
visible: widget.fromBusiness.contactPhone != null,
child: ListTile(
leading: const Icon(Icons.phone),
title: Text(widget.fromBusiness.contactPhone!),
// maybe replace ! with ?? ''. same is true for below
onTap: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor:
Theme.of(context).colorScheme.surface,
title: Text(
'Contact ${widget.fromBusiness.contactName}'),
content: Text(
'Would you like to call or text ${widget.fromBusiness.contactName}?'),
actions: [
TextButton(
child: const Text('Text'),
onPressed: () {
launchUrl(Uri.parse(
'sms:${widget.fromBusiness.contactPhone}'));
Navigator.of(context).pop();
}),
TextButton(
child: const Text('Call'),
onPressed: () async {
launchUrl(Uri.parse(
'tel:${widget.fromBusiness.contactPhone}'));
Navigator.of(context).pop();
}),
],
);
});
},
),
),
ListTile(
leading: const Icon(Icons.email),
title: Text(widget.fromBusiness.contactEmail),
onTap: () {
launchUrl(
Uri.parse('mailto:${widget.fromBusiness.contactEmail}'));
},
),
],
),
),
],
);
}
List<Widget>? _getActions(JobListing listing, Business fromBusiness) {
if (loggedIn) {
return [
IconButton(
icon: const Icon(Icons.edit),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => CreateEditJobListing(
inputJobListing: listing,
inputBusiness: fromBusiness,
)));
},
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: Theme.of(context).colorScheme.surface,
title: const Text('Are You Sure?'),
content:
Text('This will permanently delete ${listing.name}.'),
actions: [
TextButton(
child: const Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
}),
TextButton(
child: const Text('Yes'),
onPressed: () async {
String? deleteResult =
await deleteListing(listing.id!);
if (deleteResult != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
width: 300,
behavior: SnackBarBehavior.floating,
content: Text(deleteResult)));
} else {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const MainApp()));
}
}),
],
);
});
},
),
];
}
return null;
}
}
+1 -2
View File
@@ -1,5 +1,4 @@
import 'package:fbla_ui/api_logic.dart';
import 'package:fbla_ui/home.dart';
import 'package:fbla_ui/shared.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -7,7 +6,7 @@ import 'package:shared_preferences/shared_preferences.dart';
bool loggedIn = false;
class SignInPage extends StatefulWidget {
final Callback refreshAccount;
final void Function() refreshAccount;
const SignInPage({super.key, required this.refreshAccount});