Fakultas Ilmu Komputer UI

Commit 8ec962cd authored by Tsamara Esperanti Erwin's avatar Tsamara Esperanti Erwin 🦄
Browse files

Merge branch 'PBI-7-Progress_Program' into 'staging'

Pbi 7 progress program

add progress program, increase coverage

See merge request !88
parents 0d106b71 013ec5f0
Pipeline #61046 passed with stages
in 31 minutes and 22 seconds
......@@ -6,7 +6,7 @@ String formatDate(DateTime dateTime) {
}
String formatDatePlusOne(DateTime dateTime) {
var addOneDay = dateTime.add(Duration(days: 1));
final addOneDay = dateTime.add(const Duration(days: 1));
final formatter = DateFormat(DateFormat.YEAR_MONTH_DAY);
return dateTime != null ? formatter.format(addOneDay.toLocal()) : '';
}
......
......@@ -31,6 +31,8 @@ import 'package:home_industry/Pages/Transactions/repositories/transaction_reposi
import 'package:home_industry/State/Auth/repositories/depedencies_repositories.dart';
import 'package:home_industry/State/ChangeBottomNavigatonBar/bloc.dart';
import '../Pages/Program/repositories/program_repository.dart';
class Router {
static const registerPage = '/register';
static const editProfilePage = '/edit-profile';
......@@ -147,15 +149,30 @@ class Router {
case programs:
return MaterialPageRoute<Programs>(
builder: (context) => const Programs());
builder: (context) => RepositoryProvider<ProgramRepository>.value(
value: ProgramRepository(
dio:
RepositoryProvider.of<DependenciesRepositories>(
context)
.dio),
child: Programs()
));
case detailProgramPage:
final Map<String, dynamic> args = settings.arguments;
return MaterialPageRoute<DetailProgram>(
builder: (context) => DetailProgram(
builder: (context) => RepositoryProvider<ProgramRepository>.value(
value: ProgramRepository(
dio:
RepositoryProvider.of<DependenciesRepositories>(
context)
.dio),
child: DetailProgram(
program: args['program'],
),
));
case donationPage:
final Map<String, dynamic> args = settings.arguments;
return MaterialPageRoute(
......
......@@ -43,7 +43,6 @@ class ChooseDonationDialog extends StatelessWidget {
class _ChooseDonation extends StatefulWidget {
final Program program;
// const _ChooseDonation(this.program);
const _ChooseDonation({Key key, @required this.program}) : super(key: key);
@override
......@@ -59,6 +58,7 @@ class __ChooseDonationState extends State<_ChooseDonation> {
return Column(
children: [
CheckboxListTile(
key: const Key('uang'),
dense: true,
controlAffinity: ListTileControlAffinity.leading,
value: uangValue,
......@@ -74,6 +74,7 @@ class __ChooseDonationState extends State<_ChooseDonation> {
});
}),
CheckboxListTile(
key: const Key('barang'),
dense: true,
controlAffinity: ListTileControlAffinity.leading,
title: Text('Barang',
......
......@@ -110,7 +110,7 @@ class _DonasiBarangState extends State<DonasiBarang> {
),
),
Container(
margin: EdgeInsets.symmetric(horizontal: 20.0),
margin: const EdgeInsets.symmetric(horizontal: 20.0),
child: Form(
key: _formKey,
child: Column(
......@@ -120,7 +120,7 @@ class _DonasiBarangState extends State<DonasiBarang> {
Row(
children: [
Radio(
key: Key('PCK-button'),
key: const Key('PCK-button'),
value: 'PCK',
groupValue: methodOfDelivery,
onChanged: (val) {
......@@ -179,7 +179,7 @@ class _DonasiBarangState extends State<DonasiBarang> {
.copyWith(fontSize: 18),
),
Radio(
key: Key('DLV-button'),
key: const Key('DLV-button'),
value: 'DLV',
groupValue: methodOfDelivery,
onChanged: (val) {
......@@ -217,7 +217,7 @@ class _DonasiBarangState extends State<DonasiBarang> {
setState(() {
deliveryWidget = Container(
height: 80,
child: Text(
child: const Text(
'Mohon pilih metode pengiriman',
style: TextStyle(color: Colors.red),
),
......@@ -265,7 +265,7 @@ class _DonasiBarangState extends State<DonasiBarang> {
}
List<Widget> _getGoodsForm(){
var goodsTextFields = <Widget>[];
final goodsTextFields = <Widget>[];
for(var i = 0; i < goodsDescList.length; i++){
goodsTextFields.add(
Padding(
......@@ -292,7 +292,7 @@ class _DonasiBarangState extends State<DonasiBarang> {
child: Divider(color: Colors.grey)),
const SizedBox(width: 10.0,),
InkWell(
key: Key('addRemoveButton' + index.toString()),
key: Key('addRemoveButton$index'),
onTap: (){
if(add){
goodsDescList.insert(index+1, null);
......@@ -305,8 +305,8 @@ class _DonasiBarangState extends State<DonasiBarang> {
setState((){});
},
child: Icon(
(add) ? Icons.add_circle : Icons.remove_circle,
color: Color.fromRGBO(60, 141, 188, 1),
add ? Icons.add_circle : Icons.remove_circle,
color: const Color.fromRGBO(60, 141, 188, 1),
),
)
],
......@@ -317,7 +317,7 @@ class _DonasiBarangState extends State<DonasiBarang> {
class GoodsForm extends StatefulWidget {
final int index;
GoodsForm({Key key, @required this.index})
const GoodsForm({Key key, @required this.index})
: super(key: key);
@override
......@@ -362,7 +362,7 @@ class _GoodsFormState extends State<GoodsForm> {
width: 187,
height: 50,
child: TextFormField(
key: Key('goods-desc'),
key: const Key('goods-desc'),
onChanged: (v) => _DonasiBarangState.goodsDescList[widget.index] = v,
controller: _goodsDescriptionController,
validator: (value) {
......@@ -403,7 +403,7 @@ class _GoodsFormState extends State<GoodsForm> {
height: 50,
color: Colors.white,
child: TextFormField(
key: Key('goods-qty'),
key: const Key('goods-qty'),
onChanged: (v) => _DonasiBarangState.goodsQtyList[widget.index] = v,
controller: _goodsQuantityController,
validator: (value) {
......
......@@ -61,13 +61,13 @@ class OTPPage extends StatelessWidget {
children: <Widget>[
const Text('Mohon masukkan OTP yang dikirim ke nomor anda',
softWrap: true),
Divider(
const Divider(
height: 20,
thickness: null,
),
Theme(
data: Theme.of(context)
.copyWith(inputDecorationTheme: InputDecorationTheme()),
.copyWith(inputDecorationTheme: const InputDecorationTheme()),
child: PinPut(
onSubmit: (String pin) {
_otpSubmitted(pin, context);
......@@ -85,7 +85,7 @@ class OTPPage extends StatelessWidget {
),
),
Container(
margin: EdgeInsets.only(top: 10),
margin: const EdgeInsets.only(top: 10),
height: MediaQuery.of(context).size.height / 18,
child: ButtonResendOTP(
label: 'Kirim Ulang OTP',
......
......@@ -12,7 +12,7 @@ abstract class SearchProductState extends Equatable {
}
class InitialSearchProductState extends SearchProductState {
const InitialSearchProductState() : super(0, '', hasReachedMax: true);
const InitialSearchProductState({hasReachedMax}) : super(0, '', hasReachedMax: true);
@override
List<Object> get props => [page, searchQuery];
......
......@@ -20,6 +20,9 @@ class ProgramBloc extends Bloc<ProgramEvent, ProgramState> {
bool _hasReachedMax(ProgramState state) =>
state is ListProgramsLoaded && state.hasReachedMax;
bool _hasReachedMaxSorted(ProgramState state) =>
state is ListProgramsSortedLoaded && state.hasReachedMax;
@override
Stream<ProgramState> mapEventToState(
ProgramEvent event,
......@@ -48,6 +51,30 @@ class ProgramBloc extends Bloc<ProgramEvent, ProgramState> {
} catch (e) {
yield ListProgramsError(error: e.toString());
}
} else if ((event is FetchProgramByStatus) && !_hasReachedMaxSorted(currentState)) {
try {
var programs =
await programRepository.fetchProgram(page: currentState.page + 1);
if (currentState is ListProgramsLoaded || currentState is InitialListProgramState) {
programs.results.sort((a,b) => b.endDateTime.compareTo(a.endDateTime));
yield ListProgramsSortedLoaded(
programs: programs.results,
hasReachedMax: programs.next == null,
page: currentState.page + 1);
return;
} else if (currentState is ListProgramsSortedLoaded) {
programs.results.sort((a,b) => b.endDateTime.compareTo(a.endDateTime));
yield ListProgramsSortedLoaded(
programs: currentState.programs + programs.results,
hasReachedMax: programs.next == null,
page: currentState.page + 1);
return;
}
} on DioError catch (e) {
yield ListProgramsError(error: e.response.data.toString());
} catch (e) {
yield ListProgramsError(error: e.toString());
}
}
}
......
......@@ -10,3 +10,10 @@ class FetchProgram extends ProgramEvent {
@override
List<Object> get props => [];
}
class FetchProgramByStatus extends ProgramEvent {
const FetchProgramByStatus();
@override
List<Object> get props => [];
}
......@@ -26,6 +26,18 @@ class ListProgramsLoaded extends ProgramState {
List<Object> get props => [programs, hasReachedMax, page];
}
class ListProgramsSortedLoaded extends ProgramState {
final List<Program> programs;
final bool hasReachedMax;
const ListProgramsSortedLoaded(
{@required this.programs, @required this.hasReachedMax, @required page})
: super(page);
@override
List<Object> get props => [programs, hasReachedMax, page];
}
class ListProgramsError extends ProgramState {
final String error;
const ListProgramsError({@required this.error}) : super(0);
......@@ -33,3 +45,13 @@ class ListProgramsError extends ProgramState {
@override
List<Object> get props => [page];
}
class ListProgramsSortedError extends ProgramState {
final String error;
const ListProgramsSortedError({@required this.error}) : super(0);
@override
List<Object> get props => [page];
}
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:dio/dio.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/cupertino.dart';
import 'package:home_industry/Pages/Program/model/program.dart';
import 'package:home_industry/Pages/Program/repositories/program_repository.dart';
part 'progress_program_event.dart';
part 'progress_program_state.dart';
class ProgressProgramBloc extends Bloc<ProgressProgramEvent, ProgressProgramState> {
final Program program;
final ProgramRepository progressProgramRepository;
ProgressProgramBloc({@required this.progressProgramRepository, @required this.program})
: assert(progressProgramRepository != null);
@override
ProgressProgramState get initialState => const ProgressProgramInitial();
@override
Stream<ProgressProgramState> mapEventToState(ProgressProgramEvent event) async* {
if (event is FetchProgressProgram) {
try {
final progressProgram = await progressProgramRepository.fetchProgressProgram(program.id);
yield ProgressProgramLoaded(progressProgram);
} on DioError catch (_) {
yield ProgressProgramError(error: _.response.toString());
}
}
}
}
\ No newline at end of file
part of 'progress_program_bloc.dart';
abstract class ProgressProgramEvent extends Equatable {
const ProgressProgramEvent();
}
class FetchProgressProgram extends ProgressProgramEvent {
const FetchProgressProgram();
@override
List<Object> get props => [];
}
part of 'progress_program_bloc.dart';
abstract class ProgressProgramState extends Equatable {
const ProgressProgramState();
}
class ProgressProgramInitial extends ProgressProgramState {
const ProgressProgramInitial();
@override
List<Object> get props => [];
}
class ProgressProgramLoaded extends ProgressProgramState {
final List<dynamic> progressList;
const ProgressProgramLoaded(this.progressList);
@override
List<Object> get props => [];
}
class ProgressProgramError extends ProgressProgramState {
final String error;
const ProgressProgramError({@required this.error}) : super();
@override
List<Object> get props => [];
}
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:home_industry/Component/border_blue_button.dart';
import 'package:home_industry/Component/custom_circular.dart';
import 'package:home_industry/Component/date_time_formatter.dart';
import 'package:home_industry/Component/product_image.dart';
import 'package:home_industry/Pages/Donation/donation_dialog.dart';
import 'package:home_industry/Pages/Program/bloc_progress_program/progress_program_bloc.dart';
import 'package:home_industry/Pages/Program/model/program.dart';
import 'package:home_industry/Pages/Program/repositories/program_repository.dart';
import 'package:outline_material_icons/outline_material_icons.dart';
import 'package:url_launcher/url_launcher.dart';
class DetailProgram extends StatelessWidget {
final Program program;
const DetailProgram({Key key, @required this.program})
: assert(program != null),
super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
......@@ -57,9 +61,7 @@ class DetailProgram extends StatelessWidget {
program.name,
style: Theme.of(context).textTheme.headline5,
),
const SizedBox(
height: 25
),
const SizedBox(height: 25),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
......@@ -68,12 +70,12 @@ class DetailProgram extends StatelessWidget {
_DeskripsiRow(
icon: Icons.calendar_today,
label:
'${formatDate(program.startDateTime)}'
),
'${formatDate(program.startDateTime)}'),
if (program.endDateTime != null)
_DeskripsiRow(
icon: Icons.cancel,
label: '${formatTime(program.startDateTime)}'
label:
'${formatTime(program.startDateTime)}'
' - '
'${formatTime(program.endDateTime)}',
),
......@@ -90,9 +92,7 @@ class DetailProgram extends StatelessWidget {
label: program.speaker),
],
),
const SizedBox(
height: 25
),
const SizedBox(height: 25),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
......@@ -113,6 +113,110 @@ class DetailProgram extends StatelessWidget {
),
],
),
),
BlocProvider(
create: (context) => ProgressProgramBloc(
program: program,
progressProgramRepository:
RepositoryProvider.of<ProgramRepository>(
context))
..add(const FetchProgressProgram()),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(
left: 30.0, top: 15.0),
child: Text(
'Progress',
style: Theme.of(context)
.textTheme
.bodyText2
.copyWith(
fontSize: 18.0,
fontWeight: FontWeight.bold),
),
),
BlocBuilder<ProgressProgramBloc,
ProgressProgramState>(
builder: (BuildContext context,
ProgressProgramState state) {
if (state is ProgressProgramInitial) {
return const Center(
child: CircularProgressIndicator(),
);
} else if (state is ProgressProgramError) {
return Center(
child: Container(
margin: const EdgeInsets.symmetric(
horizontal: 30.0),
child: Text(state.error)),
);
} else if (state is ProgressProgramLoaded) {
if (state.progressList.isEmpty) {
return Container(
margin: const EdgeInsets.symmetric(
horizontal: 30.0),
child: const Text(
'Program belum memiliki progress'));
} else {
return Container(
margin: const EdgeInsets.symmetric(
horizontal: 15.0),
height:
MediaQuery.of(context).size.height /
3,
child: ListView.builder(
itemCount: state.progressList.length,
itemBuilder: (context, index) {
return ListTile(
title: Padding(
padding: const EdgeInsets.only(
top: 10.0),
child: Text(formatDate(
DateTime.parse(state
.progressList[index]
['date']))),
),
subtitle: Text(
state.progressList[index]
['description']),
trailing: state.progressList[index]['image'] != null? Padding(
padding: const EdgeInsets.only(
top: 13.0),
child: GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (BuildContext context) =>
ImageDialog(image: state.progressList[index]['image']));
},
child: Column(
key: const Key('camera-icon'),
children: [
Icon(Icons.camera_alt),
const Text(
'Lihat Gambar',
style: TextStyle(
fontSize: 12.0),
)
],
),
),
) : null,
isThreeLine: true,
);
},
),
);
}
}
return const Text(
'Terjadi kesalahan, silahkan coba lagi.');
},
)
],
),
)
],
),
......@@ -135,7 +239,8 @@ class DetailProgram extends StatelessWidget {
onTap: () {
showDialog(
context: context,
builder: (BuildContext context) => ChooseDonationDialog(program: program));
builder: (BuildContext context) =>
ChooseDonationDialog(program: program));
},
child: Align(
alignment: Alignment.bottomCenter,
......@@ -143,11 +248,21 @@ class DetailProgram extends StatelessWidget {
width: 330,
height: 45,
decoration: BoxDecoration(
color: const Color.fromRGBO(60, 141, 188, 1),
borderRadius: BorderRadius.circular(50)
),
child: Center(child: Text('DONASI',
style: Theme.of(context).textTheme.bodyText2.copyWith(fontSize: 18, color: Colors.white, fontWeight: FontWeight.bold, letterSpacing: 1.2),)),
color:
const Color.fromRGBO(60, 141, 188, 1),
borderRadius: BorderRadius.circular(50)),
child: Center(
child: Text(
'DONASI',
style: Theme.of(context)
.textTheme
.bodyText2
.copyWith(
fontSize: 18,
color: Colors.white,
fontWeight: FontWeight.bold,
letterSpacing: 1.2),
)),
),
),
),
......@@ -165,6 +280,38 @@ class DetailProgram extends StatelessWidget {
}
}
class ImageDialog extends StatelessWidget {
final String image;
const ImageDialog({Key key, @required this.image})
: super(key: key);
@override
Widget build(BuildContext context) {
return Dialog(
child: Container(
height: 300,
margin: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [