Update iOS deployment target to 13.0, enhance Flutter integration, and improve tender submission flow
- Changed the iOS deployment target from 12.0 to 13.0 in Podfile and project settings. - Updated AppDelegate to support implicit Flutter engine initialization. - Modified Info.plist to include scene configuration for multiple scenes. - Enhanced CompletionOfDocumentsRouteData to accept a tenderId parameter. - Implemented tender submission functionality in TenderDetailViewModel and updated UI components to reflect loading states and success/error messages. - Added new strings for submission success and error notifications.
This commit is contained in:
@@ -250,11 +250,14 @@ class FinalCompletionOfDocumentsRouteData extends GoRouteData
|
||||
@TypedGoRoute<CompletionOfDocumentsRouteData>(path: '/completion_of_documents')
|
||||
class CompletionOfDocumentsRouteData extends GoRouteData
|
||||
with _$CompletionOfDocumentsRouteData {
|
||||
const CompletionOfDocumentsRouteData();
|
||||
final String? tenderId;
|
||||
const CompletionOfDocumentsRouteData({this.tenderId});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return const CompletionOfDocumentsScreen();
|
||||
return tenderDetailProvider(
|
||||
child: CompletionOfDocumentsScreen(tenderId: tenderId),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -308,10 +308,20 @@ RouteBase get $completionOfDocumentsRouteData => GoRouteData.$route(
|
||||
|
||||
mixin _$CompletionOfDocumentsRouteData on GoRouteData {
|
||||
static CompletionOfDocumentsRouteData _fromState(GoRouterState state) =>
|
||||
const CompletionOfDocumentsRouteData();
|
||||
CompletionOfDocumentsRouteData(
|
||||
tenderId: state.uri.queryParameters['tender-id'],
|
||||
);
|
||||
|
||||
CompletionOfDocumentsRouteData get _self =>
|
||||
this as CompletionOfDocumentsRouteData;
|
||||
|
||||
@override
|
||||
String get location => GoRouteData.$location('/completion_of_documents');
|
||||
String get location => GoRouteData.$location(
|
||||
'/completion_of_documents',
|
||||
queryParams: {
|
||||
if (_self.tenderId != null) 'tender-id': _self.tenderId!,
|
||||
},
|
||||
);
|
||||
|
||||
@override
|
||||
void go(BuildContext context) => context.go(location);
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:tm_app/data/services/model/tender_data/tender_data.dart';
|
||||
import 'package:tm_app/data/services/model/tender_detail_response/tender_detail_response_model.dart';
|
||||
|
||||
import '../core/constants/tender_approval_status.dart';
|
||||
import '../core/constants/tender_submision_mode.dart';
|
||||
import '../data/services/model/tender_approvals_by_id_response/tender_approvals_by_id_response.dart';
|
||||
import '../data/services/model/tender_approvals_data/tender_approvals_data.dart';
|
||||
import '../data/services/model/tender_approvals_toggle_response/tender_approvals_toggle_response.dart';
|
||||
@@ -143,4 +144,33 @@ class TenderDetailViewModel with ChangeNotifier {
|
||||
_errorMessage = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Submits the tender for completion via the self-apply flow.
|
||||
/// POSTs to /api/v1/tender-approvals with status "submitted" and
|
||||
/// submission_mode "self-apply". Returns true on success.
|
||||
Future<bool> submitForCompletion({required String tenderId}) async {
|
||||
_isSubmitApprovalLoading = true;
|
||||
_errorMessage = null;
|
||||
notifyListeners();
|
||||
|
||||
bool success = false;
|
||||
final result = await _tendersRepository.toggleApprovals(
|
||||
tenderId: tenderId,
|
||||
status: TenderApprovalStatus.submitted.value,
|
||||
submissionMode: TenderSubmissionMode.selfApply.value,
|
||||
);
|
||||
switch (result) {
|
||||
case Ok<TenderApprovalsToggleResponse>():
|
||||
success = true;
|
||||
_status = result.value.data?.status ?? '';
|
||||
break;
|
||||
case Error<TenderApprovalsToggleResponse>():
|
||||
_errorMessage = result.error.toString();
|
||||
break;
|
||||
}
|
||||
|
||||
_isSubmitApprovalLoading = false;
|
||||
notifyListeners();
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +206,9 @@ class YourTendersViewModel with ChangeNotifier {
|
||||
offset: _tendersOffset,
|
||||
);
|
||||
await getTenders(requestModel: requestModel);
|
||||
} else if (selectedStatus == YourTendersStrings.tendersSubmitted) {
|
||||
} else if (selectedStatus == YourTendersStrings.tendersSubmitted ||
|
||||
selectedStatus == TenderApprovalStatus.submitted.value ||
|
||||
selectedStatus == TenderApprovalStatus.approved.value) {
|
||||
final requestModel = GetTendersRequestModel(
|
||||
status: TenderApprovalStatus.submitted.value,
|
||||
createdFrom: startDateUnix,
|
||||
@@ -299,10 +301,17 @@ class YourTendersViewModel with ChangeNotifier {
|
||||
reset: true,
|
||||
);
|
||||
} else if (selectedStatus == TenderApprovalStatus.approved.value) {
|
||||
await getTendersFeedback(
|
||||
feedbackType: TenderApprovalStatus.approved.value,
|
||||
reset: true,
|
||||
// "Approved tenders" maps to submitted tender-approvals. The API has no
|
||||
// `approved` value; approved tenders are the ones a company has submitted
|
||||
// for pursuit (GET /api/v1/tender-approvals?status=submitted).
|
||||
final requestModel = GetTendersRequestModel(
|
||||
status: TenderApprovalStatus.submitted.value,
|
||||
createdFrom: startDateUnix,
|
||||
createdTo: endDateUnix,
|
||||
limit: _tendersLimit,
|
||||
offset: _tendersOffset,
|
||||
);
|
||||
await getTenders(requestModel: requestModel, reset: true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ import '../../../core/utils/size_config.dart';
|
||||
import '../../shared/responsive_builder.dart';
|
||||
|
||||
class CompletionOfDocumentsScreen extends StatefulWidget {
|
||||
const CompletionOfDocumentsScreen({super.key});
|
||||
final String? tenderId;
|
||||
const CompletionOfDocumentsScreen({this.tenderId, super.key});
|
||||
|
||||
@override
|
||||
State<CompletionOfDocumentsScreen> createState() => _CompletionOfDocumentsScreenState();
|
||||
@@ -41,10 +42,10 @@ class _CompletionOfDocumentsScreenState extends State<CompletionOfDocumentsScree
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SizeConfig.init(context);
|
||||
return const ResponsiveBuilder(
|
||||
mobile: CompletionOfDocumentsMobilePage(),
|
||||
tablet: CompletionOfDocumentsTabletPage(),
|
||||
desktop: CompletionOfDocumentsDesktopPage(),
|
||||
return ResponsiveBuilder(
|
||||
mobile: CompletionOfDocumentsMobilePage(tenderId: widget.tenderId),
|
||||
tablet: CompletionOfDocumentsTabletPage(tenderId: widget.tenderId),
|
||||
desktop: CompletionOfDocumentsDesktopPage(tenderId: widget.tenderId),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@ import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:tm_app/core/constants/assets.dart';
|
||||
import 'package:tm_app/core/theme/colors.dart';
|
||||
import 'package:tm_app/core/utils/size_config.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tm_app/core/utils/app_toast.dart';
|
||||
import 'package:tm_app/view_models/tender_detail_view_model.dart';
|
||||
import 'package:tm_app/views/completion_of_documents/strings/completion_of_documents_string.dart';
|
||||
import 'package:tm_app/views/shared/base_button.dart';
|
||||
import 'package:tm_app/views/shared/desktop_navigation_widget.dart';
|
||||
@@ -10,7 +14,8 @@ import 'package:tm_app/views/shared/tablet_desktop_appbar.dart'
|
||||
show TabletDesktopAppbar;
|
||||
|
||||
class CompletionOfDocumentsDesktopPage extends StatelessWidget {
|
||||
const CompletionOfDocumentsDesktopPage({super.key});
|
||||
final String? tenderId;
|
||||
const CompletionOfDocumentsDesktopPage({this.tenderId, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -77,14 +82,17 @@ class CompletionOfDocumentsDesktopPage extends StatelessWidget {
|
||||
|
||||
SizedBox(height: 24.0.h()),
|
||||
|
||||
BaseButton(
|
||||
isEnabled: true,
|
||||
onPressed: () {},
|
||||
text:
|
||||
CompletionOfDocumentsStrings
|
||||
.submitForCompletion,
|
||||
textColor: AppColors.textBlue,
|
||||
backgroundColor: AppColors.primary30,
|
||||
Consumer<TenderDetailViewModel>(
|
||||
builder: (context, viewModel, _) => BaseButton(
|
||||
isEnabled: !viewModel.isSubmitApprovalLoading,
|
||||
isLoading: viewModel.isSubmitApprovalLoading,
|
||||
onPressed: () => _submitForCompletion(context),
|
||||
text:
|
||||
CompletionOfDocumentsStrings
|
||||
.submitForCompletion,
|
||||
textColor: AppColors.textBlue,
|
||||
backgroundColor: AppColors.primary30,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24.0.h()),
|
||||
],
|
||||
@@ -304,6 +312,26 @@ class CompletionOfDocumentsDesktopPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _submitForCompletion(BuildContext context) async {
|
||||
final id = tenderId;
|
||||
if (id == null || id.isEmpty) {
|
||||
AppToast.error(context, CompletionOfDocumentsStrings.submitError);
|
||||
return;
|
||||
}
|
||||
final viewModel = context.read<TenderDetailViewModel>();
|
||||
final success = await viewModel.submitForCompletion(tenderId: id);
|
||||
if (!context.mounted) return;
|
||||
if (success) {
|
||||
AppToast.success(context, CompletionOfDocumentsStrings.submitSuccess);
|
||||
context.pop();
|
||||
} else {
|
||||
AppToast.error(
|
||||
context,
|
||||
viewModel.errorMessage ?? CompletionOfDocumentsStrings.submitError,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _docCard({
|
||||
required String title,
|
||||
required String subtitle,
|
||||
|
||||
@@ -3,13 +3,18 @@ import 'package:flutter_svg/svg.dart';
|
||||
import 'package:tm_app/core/constants/assets.dart';
|
||||
import 'package:tm_app/core/theme/colors.dart';
|
||||
import 'package:tm_app/core/utils/size_config.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tm_app/core/utils/app_toast.dart';
|
||||
import 'package:tm_app/view_models/tender_detail_view_model.dart';
|
||||
import 'package:tm_app/views/completion_of_documents/strings/completion_of_documents_string.dart';
|
||||
import 'package:tm_app/views/shared/base_button.dart';
|
||||
|
||||
import '../../shared/tender_app_bar.dart';
|
||||
|
||||
class CompletionOfDocumentsMobilePage extends StatelessWidget {
|
||||
const CompletionOfDocumentsMobilePage({super.key});
|
||||
final String? tenderId;
|
||||
const CompletionOfDocumentsMobilePage({this.tenderId, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -242,12 +247,15 @@ class CompletionOfDocumentsMobilePage extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24.0.h()),
|
||||
BaseButton(
|
||||
isEnabled: true,
|
||||
onPressed: () {},
|
||||
text: CompletionOfDocumentsStrings.submitForCompletion,
|
||||
textColor: AppColors.textBlue,
|
||||
backgroundColor: AppColors.primary30,
|
||||
Consumer<TenderDetailViewModel>(
|
||||
builder: (context, viewModel, _) => BaseButton(
|
||||
isEnabled: !viewModel.isSubmitApprovalLoading,
|
||||
isLoading: viewModel.isSubmitApprovalLoading,
|
||||
onPressed: () => _submitForCompletion(context),
|
||||
text: CompletionOfDocumentsStrings.submitForCompletion,
|
||||
textColor: AppColors.textBlue,
|
||||
backgroundColor: AppColors.primary30,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -256,6 +264,26 @@ class CompletionOfDocumentsMobilePage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _submitForCompletion(BuildContext context) async {
|
||||
final id = tenderId;
|
||||
if (id == null || id.isEmpty) {
|
||||
AppToast.error(context, CompletionOfDocumentsStrings.submitError);
|
||||
return;
|
||||
}
|
||||
final viewModel = context.read<TenderDetailViewModel>();
|
||||
final success = await viewModel.submitForCompletion(tenderId: id);
|
||||
if (!context.mounted) return;
|
||||
if (success) {
|
||||
AppToast.success(context, CompletionOfDocumentsStrings.submitSuccess);
|
||||
context.pop();
|
||||
} else {
|
||||
AppToast.error(
|
||||
context,
|
||||
viewModel.errorMessage ?? CompletionOfDocumentsStrings.submitError,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _docCard({
|
||||
required String title,
|
||||
required String subtitle,
|
||||
|
||||
@@ -3,6 +3,10 @@ import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:tm_app/core/constants/assets.dart';
|
||||
import 'package:tm_app/core/theme/colors.dart';
|
||||
import 'package:tm_app/core/utils/size_config.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tm_app/core/utils/app_toast.dart';
|
||||
import 'package:tm_app/view_models/tender_detail_view_model.dart';
|
||||
import 'package:tm_app/views/completion_of_documents/strings/completion_of_documents_string.dart';
|
||||
import 'package:tm_app/views/shared/base_button.dart';
|
||||
import 'package:tm_app/views/shared/tablet_desktop_appbar.dart';
|
||||
@@ -10,7 +14,8 @@ import 'package:tm_app/views/shared/tablet_navigation_widget.dart';
|
||||
import 'package:tm_app/views/shared/tender_app_bar.dart';
|
||||
|
||||
class CompletionOfDocumentsTabletPage extends StatelessWidget {
|
||||
const CompletionOfDocumentsTabletPage({super.key});
|
||||
final String? tenderId;
|
||||
const CompletionOfDocumentsTabletPage({this.tenderId, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -83,14 +88,17 @@ class CompletionOfDocumentsTabletPage extends StatelessWidget {
|
||||
|
||||
SizedBox(height: 24.0.h()),
|
||||
|
||||
BaseButton(
|
||||
isEnabled: true,
|
||||
onPressed: () {},
|
||||
text:
|
||||
CompletionOfDocumentsStrings
|
||||
.submitForCompletion,
|
||||
textColor: AppColors.textBlue,
|
||||
backgroundColor: AppColors.primary30,
|
||||
Consumer<TenderDetailViewModel>(
|
||||
builder: (context, viewModel, _) => BaseButton(
|
||||
isEnabled: !viewModel.isSubmitApprovalLoading,
|
||||
isLoading: viewModel.isSubmitApprovalLoading,
|
||||
onPressed: () => _submitForCompletion(context),
|
||||
text:
|
||||
CompletionOfDocumentsStrings
|
||||
.submitForCompletion,
|
||||
textColor: AppColors.textBlue,
|
||||
backgroundColor: AppColors.primary30,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -309,6 +317,26 @@ class CompletionOfDocumentsTabletPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _submitForCompletion(BuildContext context) async {
|
||||
final id = tenderId;
|
||||
if (id == null || id.isEmpty) {
|
||||
AppToast.error(context, CompletionOfDocumentsStrings.submitError);
|
||||
return;
|
||||
}
|
||||
final viewModel = context.read<TenderDetailViewModel>();
|
||||
final success = await viewModel.submitForCompletion(tenderId: id);
|
||||
if (!context.mounted) return;
|
||||
if (success) {
|
||||
AppToast.success(context, CompletionOfDocumentsStrings.submitSuccess);
|
||||
context.pop();
|
||||
} else {
|
||||
AppToast.error(
|
||||
context,
|
||||
viewModel.errorMessage ?? CompletionOfDocumentsStrings.submitError,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------- Document Card --------------------
|
||||
Widget _docCard({
|
||||
required String title,
|
||||
|
||||
@@ -7,4 +7,6 @@ class CompletionOfDocumentsStrings {
|
||||
'Set Deadline for Completion of documents :';
|
||||
static const String note = 'Note:';
|
||||
static const String submitForCompletion = 'Submit for completion';
|
||||
static const String submitSuccess = 'Tender submitted for completion';
|
||||
static const String submitError = 'Failed to submit tender for completion';
|
||||
}
|
||||
|
||||
@@ -75,9 +75,9 @@ class _TenderDetailActionsState extends State<TenderDetailActions> {
|
||||
// submissionMode: value,
|
||||
// );
|
||||
|
||||
const CompletionOfDocumentsRouteData().push(
|
||||
context,
|
||||
);
|
||||
CompletionOfDocumentsRouteData(
|
||||
tenderId: widget.detail.id!,
|
||||
).push(context);
|
||||
} else if (value ==
|
||||
TenderSubmissionMode.partnership.value) {
|
||||
Future.delayed(Duration.zero, () {
|
||||
@@ -213,8 +213,9 @@ class _TenderDetailActionsState extends State<TenderDetailActions> {
|
||||
const FinalCompletionOfDocumentsRouteData()
|
||||
.push(context);
|
||||
} else {
|
||||
const CompletionOfDocumentsRouteData()
|
||||
.push(context);
|
||||
CompletionOfDocumentsRouteData(
|
||||
tenderId: widget.detail.id!,
|
||||
).push(context);
|
||||
}
|
||||
}
|
||||
} else if (value ==
|
||||
|
||||
@@ -23,4 +23,5 @@ class ProfileStrings {
|
||||
static const String mainDataTab = 'Main Data';
|
||||
static const String membersTab = 'Members';
|
||||
static const String subjectTab = 'Subject';
|
||||
static const String appVersion = 'Version 1.0.7';
|
||||
}
|
||||
|
||||
@@ -98,6 +98,15 @@ class MainDataTab extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24.0.h()),
|
||||
Text(
|
||||
ProfileStrings.appVersion,
|
||||
style: TextStyle(
|
||||
fontSize: 12.0.sp(),
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.grey70,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user