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

Study circle detail page #39

Merged
merged 12 commits into from
Mar 11, 2024
Merged

Study circle detail page #39

merged 12 commits into from
Mar 11, 2024

Conversation

mikolaj-jalocha
Copy link
Member

#33

Implemented page that shows details of particural study circle. Along with nested navigation in home view.

# Conflicts:
#	lib/features/home_view/widgets/study_circles_section.dart
- added loading screen for study_circle_details.dart
- some parts of the code's been extracted and refactored
# Conflicts:
#	android/app/build.gradle
#	lib/config.dart
#	lib/features/bottom_nav_bar/bottom_nav_bar.dart
#	lib/features/departments_tab/departments_tab.dart
#	lib/features/home_view/home_view.dart
#	lib/features/home_view/widgets/study_circles_section.dart
#	lib/l10n/app_pl.arb
#	pubspec.yaml
…tudy-circle-detail-page

# Conflicts:
#	lib/features/home_view/home_view.dart
#	lib/features/home_view/widgets/study_circles_section.dart
Copy link
Member

@simon-the-shark simon-the-shark left a comment

Choose a reason for hiding this comment

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

Nice work. Few things noted. Don't be scared when you use all those long code blocks - usually changes are only in few lines - the examples are just long.

AsyncLoading() => const _StudyCircleDetailsLoading(),
AsyncError(:final error) => MyErrorWidget(error),
AsyncValue(:final value) => Scaffold(
appBar: PreferredSize(
Copy link
Member

Choose a reason for hiding this comment

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

There are few ways to avoid extra wrapping of custom app bar with PrefferedSizedWidget.

The way I usually do this is extending AppBar, instead of StatelessWidget (another way is to implement PrefferedSizedWidget in this custom widget, but you still need to define pref size). And when extending AppBar, we don't need to define it size. (To be honest, its definition is same as yours preffered size widget, but we just don't need to do it second time and by ourselves)

 appBar: DetailsScreenAppBar(
            context,
            title: context.localize?.study_circles ?? '',
          ),
class DetailsScreenAppBar extends AppBar {
  DetailsScreenAppBar(BuildContext context, {super.key, required String title})
      : super(
          automaticallyImplyLeading: false,
          title: TextButton(
              onPressed: () {
                Navigator.pop(context);
              },
              style: TextButton.styleFrom(
                padding: EdgeInsets.zero,
              ),
              child: Text(
                '< $title',
                style: context.textTheme.boldBodyOrange,
              )),
        );
}

It still works and behaves just the same, but I just think its clearer.

Copy link
Member Author

Choose a reason for hiding this comment

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

Indeed, much cleaner. Implemented as proposed

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
Copy link
Member

Choose a reason for hiding this comment

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

The Scaffold and your custom app bar is repeated here, and it can be avoided. Two possibilities:

  1. You can just move the switch 2 steps down the three:
class _StudyCircleDetailsLoading extends StatelessWidget {
  const _StudyCircleDetailsLoading();

  @override
  Widget build(BuildContext context) {
    return Shimmer(
      linearGradient: shimmerGradient,
      child: ListView(
        physics: const NeverScrollableScrollPhysics(),
        children: const [
          HeaderSectionLoading(),
          SizedBox(height: DetailsScreenConfig.spacerHeight),
          ContactSectionLoading(),
          SizedBox(height: DetailsScreenConfig.spacerHeight),
          AboutUsSectionLoading()
        ],
      ),
    );
  }
}

class StudyCircleDetails extends ConsumerWidget {
  const StudyCircleDetails({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final itemId = ModalRoute.of(context)?.settings.arguments as String;
    final state = ref.watch(studyCircleRepositoryProvider(itemId));

    return Scaffold(
      appBar: DetailsScreenAppBar(
        context,
        title: context.localize?.study_circles ?? '',
      ),
      body: switch (state) {
        AsyncLoading() => const _StudyCircleDetailsLoading(),
        AsyncError(:final error) => MyErrorWidget(error),
        AsyncValue(:final value) => ListView(
            children: [
              HeaderSection(
  1. Or simply extract one more widget: (this is slighty better, cause won't rebuild scaffold and app bar whenever provider changes - but not a change that is noticeable)
class StudyCircleDetails extends StatelessWidget {
  const StudyCircleDetails({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: DetailsScreenAppBar(context,
          title: context.localize?.study_circles ?? ''),
      body: const _CircleDetailsDataView(),
    );
  }
}

class _CircleDetailsDataView extends ConsumerWidget {
  const _CircleDetailsDataView();

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final itemId = ModalRoute.of(context)?.settings.arguments as String;
    final state = ref.watch(studyCircleRepositoryProvider(itemId));
    return switch (state) {
      AsyncLoading() => const _StudyCircleDetailsLoading(),
      AsyncError(:final error) => MyErrorWidget(error),
      AsyncValue(:final value) => ListView(
          children: [
           // ... contents
          ],
        ),
    };
  }
}

class _StudyCircleDetailsLoading extends StatelessWidget {
  const _StudyCircleDetailsLoading();

  @override
  Widget build(BuildContext context) {
    return Shimmer(
      linearGradient: shimmerGradient,
      child: ListView(

You can choose whatever you prefer.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thank you, the 2nd option looks great. Imo optimalization matters even in slightest matters :)

@override
Widget build(BuildContext context) {
return
text.isEmpty ? const SizedBox.shrink() :
Copy link
Member

Choose a reason for hiding this comment

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

I think, when we have a normal function body, its a bit clearer with old-fashioned if - but a matter of preferences I guess

@override
  Widget build(BuildContext context) {
    if (text.isEmpty) return const SizedBox.shrink();
    return Padding(
      padding: const EdgeInsets.all(24),
      child: Column(
//...

not forcing this one on you

@override
Widget build(BuildContext context) {
return AppBar(
automaticallyImplyLeading: false,
Copy link
Member

Choose a reason for hiding this comment

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

Apart from what I've mentioned before: quick note that at least for me, the title is centered and in figma is not. The solution is to just add centerTitle:false

class DetailsScreenAppBar extends AppBar {
  DetailsScreenAppBar(BuildContext context, {super.key, required String title})
      : super(
          centerTitle: false, // add this I guess ?
          automaticallyImplyLeading: false,
          title: TextButton(
              onPressed: () {

Zrzut ekranu 2024-03-5 o 17 21 44

Unless you've centered it on purpose, then all fine

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 an interesting one. On an Android device, everything looks normal (text is not centered). Apparently, it's some iOS platform thing here. I have added the line that you mentioned, but I don't have the possibility to test it on an iOS device. So please check and let me know :)

image

import '../../../utils/context_extensions.dart';
import '../../../widgets/my_icon.dart';

class ContactSectionData {
Copy link
Member

Choose a reason for hiding this comment

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

I would move this model to some separate file maybe. Example location: lib\features\detail_screen\models\contact_section_data.dart

Copy link
Member Author

Choose a reason for hiding this comment

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

Good idea! Implemented as proposed

style: context.textTheme.bodyOrange
.copyWith(decoration: TextDecoration.underline),
recognizer: TapGestureRecognizer()
..onTap = () => launchUrl(url),
Copy link
Member

Choose a reason for hiding this comment

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

Also recently, I've made a util class for launching urls. so you can use it:

LaunchUrlUtil.launch(url) // but the url here accepts as String, so change this as well for the widget

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed as proposed

height: MediaQuery.of(context).size.height/10,
width: double.maxFinite,
child: ShimmerLoadingItem(
child: 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.

I suppose you can use my recently madeScrollableLoaderBuilder (without calculating elements count):

SizedBox(
            height: MediaQuery.of(context).size.height / 10,
            child: ShimmerLoadingItem(
              child: ScrollableLoaderBuilder(
                mainAxisItemSize: 16,
                itemsSpacing: 4,
                scrollDirection: Axis.vertical,
                itemBuilder: (BuildContext context, int index) {
                  return Container(
                    height: 16,
                    width: MediaQuery.of(context).size.width,
                    decoration: BoxDecoration(
                      color: Colors.white,
                      borderRadius: BorderRadius.circular(16),
                    ),
                  );
                },
              ),
            ),
          ),

check if it works the same though.

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed as proposed. I didn't notice any changes in UI relative to the previous one.

width: double.maxFinite,
height: 116,
child: ListView.separated(
itemBuilder: (BuildContext context, int index) {
Copy link
Member

Choose a reason for hiding this comment

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

Here you have probably forgotten to lock the scrolling:

                physics: const NeverScrollableScrollPhysics(),

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah.. Thank you. Changed as proposed.

}
}

class _HomeViewNavigator extends StatelessWidget {
Copy link
Member

Choose a reason for hiding this comment

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

To be honest, nesting navigator inside of HomeScreen and only HomeScreen is a bit weird. We will want to start the same details screen from others tab. I would change it this way: (As I said, I'm not the best authority on the topic, but I think this will be fine):

  1. Reverse everything in this file as it was previously
  2. Create new file lib\navigator.dart with following content: (it's basically a bit refactored version of your navigator)
import 'package:flutter/cupertino.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import 'features/bottom_nav_bar/bottom_nav_bar.dart';
import 'features/details_screen/study_circle_details.dart';
import 'widgets/my_error_widget.dart';

class AppNavigator {
  final navigatorKey = GlobalKey<NavigatorState>();

  static Route<dynamic> onGenerateRoute(RouteSettings settings) {
    return CupertinoPageRoute(
      builder: switch (settings.name) {
        _Routes.home => (_) => const BottomNavBar(),
        _Routes.studyCircleDetails => (_) => const StudyCircleDetails(),
        _ => (_) => MyErrorWidget('Invalid route: ${settings.name}')
      },
      settings: settings,
    );
  }

  void navigateToStudyCircleDetails(String argument) {
    navigatorKey.currentState
        ?.pushNamed(_Routes.studyCircleDetails, arguments: argument);
  }
}

class _Routes {
  _Routes._(); //no constructor available

  static const home = '/';
  static const studyCircleDetails = 'study-circle-details';
}

final navigatorProvider = Provider<AppNavigator>((ref) {
  return AppNavigator();
});
  1. Then in lib\main.dart, we can provide our navigator to be used as default one:
class MyApp extends ConsumerWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return MaterialApp(
      title: MyAppConfig.title,
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
      theme: ThemeData(extensions: const [AppTheme()]),
      debugShowCheckedModeBanner: false,
      onGenerateRoute: AppNavigator.onGenerateRoute, //this
      navigatorKey: ref.watch(navigatorProvider).navigatorKey, // and this
    );
  }
}
  1. Then you can use navigation wherever provider's ref is available:
ref.watch(navigatorProvider).navigateToStudyCircleDetails(id)
// ref.watch or ref.read - depending on the context

I'm not entirely sure it's the best way possible, but I think this will be more standard and usefull (as it will be global navigator for all tabs)

Copy link
Member Author

Choose a reason for hiding this comment

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

This solution is defenitely better than mine, thank you :-) (still searching for good tutorial on topic).

I've changed everything as proposed, but one problem arose:

After changing the navigator logic, bottom bar's not visible when details screen's open.

image

In Figma, mentioned screen has bottom bar with proper tab marked as selected:

image

Any idea how we can cope with this?

Copy link
Member

Choose a reason for hiding this comment

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

I guess we can ask during our next weekly if this nav bar down there in Figma is actually supposed to be there. Cause I personally find this a bit odd, so I haven't noticed it before. So it's a good catch from you. We can discuss in weekly if we want such a nav bar in these overlaying"detail views"

If we actually want the navbar there, then I guess some sort of custom navigator nesting might be the actual best solution for it. Probably something similar to your previous one, but with some refactoring like we did just now and we would need to nest it not only in the home tab (HomeScreen) but the whole view (I'm talking about BottomNavBar widget - the name is kinda misleading).

Cause if we follow the Figma to the letter, it changes the tab to this "rocket" icon as well, so we would need to nest it in the whole view and not only the home tab. And this way we won't repeat the code for every tab too.

Copy link
Member

Choose a reason for hiding this comment

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

I will play with it tomorrow (probably). Cause I have one idea, but need to test it now

Copy link
Member

Choose a reason for hiding this comment

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

I've left note in the bottom of pr

@@ -78,7 +84,9 @@ class _StudyCirclesDataList extends StatelessWidget {
title: circle.name,
shortDescription: circle.description,
photoUrl: circle.photo?.previewUrl,
onClick: () {},
onClick: (){
onNavigate(circle.id);
Copy link
Member

Choose a reason for hiding this comment

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

I guess, after above changes, you can delete onNavigate and use zef.watch(navigatorProvider).navigateSMth directly here (?)

Copy link
Member Author

Choose a reason for hiding this comment

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

changed as proposed

@simon-the-shark simon-the-shark linked an issue Mar 5, 2024 that may be closed by this pull request
@simon-the-shark
Copy link
Member

simon-the-shark commented Mar 5, 2024

Also, I've forgot to add one thing in the end.

Pretty standard effect in such details views is when this background image and logo at the top, when scrolled down, it shrinks to simple app bar (or dissapears).

This is not a requested change - we can defienetly merge without it, but I think we can add this feature as TODO (we can discuss it in the weekly beforehand - but I think it will be nice in all details views with similar layout as this).

It is very easily achieavable in Flutter with CustomScrollView and SliverAppBar:
https://www.youtube.com/watch?v=G6qJkjt08k0
https://api.flutter.dev/flutter/material/SliverAppBar-class.html

  • and many many more tutorials on the net.

Below are very simple example gifs from https://medium.com/@omer28gunaydin/flutter-sliverappbar-widget-fb5f4f30cf62:

exmpl1
exmpl2

but again - don't need to worry about this now. We can merge without it and this is potential future TODO task

@mikolaj-jalocha
Copy link
Member Author

Also, I've forgot to add one thing in the end.

Pretty standard effect in such details views is when this background image and logo at the top, when scrolled down, it shrinks to simple app bar (or dissapears).

This is not a requested change - we can defienetly merge without it, but I think we can add this feature as TODO (we can discuss it in the weekly beforehand - but I think it will be nice in all details views with similar layout as this).

It is very easily achieavable in Flutter with CustomScrollView and SliverAppBar: https://www.youtube.com/watch?v=G6qJkjt08k0 https://api.flutter.dev/flutter/material/SliverAppBar-class.html

  • and many many more tutorials on the net.

Below are very simple example gifs from https://medium.com/@omer28gunaydin/flutter-sliverappbar-widget-fb5f4f30cf62:

exmpl1 exmpl1 exmpl2 exmpl2

but again - don't need to worry about this now. We can merge without it and this is potential future TODO task

Good idea! I will create separate TODO for this and cope with it later :)

mikolaj-jalocha and others added 2 commits March 10, 2024 20:12
- optimized study_circle_details.dart
- on IOS devices appbar title's now not centered
@simon-the-shark
Copy link
Member

simon-the-shark commented Mar 10, 2024

@mikolaj-jalocha I've pushed a commit with what I've managed to achieve. Now we have a nested navigator with an overlaying bottom nav bar. I think it works pretty fine. Check it out and tell me what you think of this solution.

Nevertheless, we still can discuss this on a weekly, how exactly we want this navigation and bottom nav bar to work. I see many possible questions about a specific behavior when we click that or this or even whether we want to see this nav bar in detail views or not. So I think we can discuss this with more people.

But if you think this will work ok, then we can merge with this now and after a discussion, we'll have another TODO task to correct some potential behavior changes (or just revert my commit if we'll decide to drop this nav bar in detail views)

@mikolaj-jalocha
Copy link
Member Author

@mikolaj-jalocha I've pushed a commit with what I've managed to achieve. Now we have a nested navigator with an overlaying bottom nav bar. I think it works pretty fine. Check it out and tell me what you think of this solution.

Nevertheless, we still can discuss this on a weekly, how exactly we want this navigation and bottom nav bar to work. I see many possible questions about a specific behavior when we click that or this or even whether we want to see this nav bar in detail views or not. So I think we can discuss this with more people.

But if you think this will work ok, then we can merge with this now and after a discussion, we'll have another TODO task to correct some potential behavior changes (or just revert my commit if we'll decide to drop this nav bar in detail views)

Thanks for your support here! I've simillar view in this regard as yours. In my opinion previous behavior without bottom nav bar within details screen or the one that didn't change actual tab were working fine. Nevertheless I think we can merge with this feature for the Figma's sake :) and discuss the matter later and take appropraite steps

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Study circle detail page
2 participants