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

#128 implement guide detail view #147

Merged
merged 8 commits into from
Aug 8, 2024
346 changes: 209 additions & 137 deletions lib/api_base/schema.graphql

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions lib/config/ttl_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ enum TtlKey {
departmentDetailsRepository,
aboutUsRepository,
buildingsRepository,
guideDetailsRepository
// ... add a new key here if you create a new repository
}

Expand All @@ -35,6 +36,7 @@ abstract class TtlStrategy {
departmentsRepository: thirtyDays,
aboutUsRepository: thirtyDays,
departmentDetailsRepository: thirtyDays,
guideDetailsRepository: thirtyDays,
);

static Duration get(TtlKey key) {
Expand Down
7 changes: 7 additions & 0 deletions lib/config/ui_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,10 @@ abstract class AboutUsConfig {
static const iconPadding = 10.0;
static const photoSize = 92.0;
}

abstract class GuideDetailViewConfig {
static const paddingSmall = 4.0;
static const paddingMedium = 16.0;
static const paddingLarge = 32.0;
static const borderRadius = 8.0;
}
137 changes: 137 additions & 0 deletions lib/features/guide_detail_view/guide_detail_view.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import "package:auto_route/auto_route.dart";
import "package:flutter/material.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart";

import "../../api_base/directus_assets_url.dart";
import "../../config/ui_config.dart";
import "../../theme/app_theme.dart";
import "../../utils/context_extensions.dart";
import "../../widgets/detail_views/detail_view_app_bar.dart";
import "../../widgets/loading_widgets/shimmer_loading.dart";
import "../../widgets/loading_widgets/simple_previews/preview_text_prototype.dart";
import "../../widgets/my_cached_image.dart";
import "../../widgets/my_error_widget.dart";
import "repository/guide_detail_view_repository.dart";
import "widgets/my_expansion_tile.dart";

@RoutePage()
class GuideDetailView extends StatelessWidget {
const GuideDetailView({
@PathParam("id") required this.id,
super.key,
});

final String id;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: DetailViewAppBar(context, title: context.localize.guide),
body: _GuideDetailDataView(id: id),
);
}
}

class _GuideDetailDataView extends ConsumerWidget {
final String id;

const _GuideDetailDataView({required this.id});

@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(guideDetailsRepositoryProvider(id));
return switch (state) {
AsyncLoading() => const _GuideDetailLoading(),
AsyncError(:final error) => MyErrorWidget(error),
AsyncValue(:final value) => ListView(
children: [
MyCachedImage(value?.cover?.filename_disk?.directusUrl),
Padding(
padding:
const EdgeInsets.all(GuideDetailViewConfig.paddingMedium),
child: HtmlWidget(
value?.description ?? "",
textStyle: context.textTheme.body.copyWith(fontSize: 16),
),
),
ListView.separated(
Copy link
Member

Choose a reason for hiding this comment

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

oh damn. ListView inside a ListView and it works. Crazy but very good job :)

Copy link
Member Author

Choose a reason for hiding this comment

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

That's because Stack Overflow and those 2 magic lines:

shrinkWrap: true,
physics: const ClampingScrollPhysics(),

Copy link
Member

Choose a reason for hiding this comment

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

Yeah I know but still crazy

shrinkWrap: true,
physics: const ClampingScrollPhysics(),
padding: const EdgeInsets.only(
bottom: GuideDetailViewConfig.paddingLarge,
),
itemCount: value?.questions?.length ?? 0,
itemBuilder: (context, index) {
final question = value?.questions?[index]?.FAQ_id;
return MyExpansionTile(
title: question?.question ?? "",
description: question?.answer ?? "",
);
},
separatorBuilder: (context, index) => const SizedBox(
height: 8,
),
),
],
)
};
}
}

