Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

旅詳細ページのUI実装 #89

Merged
merged 11 commits into from
Apr 29, 2023
17 changes: 17 additions & 0 deletions lib/router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:trip_app_nativeapp/core/exception/app_exception.dart';
import 'package:trip_app_nativeapp/features/user/controller/app_user_controller.dart';
import 'package:trip_app_nativeapp/view/pages/debug_page.dart';
import 'package:trip_app_nativeapp/view/pages/error_page.dart';
import 'package:trip_app_nativeapp/view/pages/loading_page.dart';
import 'package:trip_app_nativeapp/view/pages/login_page.dart';
import 'package:trip_app_nativeapp/view/pages/trips/trip_detail_page.dart';
import 'package:trip_app_nativeapp/view/pages/trips/trips_list_page.dart';

part 'router.g.dart';
Expand Down Expand Up @@ -59,6 +61,21 @@ GoRouter router(RouterRef ref) {
GoRoute(
path: TripListPage.path,
builder: (context, state) => const TripListPage(),
routes: [
GoRoute(
path: TripDetailPage.pathParam,
builder: (context, state) {
final id = int.tryParse(state.params['id'] ?? '');
if (id != null) {
return TripDetailPage(id);
} else {
return const ErrorPage(
exception: AppException(message: '旅の選択に失敗しました。'),
);
}
},
),
],
),
GoRoute(
path: LoginPage.path,
Expand Down
121 changes: 121 additions & 0 deletions lib/view/pages/trips/trip_detail_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import 'package:collection/collection.dart';
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/exception/app_exception.dart';
import 'package:trip_app_nativeapp/core/extensions/build_context.dart';
import 'package:trip_app_nativeapp/features/trips/controller/trip_controller.dart';
import 'package:trip_app_nativeapp/view/pages/trips/trips_list_page.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/trip_belonging_list.dart';
import 'package:trip_app_nativeapp/view/widgets/trips/trip_overview_card.dart';
import 'package:trip_app_nativeapp/view/widgets/trips/trip_schedule.dart';

class TripDetailPage extends HookConsumerWidget {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

デザインめっちゃ良い感じだと思う!🧑‍🎨

const TripDetailPage(this.id, {super.key});

static String path({required int id}) => '${TripListPage.path}/$id';
static const pathParam = ':id';
static const scheduleTabIndex = 0;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こうするのいいね!👍

final int id;

@override
Widget build(BuildContext context, WidgetRef ref) {
final tabIndex = useState(scheduleTabIndex);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こうしたことなかった!ためになる!

final backgroundImageHeight = context.displaySize.width / 16 * 9;
return Scaffold(
body: SafeArea(
top: false,
child: ref.watch(tripsProvider).when(
data: (trips) {
final trip = trips.firstWhereOrNull((trip) => trip.id == id);
if (trip == null) {
return const Center(
child: ErrorCat(
AppException(
code: 'not_found',
message: '選択した旅の予定が見つかりませんでした。',
),
null,
),
);
}

return NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
// TODO(seigi0714): スクロールした場合のみ表示したい。
title: Text(
trip.title.value,
style: context.textTheme.titleLarge,
),
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[memo]
ここtitleで取るのちょっとイケてないから消したいけど,ちょいめんどくさそうやから一旦おいてる!
スクリーンショット 2023-04-24 21 37 06

expandedHeight:
backgroundImageHeight + TripOverviewCard.height / 2,
flexibleSpace: FlexibleSpaceBar(
background: SizedBox(
height: backgroundImageHeight +
TripOverviewCard.height / 2,
child: Stack(
alignment: Alignment.topCenter,
children: [
SizedBox(
height: backgroundImageHeight,
child: Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: NetworkImage(
'https://tsunagutabi.com/wp-content/uploads/2020/04/%E6%97%85%E8%A1%8C%E3%83%96%E3%83%AD%E3%82%B0%E3%81%AB%E3%81%8A%E3%81%99%E3%81%99%E3%82%81%E3%81%AE%E3%83%95%E3%83%AA%E3%83%BC%E7%94%BB%E5%83%8F%EF%BC%86%E7%B4%A0%E6%9D%90%E3%82%B5%E3%82%A4%E3%83%88%E3%81%BE%E3%81%A8%E3%82%81.jpg',
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[memo]
一旦仮の画像入れてる!
その旅ごとのホーム画像みたいなのを入れれる用にする!

),
fit: BoxFit.cover,
),
),
),
),
Positioned(
top: backgroundImageHeight -
TripOverviewCard.height / 2,
child: TripOverviewCard(trip),
),
],
),
),
),
pinned: true,
bottom: PreferredSize(
preferredSize: const Size.fromHeight(48),
child: TabBar(
controller: TabController(
length: 2,
vsync: Scaffold.of(context),
initialIndex: tabIndex.value,
),
labelColor: Colors.black,
tabs: const [
Tab(
text: '🗓️日程',
),
Tab(
text: '🧳持ち物',
)
],
onTap: (i) => tabIndex.value = i,
),
),
),
];
},
body: tabIndex.value == scheduleTabIndex
? const TripSchedule()
: const TripBelongingList(),
);
},
error: ErrorCat.new,
loading: CarDrivingLoading.new,
),
),
);
}
}
12 changes: 12 additions & 0 deletions lib/view/widgets/trips/trip_belonging_list.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'package:flutter/material.dart';

