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,14 +149,29 @@ 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(
program: args['program'],
));
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;
......
......@@ -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',
......
......@@ -39,10 +39,10 @@ class _DonasiBarangState extends State<DonasiBarang> {
void onTap(
{@required String quantity,
@required String description,
@required String methodOfDelivery,
@required bool isFinal,
String address}) {
@required String description,
@required String methodOfDelivery,
@required bool isFinal,
String address}) {
goodsDonationBloc.add(GoodsDonationButtonClicked(
program: widget.program.id,
quantity: quantity,
......@@ -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) {
......@@ -128,7 +128,7 @@ class _DonasiBarangState extends State<DonasiBarang> {
methodOfDelivery = val;
deliveryWidget = Row(
crossAxisAlignment:
CrossAxisAlignment.start,
CrossAxisAlignment.start,
children: [
RichText(
text: TextSpan(
......@@ -144,8 +144,8 @@ class _DonasiBarangState extends State<DonasiBarang> {
.textTheme
.bodyText2
.copyWith(
fontSize: 18,
color: Colors.red)),
fontSize: 18,
color: Colors.red)),
],
),
),
......@@ -157,7 +157,7 @@ class _DonasiBarangState extends State<DonasiBarang> {
child: TextFormField(
controller: addressController,
keyboardType:
TextInputType.multiline,
TextInputType.multiline,
validator: (value) {
if (value.isEmpty) {
return 'Mohon isi alamat';
......@@ -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),
),
......@@ -252,20 +252,20 @@ class _DonasiBarangState extends State<DonasiBarang> {
borderRadius: BorderRadius.circular(50)),
child: Center(
child: Text(
'DONASI SEKARANG',
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: 18,
color: Colors.white,
fontWeight: FontWeight.bold,
letterSpacing: 1.2),
)),
'DONASI SEKARANG',
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontSize: 18,
color: Colors.white,
fontWeight: FontWeight.bold,
letterSpacing: 1.2),
)),
),
),
);
}
List<Widget> _getGoodsForm(){
var goodsTextFields = <Widget>[];
final goodsTextFields = <Widget>[];
for(var i = 0; i < goodsDescList.length; i++){
goodsTextFields.add(
Padding(
......@@ -286,30 +286,30 @@ class _DonasiBarangState extends State<DonasiBarang> {
return Padding(
padding: const EdgeInsets.only(top: 15.0),
child: Row(
children: [
Container(
children: [
Container(
width: MediaQuery.of(context).size.width - 80,
child: Divider(color: Colors.grey)),
const SizedBox(width: 10.0,),
InkWell(
key: Key('addRemoveButton' + index.toString()),
onTap: (){
if(add){
goodsDescList.insert(index+1, null);
goodsQtyList.insert(index+1, null);
}
else {
goodsDescList.removeAt(index);
goodsQtyList.removeAt(index);
}
setState((){});
child: Divider(color: Colors.grey)),
const SizedBox(width: 10.0,),
InkWell(
key: Key('addRemoveButton$index'),
onTap: (){
if(add){
goodsDescList.insert(index+1, null);
goodsQtyList.insert(index+1, null);
}
else {
goodsDescList.removeAt(index);
goodsQtyList.removeAt(index);
}
setState((){});
},
child: Icon(
(add) ? Icons.add_circle : Icons.remove_circle,
color: Color.fromRGBO(60, 141, 188, 1),
),
)
],
child: Icon(
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) {
......@@ -433,4 +433,4 @@ InputDecoration _inputDecoration() {
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Color.fromRGBO(224, 225, 226, 1))),
);
}
}
\ No newline at end of file
......@@ -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(
......@@ -25,143 +29,286 @@ class DetailProgram extends StatelessWidget {
floatingActionButton: program.link == null || program.link == ''
? null
: _Tautan(
link: program.link,
),
link: program.link,
),
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints viewport) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: viewport.maxHeight),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: viewport.maxHeight),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
SizedBox(
height: MediaQuery.of(context).size.height / 5,
child: Hero(
tag: program.id,
child: CachedImage(url: program.posterImage)),
),
Padding(
padding: const EdgeInsets.only(
left: 30, right: 16, bottom: 8, top: 30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceAround,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
program.name,
style: Theme.of(context).textTheme.headline5,
),
const SizedBox(
height: 25
),
Column(
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(
height: MediaQuery.of(context).size.height / 5,
child: Hero(
tag: program.id,
child: CachedImage(url: program.posterImage)),
),
Padding(
padding: const EdgeInsets.only(
left: 30, right: 16, bottom: 8, top: 30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceAround,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
if (program.startDateTime != null)
_DeskripsiRow(
icon: Icons.calendar_today,
label:
'${formatDate(program.startDateTime)}'
),
if (program.endDateTime != null)
_DeskripsiRow(
icon: Icons.cancel,
label: '${formatTime(program.startDateTime)}'
' - '
'${formatTime(program.endDateTime)}',
),
if (program.location != null &&
program.location != '')
_DeskripsiRow(
icon: OMIcons.room,
label: program.location,
),
if (program.speaker != null &&
program.speaker != '')
_DeskripsiRow(
icon: Icons.perm_identity,
label: program.speaker),
Text(
program.name,
style: Theme.of(context).textTheme.headline5,
),
const SizedBox(height: 25),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[