class _GuideDetailLoading extends StatelessWidget {
const _GuideDetailLoading();

@override
Widget build(BuildContext context) {
return Shimmer(
linearGradient: shimmerGradient,
child: ListView(
physics: const NeverScrollableScrollPhysics(),
children: [
ShimmerLoadingItem(
child: Container(
color: Colors.white,
width: double.infinity,
height: 300,
),
),
Padding(
padding: const EdgeInsets.all(GuideDetailViewConfig.paddingMedium),
child: ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, _) {
return ShimmerLoadingItem(
child: PreviewTextPrototype(
width: double.infinity,
),
);
},
separatorBuilder: (context, _) => const SizedBox(
height: 8,
),
itemCount: 5,
),
),
Padding(
padding: const EdgeInsets.all(GuideDetailViewConfig.paddingMedium),
child: ShimmerLoadingItem(
child: ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, _) {
return const MyExpansionTileLoading();
},
separatorBuilder: (context, _) => const SizedBox(
height: 8,
),
itemCount: 3,
),
),
),
],
),
);
}
}
15 changes: 15 additions & 0 deletions lib/features/guide_detail_view/repository/getGuideDetails.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
query GetGuideDetails($id: ID!) {
FAQ_Types_by_id(id: $id){
name,
cover {
filename_disk
},
description,
questions {
FAQ_id {
question,
answer
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import "package:riverpod_annotation/riverpod_annotation.dart";
import "../../../api_base/watch_query_adapter.dart";
import "../../../config/ttl_config.dart";
import "getGuideDetails.graphql.dart";

part "guide_detail_view_repository.g.dart";

typedef GuideDetails = Query$GetGuideDetails$FAQ_Types_by_id;
typedef _GetGuideDetails = WatchOptions$Query$GetGuideDetails;
typedef _Vars = Variables$Query$GetGuideDetails;
@riverpod
Stream<GuideDetails?> guideDetailsRepository(
GuideDetailsRepositoryRef ref,
String id,
) async* {
final stream = ref.watchQueryWithCache(
_GetGuideDetails(
eagerlyFetchResults: true,
variables: _Vars(id: id),
),
TtlKey.guideDetailsRepository,
);
yield* stream.map((event) => event?.FAQ_Types_by_id);
}
71 changes: 71 additions & 0 deletions lib/features/guide_detail_view/widgets/my_expansion_tile.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import "package:flutter/material.dart";

import "../../../config/ui_config.dart";
import "../../../theme/app_theme.dart";
import "../../../widgets/loading_widgets/shimmer_loading.dart";

class MyExpansionTile extends StatelessWidget {
final String title;
final String description;

const MyExpansionTile({
required this.title,
required this.description,
});

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: GuideDetailViewConfig.paddingMedium,
),
child: ExpansionTile(
mikolaj-jalocha marked this conversation as resolved.
Show resolved Hide resolved
title: Text(title, style: context.textTheme.title),
backgroundColor: context.colorTheme.greyLight,
collapsedBackgroundColor: context.colorTheme.greyLight,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(GuideDetailViewConfig.borderRadius),
),
collapsedShape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(GuideDetailViewConfig.borderRadius),
),
iconColor: context.colorTheme.orangePomegranade,
collapsedIconColor: context.colorTheme.orangePomegranade,
children: [
Padding(
padding: const EdgeInsets.symmetric(
vertical: GuideDetailViewConfig.paddingSmall,
),
child: ListTile(
title: Text(
description,
style: context.textTheme.body.copyWith(fontSize: 14),
),
),
),
],
),
);
}
}

class MyExpansionTileLoading extends StatelessWidget {
const MyExpansionTileLoading();

@override
Widget build(BuildContext context) {
return ShimmerLoadingItem(
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius:
BorderRadius.circular(GuideDetailViewConfig.borderRadius),
),
width: double.infinity,
height: 45,
),
);
}
}
23 changes: 23 additions & 0 deletions lib/features/guide_view/guide_view.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import "dart:async";

import "package:auto_route/auto_route.dart";
import "package:flutter/material.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
Expand Down Expand Up @@ -40,6 +42,27 @@ class GuideView extends ConsumerWidget {
),
),
),
GestureDetector(
onTap: () async {
unawaited(ref.navigateGuideDetail("1"));
},
child: Container(
padding: const EdgeInsets.all(10),
width: double.infinity,
height: 50,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: context.colorTheme.greyLight,
),
child: Text(
"Przykładowy wpis",
style: TextStyle(
fontSize: 24,
color: context.colorTheme.orangePomegranade,
),
),
),
),
],
),
),
Expand Down
5 changes: 5 additions & 0 deletions lib/features/navigator/app_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import "../about_us_view/about_us_view.dart";
import "../buildings_view/buildings_view.dart";
import "../department_detail_view/department_detail_view.dart";
import "../departments_view/departments_view.dart";
import "../guide_detail_view/guide_detail_view.dart";
import "../guide_view/guide_view.dart";
import "../home_view/home_view.dart";
import "../parkings_view/parkings_view.dart";
Expand Down Expand Up @@ -95,6 +96,10 @@ class AppRouter extends _$AppRouter {
path: "aboutUs",
page: AboutUsRoute.page,
),
AutoRoute(
path: "guide/:id",
page: GuideDetailRoute.page,
),
],
),
];
Expand Down
4 changes: 4 additions & 0 deletions lib/features/navigator/utils/navigation_commands.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,8 @@ extension NavigationX on WidgetRef {
Future<void> navigateAboutUs() async {
await _router.push(const AboutUsRoute());
}

Future<void> navigateGuideDetail(String id) async {
await _router.push(GuideDetailRoute(id: id));
}
}
1 change: 1 addition & 0 deletions lib/features/offline_messages/messages_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ extension GqlOfflineMessageX on BuildContext {
departmentsRepository: localize.offline_departments,
aboutUsRepository: localize.offline_about_us,
departmentDetailsRepository: localize.offline_department_details,
guideDetailsRepository: localize.offline_guide_details,
);

String gqlOfflineMessageLocalized(TtlKey key) {
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_pl.arb
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"offline_academic_calendar":"kalendarza akademickiego",
"offline_about_us":"o nas",
"offline_department_details":"wydziału",
"offline_guide_details":"wpisu z przewodnika",
"guide": "Przewodnik",
"deans_office" : "Dziekanat",
"fields_of_study" : "Kierunki"
Expand Down