Skip to content

Commit

Permalink
Merge pull request #112 from seigi0714/feature/add_belonging
Browse files Browse the repository at this point in the history
持ち物追加処理を実装
  • Loading branch information
seigi0714 committed May 20, 2023
2 parents 282f1b4 + b99d0f7 commit ff4a775
Show file tree
Hide file tree
Showing 5 changed files with 402 additions and 6 deletions.
30 changes: 27 additions & 3 deletions lib/features/trips/controller/trip_belonging_controller.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,39 @@
import 'package:flutter/material.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:trip_app_nativeapp/core/exception/exception_handler.dart';
import 'package:trip_app_nativeapp/features/trips/domain/entity/trip/trip_belonging.dart';
import 'package:trip_app_nativeapp/features/trips/domain/interactor/trip_interactor.dart';
import 'package:trip_app_nativeapp/view/widgets/common/loading.dart';

part 'trip_belonging_controller.g.dart';

@riverpod
class TripBelongingsController extends _$TripBelongingsController {
@override
FutureOr<List<AddedTripBelonging>> build({required int tripId}) async {
return ref
.read(tripInteractorProvider)
.fetchTripBelongings(tripId);
return ref.read(tripInteractorProvider).fetchTripBelongings(tripId);
}

Future<void> addBelonging({
required int tripId,
required String name,
required int numOf,
required bool isShareAmongMember,
VoidCallback? onFinished,
}) async {
try {
final result = await ref.read(tripInteractorProvider).addTripBelonging(
tripId: tripId,
name: name,
numOf: numOf,
isShareAmongMember: isShareAmongMember,
);
state = AsyncValue.data([result, ...state.value ?? []]);
} on Exception catch (e) {
ref.read(exceptionHandlerProvider).handleException(e);
} finally {
ref.read(overlayLoadingProvider.notifier).endLoading();
onFinished?.call();
}
}
}
17 changes: 17 additions & 0 deletions lib/features/trips/domain/interactor/trip_interactor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import 'package:trip_app_nativeapp/features/trips/data/repositories/trip_reposit
import 'package:trip_app_nativeapp/features/trips/domain/entity/trip/trip.dart';
import 'package:trip_app_nativeapp/features/trips/domain/entity/trip/trip_belonging.dart';
import 'package:trip_app_nativeapp/features/trips/domain/entity/trip/trip_invitation.dart';
import 'package:trip_app_nativeapp/features/trips/domain/entity/trip/value/trip_belonging_name.dart';
import 'package:trip_app_nativeapp/features/trips/domain/entity/trip/value/trip_belonging_num.dart';
import 'package:trip_app_nativeapp/features/trips/domain/entity/trip/value/trip_invitation_num.dart';
import 'package:trip_app_nativeapp/features/trips/domain/entity/trip/value/trip_period.dart';
import 'package:trip_app_nativeapp/features/trips/domain/entity/trip/value/trip_title.dart';
Expand Down Expand Up @@ -56,6 +58,21 @@ class TripInteractor {
Future<List<ExistingTrip>> fetchTripsByUserId(int userId) =>
tripRepo.fetchTripsByUserId(userId);

Future<AddedTripBelonging> addTripBelonging({
required int tripId,
required String name,
required int numOf,
required bool isShareAmongMember,
}) {
final belonging = TripBelonging.createNewTripBelonging(
name: TripBelongingName(value: name),
numOf: TripBelongingNum(value: numOf),
isShareAmongMember: isShareAmongMember,
) as NewTripBelonging;

return tripRepo.addTripBelonging(tripId, belonging);
}

Future<List<AddedTripBelonging>> fetchTripBelongings(int tripId) =>
tripRepo.fetchTripBelongings(tripId);
}
208 changes: 208 additions & 0 deletions lib/view/widgets/trips/add_trip_belonging_sheet.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:trip_app_nativeapp/core/extensions/build_context.dart';
import 'package:trip_app_nativeapp/features/trips/controller/trip_belonging_controller.dart';

class AddTripBelongingSheet extends HookConsumerWidget {
const AddTripBelongingSheet(this.tripId, {super.key});

final int tripId;
@override
Widget build(BuildContext context, WidgetRef ref) {
final titleEditingController = useTextEditingController();
final numOfEditingController = useTextEditingController();

final isTitleEmpty = useState<bool>(true);
final isNumOfEmpty = useState<bool>(true);
final isShareAmongMember = useState<bool>(false);

useEffect(
() {
titleEditingController.addListener(
() => isTitleEmpty.value = titleEditingController.text.isEmpty,
);
numOfEditingController.addListener(
() => isNumOfEmpty.value = numOfEditingController.text.isEmpty,
);
return null;
},
[titleEditingController, numOfEditingController],
);

return Container(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
height: 280,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_TitleTextField(
controller: titleEditingController,
isTitleEmpty: isTitleEmpty,
),
_NumOfForm(
controller: numOfEditingController,
isNumEmpty: isNumOfEmpty,
),
_IsShareCheckBoxRow(isShareAmongMember: isShareAmongMember),
_CreateButton(
tripId: tripId,
titleEditingController: titleEditingController,
numOfEditingController: numOfEditingController,
isTitleEmpty: isTitleEmpty,
isNumOfEmpty: isNumOfEmpty,
isShareAmongMember: isShareAmongMember,
),
],
),
);
}
}

