3rd commit
This commit is contained in:
52
lib/support/blocs/help_support_bloc.dart
Normal file
52
lib/support/blocs/help_support_bloc.dart
Normal file
@@ -0,0 +1,52 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../viewmodel/support_model.dart';
|
||||
|
||||
|
||||
abstract class HelpSupportEvent {}
|
||||
|
||||
class LoadFAQ extends HelpSupportEvent {}
|
||||
|
||||
class OpenQuestion extends HelpSupportEvent {
|
||||
final SupportItem item;
|
||||
OpenQuestion(this.item);
|
||||
}
|
||||
|
||||
class HelpSupportState {
|
||||
final List<SupportItem> faqs;
|
||||
final bool isLoading;
|
||||
|
||||
HelpSupportState({required this.faqs, required this.isLoading});
|
||||
|
||||
factory HelpSupportState.initial() => HelpSupportState(faqs: [], isLoading: false);
|
||||
|
||||
HelpSupportState copyWith({
|
||||
List<SupportItem>? faqs,
|
||||
bool? isLoading,
|
||||
}) {
|
||||
return HelpSupportState(
|
||||
faqs: faqs ?? this.faqs,
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HelpSupportBloc extends Bloc<HelpSupportEvent, HelpSupportState> {
|
||||
HelpSupportBloc() : super(HelpSupportState.initial()) {
|
||||
on<LoadFAQ>(_onLoadFAQ);
|
||||
}
|
||||
|
||||
Future<void> _onLoadFAQ(LoadFAQ event, Emitter<HelpSupportState> emit) async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
final data = [
|
||||
SupportItem("How to redeem passes?", "assets/icon/material-symbols_policy-rounded.png", "Guide"),
|
||||
SupportItem("Refund Policy", "assets/icon/mingcute_information-fill.png", "Policy"),
|
||||
SupportItem("Payment Issues", "assets/icon/solar_dollar-bold.png", "Billing and Payments"),
|
||||
SupportItem("How to redeem passes?", "assets/icon/material-symbols_policy-rounded.png", "Guide"),
|
||||
SupportItem("Refund Policy", "assets/icon/mingcute_information-fill.png", "Policy"),
|
||||
SupportItem("Payment Issues", "assets/icon/solar_dollar-bold.png", "Billing and Payments"),
|
||||
];
|
||||
emit(state.copyWith(isLoading: false, faqs: data));
|
||||
}
|
||||
}
|
||||
75
lib/support/blocs/ticket_bloc.dart
Normal file
75
lib/support/blocs/ticket_bloc.dart
Normal file
@@ -0,0 +1,75 @@
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
abstract class TicketEvent {}
|
||||
|
||||
class SubjectChanged extends TicketEvent {
|
||||
final String subject;
|
||||
SubjectChanged(this.subject);
|
||||
}
|
||||
|
||||
class DescriptionChanged extends TicketEvent {
|
||||
final String description;
|
||||
DescriptionChanged(this.description);
|
||||
}
|
||||
|
||||
class UploadFile extends TicketEvent {}
|
||||
|
||||
class SubmitTicket extends TicketEvent {}
|
||||
|
||||
class TicketState {
|
||||
final String subject;
|
||||
final String description;
|
||||
final PlatformFile? selectedFile;
|
||||
final bool isLoading;
|
||||
|
||||
const TicketState({
|
||||
required this.subject,
|
||||
required this.description,
|
||||
required this.selectedFile,
|
||||
required this.isLoading,
|
||||
});
|
||||
|
||||
factory TicketState.initial() => const TicketState(
|
||||
subject: '',
|
||||
description: '',
|
||||
selectedFile: null,
|
||||
isLoading: false,
|
||||
);
|
||||
|
||||
TicketState copyWith({
|
||||
String? subject,
|
||||
String? description,
|
||||
PlatformFile? selectedFile,
|
||||
bool? isLoading,
|
||||
}) {
|
||||
return TicketState(
|
||||
subject: subject ?? this.subject,
|
||||
description: description ?? this.description,
|
||||
selectedFile: selectedFile ?? this.selectedFile,
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TicketBloc extends Bloc<TicketEvent, TicketState> {
|
||||
TicketBloc() : super(TicketState.initial()) {
|
||||
on<SubjectChanged>((event, emit) => emit(state.copyWith(subject: event.subject)));
|
||||
on<DescriptionChanged>((event, emit) => emit(state.copyWith(description: event.description)));
|
||||
on<UploadFile>(_onUploadFile);
|
||||
on<SubmitTicket>(_onSubmitTicket);
|
||||
}
|
||||
|
||||
Future<void> _onUploadFile(UploadFile event, Emitter<TicketState> emit) async {
|
||||
final result = await FilePicker.platform.pickFiles();
|
||||
if (result != null && result.files.isNotEmpty) {
|
||||
emit(state.copyWith(selectedFile: result.files.first));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onSubmitTicket(SubmitTicket event, Emitter<TicketState> emit) async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
await Future.delayed(const Duration(seconds: 2)); // mock API call
|
||||
emit(state.copyWith(isLoading: false));
|
||||
}
|
||||
}
|
||||
146
lib/support/view/help_support_page.dart
Normal file
146
lib/support/view/help_support_page.dart
Normal file
@@ -0,0 +1,146 @@
|
||||
import 'package:citycards_partner_flutter/support/view/support_form_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../blocs/help_support_bloc.dart';
|
||||
|
||||
|
||||
class HelpSupportPage extends StatelessWidget {
|
||||
const HelpSupportPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (_) => HelpSupportBloc()..add(LoadFAQ()),
|
||||
child: const _HelpSupportView(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HelpSupportView extends StatelessWidget {
|
||||
const _HelpSupportView();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar:
|
||||
|
||||
AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
leading: Padding(
|
||||
padding: EdgeInsetsGeometry.symmetric(horizontal: 8),
|
||||
child: Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFF06969),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.arrow_back, color: Colors.white),
|
||||
),
|
||||
),
|
||||
centerTitle: true,
|
||||
title: Text("Help & Support",
|
||||
style: GoogleFonts.poppins(fontWeight: FontWeight.w700, color: Colors.black)),
|
||||
),
|
||||
body: BlocBuilder<HelpSupportBloc, HelpSupportState>(
|
||||
builder: (context, state) {
|
||||
if (state.isLoading) {
|
||||
return const Center(child: CircularProgressIndicator(color: Color(0xffF95F62)));
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text("Get assistance with your CityCards experience",
|
||||
style: GoogleFonts.poppins(color: Colors.black54)),
|
||||
const SizedBox(height: 16),
|
||||
Text("Common Questions",
|
||||
style: GoogleFonts.poppins(fontWeight: FontWeight.w700, fontSize: 16)),
|
||||
const SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: state.faqs.length,
|
||||
itemBuilder: (context, index) {
|
||||
final faq = state.faqs[index];
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const SupportFormPage()),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade100,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(faq.title,
|
||||
style: GoogleFonts.poppins(
|
||||
fontWeight: FontWeight.w500, fontSize: 14)),
|
||||
Row(
|
||||
children: [
|
||||
Image.asset(faq.icon, width: 18, color: const Color(0xffF95F62)),
|
||||
const SizedBox(width: 6),
|
||||
Text(faq.tag,
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12, color: Colors.black54)),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const SupportFormPage()),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xffF95F62),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(50))),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
child: Center(
|
||||
child: Text("Contact Support",
|
||||
style: GoogleFonts.poppins(
|
||||
fontWeight: FontWeight.w600, color: Colors.white)),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
OutlinedButton(
|
||||
onPressed: () {},
|
||||
style: OutlinedButton.styleFrom(
|
||||
side: const BorderSide(color: Color(0xffF95F62)),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(50)),
|
||||
),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
child: Center(
|
||||
child: Text("Browse FAQ",
|
||||
style: GoogleFonts.poppins(
|
||||
color: const Color(0xffF95F62),
|
||||
fontWeight: FontWeight.w600)),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
214
lib/support/view/support_form_page.dart
Normal file
214
lib/support/view/support_form_page.dart
Normal file
@@ -0,0 +1,214 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../blocs/ticket_bloc.dart';
|
||||
|
||||
class SupportFormPage extends StatelessWidget {
|
||||
const SupportFormPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (_) => TicketBloc(),
|
||||
child: const _SupportFormView(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SupportFormView extends StatelessWidget {
|
||||
const _SupportFormView();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bloc = context.read<TicketBloc>();
|
||||
return Scaffold(
|
||||
|
||||
backgroundColor: Colors.white,
|
||||
bottomNavigationBar: BlocBuilder<TicketBloc, TicketState>(
|
||||
builder: (context, state) {return
|
||||
Visibility(
|
||||
visible: MediaQuery.of(context).viewInsets.bottom == 0.0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: state.isLoading
|
||||
? null
|
||||
: () => bloc.add(SubmitTicket()),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xffF95F62),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8)),
|
||||
),
|
||||
child: state.isLoading
|
||||
? const CircularProgressIndicator(color: Colors.white)
|
||||
: Text("Submit Ticket",
|
||||
style: GoogleFonts.poppins(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600)),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
leading: Padding(
|
||||
padding: EdgeInsetsGeometry.symmetric(horizontal: 8),
|
||||
child: Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFF06969),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.arrow_back, color: Colors.white),
|
||||
),
|
||||
),
|
||||
centerTitle: true,
|
||||
title: Text("Support",
|
||||
style: GoogleFonts.poppins(fontWeight: FontWeight.w700, color: Colors.black)),
|
||||
),
|
||||
body: BlocBuilder<TicketBloc, TicketState>(
|
||||
builder: (context, state) {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Need help? We’re here for you. Raise a ticket and our support team will get back to you shortly",
|
||||
style: GoogleFonts.poppins(color: Colors.black54, fontSize: 13),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text("Subject", style: GoogleFonts.poppins(fontWeight: FontWeight.w600)),
|
||||
const SizedBox(height: 6),
|
||||
TextField(
|
||||
onChanged: (v) => bloc.add(SubjectChanged(v)),
|
||||
decoration: InputDecoration(
|
||||
fillColor: Colors.black.withOpacity(0.04),
|
||||
hintText: "Enter Subject",
|
||||
filled: true,
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text("Description", style: GoogleFonts.poppins(fontWeight: FontWeight.w600)),
|
||||
const SizedBox(height: 6),
|
||||
TextField(
|
||||
onChanged: (v) => bloc.add(DescriptionChanged(v)),
|
||||
maxLines: 4,
|
||||
decoration: InputDecoration(
|
||||
fillColor: Colors.black.withOpacity(0.04),
|
||||
hintText: "Enter Description",
|
||||
filled: true,
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text("File upload", style: GoogleFonts.poppins(fontWeight: FontWeight.w600)),
|
||||
const SizedBox(height: 6),
|
||||
GestureDetector(
|
||||
onTap: () => bloc.add(UploadFile()),
|
||||
child: Container(
|
||||
height: 45,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.04),
|
||||
border: Border.all(color: Colors.grey),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Text(
|
||||
state.selectedFile != null
|
||||
? state.selectedFile!.name
|
||||
: "Upload File",
|
||||
style: GoogleFonts.poppins(
|
||||
color: state.selectedFile != null
|
||||
? Colors.black
|
||||
: Colors.black54,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Icon(Icons.upload_outlined, color: Colors.black54),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
Text("Contact Details",
|
||||
style: GoogleFonts.poppins(
|
||||
fontWeight: FontWeight.w700, fontSize: 15)),
|
||||
const SizedBox(height: 12),
|
||||
Divider(),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.email_outlined, color: Colors.black87),
|
||||
const SizedBox(width: 12),
|
||||
Text("Email", style: GoogleFonts.poppins(fontSize: 13)),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(width: 12),
|
||||
Text("Lila Hart", style: GoogleFonts.poppins(fontSize: 13,color: Colors.black)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
|
||||
Divider(),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Row(
|
||||
|
||||
children: [
|
||||
const Icon(Icons.phone_outlined, color: Colors.black87),
|
||||
const SizedBox(width: 12),
|
||||
Text("Phone", style: GoogleFonts.poppins(fontSize: 13)),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 12),
|
||||
Text("(+971) 050 4245 564",
|
||||
style: GoogleFonts.poppins(fontSize: 13)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Divider(),
|
||||
const SizedBox(height: 80),
|
||||
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
7
lib/support/viewmodel/support_model.dart
Normal file
7
lib/support/viewmodel/support_model.dart
Normal file
@@ -0,0 +1,7 @@
|
||||
class SupportItem {
|
||||
final String title;
|
||||
final String icon;
|
||||
final String tag;
|
||||
|
||||
SupportItem(this.title, this.icon, this.tag);
|
||||
}
|
||||
Reference in New Issue
Block a user