class TripBelongingList extends StatelessWidget {
const TripBelongingList({super.key});

@override
Widget build(BuildContext context) {
return const Center(
child: Text('持ち物リスト'),
);
}
}
69 changes: 37 additions & 32 deletions lib/view/widgets/trips/trip_card.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart';
import 'package:lottie/lottie.dart';
import 'package:trip_app_nativeapp/core/extensions/build_context.dart';
import 'package:trip_app_nativeapp/core/extensions/datetime.dart';
import 'package:trip_app_nativeapp/core/gen/assets.gen.dart';
import 'package:trip_app_nativeapp/features/trips/domain/entity/trip/trip.dart';
import 'package:trip_app_nativeapp/view/pages/trips/trip_detail_page.dart';

class TripCard extends StatelessWidget {
const TripCard(this.trip, {super.key});
Expand All @@ -13,39 +15,42 @@ class TripCard extends StatelessWidget {

@override
Widget build(BuildContext context) {
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(8),
child: Column(
mainAxisSize: MainAxisSize.min, // Columnの高さを最小限にする
children: [
Expanded(
child: Lottie.asset(
Assets.lotties.tripCard,
height: context.displaySize.height * 0.14,
return GestureDetector(
onTap: () => context.push(TripDetailPage.path(id: trip.id)),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(8),
child: Column(
mainAxisSize: MainAxisSize.min, // Columnの高さを最小限にする
children: [
Expanded(
child: Lottie.asset(
Assets.lotties.tripCard,
height: context.displaySize.height * 0.14,
),
),
Text(
trip.title.value,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: context.textTheme.titleLarge,
),
const Gap(8),
Text(
'🛫 ${trip.period.fromDate.toJsonDateString()}',
style: context.textTheme.titleMedium,
),
const Gap(8),
Text(
'${trip.period.endDate.toJsonDateString()} 🔚',
style: context.textTheme.titleMedium,
),
),
Text(
trip.title.value,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: context.textTheme.titleLarge,
),
const Gap(8),
Text(
'🛫 ${trip.period.fromDate.toJsonDateString()}',
style: context.textTheme.titleMedium,
),
const Gap(8),
Text(
'${trip.period.endDate.toJsonDateString()} 🔚',
style: context.textTheme.titleMedium,
),
],
],
),
),
),
);
Expand Down
89 changes: 89 additions & 0 deletions lib/view/widgets/trips/trip_overview_card.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import 'dart:developer';

import 'package:flutter/material.dart';
import 'package:trip_app_nativeapp/core/extensions/build_context.dart';
import 'package:trip_app_nativeapp/core/extensions/datetime.dart';
import 'package:trip_app_nativeapp/features/trips/domain/entity/trip/trip.dart';

class TripOverviewCard extends StatelessWidget {
const TripOverviewCard(this.trip, {super.key});

final ExistingTrip trip;
static const height = 150.0;

@override
Widget build(BuildContext context) {
return Center(
child: SizedBox(
height: height,
width: context.displaySize.width * 0.95,
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 4,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Container(
alignment: Alignment.centerLeft,
child: Text(
trip.title.value,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: context.textTheme.headlineMedium,
),
),
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'🛫 ${trip.period.fromDate.toJsonDateString()}',
style: context.textTheme.titleMedium,
),
Text(
'${trip.period.endDate.toJsonDateString()} 🔚',
style: context.textTheme.titleMedium,
),
],
),
),
SizedBox(
width: 36,
height: 36,
child: IconButton(
onPressed: () => log('share'),
icon: const Icon(
Icons.share,
),
),
),
SizedBox(
width: 36,
height: 36,
child: IconButton(
onPressed: () => log('edit'),
icon: const Icon(
Icons.edit,
),
),
),
],
),
],
),
),
),
),
);
}
}
12 changes: 12 additions & 0 deletions lib/view/widgets/trips/trip_schedule.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'package:flutter/material.dart';

class TripSchedule extends StatelessWidget {
const TripSchedule({super.key});

@override
Widget build(BuildContext context) {
return const Center(
child: Text('スケジュール'),
);
}
}
Loading
Loading