class _TitleTextField extends StatelessWidget {
const _TitleTextField({
required this.controller,
required this.isTitleEmpty,
});

final TextEditingController controller;
final ValueNotifier<bool> isTitleEmpty;

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: isTitleEmpty.value
? context.theme.colorScheme.secondaryContainer
: context.theme.colorScheme.primary,
),
),
child: TextField(
autofocus: true,
controller: controller,
decoration: const InputDecoration(
hintText: '持ち物名🧳',
border: InputBorder.none,
contentPadding: EdgeInsets.all(16),
),
style: TextStyle(
color: context.theme.colorScheme.primary,
),
),
),
);
}
}

class _NumOfForm extends HookWidget {
const _NumOfForm({
required this.controller,
required this.isNumEmpty,
});

final TextEditingController controller;
final ValueNotifier<bool> isNumEmpty;

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: isNumEmpty.value
? context.theme.colorScheme.secondaryContainer
: context.theme.colorScheme.primary,
),
),
child: TextField(
controller: controller,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
hintText: '個数',
border: InputBorder.none,
contentPadding: EdgeInsets.all(16),
),
style: TextStyle(
color: context.theme.colorScheme.primary,
),
),
),
);
}
}

class _IsShareCheckBoxRow extends HookWidget {
const _IsShareCheckBoxRow({
required this.isShareAmongMember,
});

final ValueNotifier<bool> isShareAmongMember;

@override
Widget build(BuildContext context) {
return Row(
children: [
Checkbox(
value: isShareAmongMember.value,
onChanged: (value) => isShareAmongMember.value = value ?? false,
fillColor: context.theme.checkboxTheme.checkColor,
),
InkWell(
child: const Text('他のメンバーと共有する?'),
onTap: () => isShareAmongMember.value = !isShareAmongMember.value,
),
],
);
}
}

class _CreateButton extends ConsumerWidget {
const _CreateButton({
required this.tripId,
required this.titleEditingController,
required this.numOfEditingController,
required this.isTitleEmpty,
required this.isNumOfEmpty,
required this.isShareAmongMember,
});

final int tripId;
final TextEditingController titleEditingController;
final TextEditingController numOfEditingController;
final ValueNotifier<bool> isTitleEmpty;
final ValueNotifier<bool> isNumOfEmpty;
final ValueNotifier<bool> isShareAmongMember;

@override
Widget build(BuildContext context, WidgetRef ref) {
return SizedBox(
width: context.displaySize.width * 0.2,
child: ElevatedButton(
onPressed: isTitleEmpty.value || isNumOfEmpty.value
? null
: () {
ref
.read(
tripBelongingsControllerProvider(tripId: tripId).notifier,
)
.addBelonging(
tripId: tripId,
name: titleEditingController.text,
numOf: int.parse(numOfEditingController.text),
isShareAmongMember: isShareAmongMember.value,
onFinished: () => Navigator.pop(context),
);
},
child: const Text('作成'),
),
);
}
}
16 changes: 13 additions & 3 deletions lib/view/widgets/trips/trip_belonging_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ import 'package:trip_app_nativeapp/core/extensions/build_context.dart';
import 'package:trip_app_nativeapp/features/trips/controller/trip_belonging_controller.dart';
import 'package:trip_app_nativeapp/view/widgets/common/car_driving_loading.dart';
import 'package:trip_app_nativeapp/view/widgets/common/error_cat.dart';
import 'package:trip_app_nativeapp/view/widgets/trips/add_trip_belonging_sheet.dart';

class TripBelongingList extends HookConsumerWidget {
const TripBelongingList(this.tripId, {super.key});

final int tripId;
@override
Widget build(BuildContext context, WidgetRef ref) {
return ref
.watch(tripBelongingsControllerProvider(tripId: tripId))
.when(
return ref.watch(tripBelongingsControllerProvider(tripId: tripId)).when(
data: (belongings) {
return Column(
children: [
Expand Down Expand Up @@ -41,6 +40,17 @@ class TripBelongingList extends HookConsumerWidget {
},
),
),
Padding(
padding: const EdgeInsets.all(8),
child: FloatingActionButton(
onPressed: () => showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
builder: (context) => AddTripBelongingSheet(tripId),
),
child: const Icon(Icons.add, color: Colors.white),
),
),
],
);
},
Expand Down
Loading

0 comments on commit ff4a775

Please sign in to comment.