stacked icon indicating copy to clipboard operation
stacked copied to clipboard

[bug]: Stacked 3.4.0. IndexTrackingViewModel PageTransitionSwitcher

Open bdairy opened this issue 1 year ago • 6 comments

Describe the bug

Not sure if this is related,, earlier versions of stacked I used to create Tabs page with PageTransitionSwitcher,, this will animate transition between tabs from BottomNavigationBar,,, using the same approach here is not working,, not sure why,, maybe you can help

The source code is below below and I am attaching a video of how it looks like

To reproduce

class HomeView extends StackedView<HomeViewModel> {
  const HomeView({Key? key}) : super(key: key);

  @override
  Widget builder(
    BuildContext context,
    HomeViewModel viewModel,
    Widget? child,
  ) {
    return Scaffold(
      bottomNavigationBar: BottomNavigationBar(
          type: BottomNavigationBarType.fixed,
          currentIndex: viewModel.currentIndex,
          selectedLabelStyle: const TextStyle(
            fontSize: 12,
          ),
          selectedItemColor: SystemColors.primary,
          unselectedItemColor: SystemColors.black,
          unselectedLabelStyle: const TextStyle(fontSize: 11),
          onTap: (index) async {
            viewModel.setIndex(index);
          },
          items: [
            ...viewModel.tabs.asMap().entries.map((entry) {
              int index = entry.key;

              return BottomNavigationBarItem(
                icon: viewModel.getTabIcon(index),
                label: viewModel.getTabName(index),
                activeIcon: viewModel.getTabActiveIcon(index),
              );
            }).toList()
          ]),
      body: PageTransitionSwitcher(
        key: key,
        duration: const Duration(milliseconds: 2000),
        reverse: viewModel.reverse,
        transitionBuilder: (Widget child, Animation<double> primaryAnimation,
            Animation<double> secondaryAnimation) {
          return SharedAxisTransition(
            animation: primaryAnimation,
            secondaryAnimation: secondaryAnimation,
            transitionType: SharedAxisTransitionType.vertical,
            fillColor: Colors.red,
            child: child,
          );
        },
        child: Container(
            decoration: const BoxDecoration(
                color: SystemColors.lightbg),
            child: viewModel.getTab(viewModel.currentIndex)),
      ),
    );
  }

  @override
  HomeViewModel viewModelBuilder(
    BuildContext context,
  ) =>
      HomeViewModel();
}

View model is IndexTrackingViewModel

class HomeViewModel extends IndexTrackingViewModel {
 
  List<Map<String, dynamic>> get tabs => [
        {
          'tab': const DashboardView(),
          'name': LocaleKeys.tabs_dashboard,
          'icon': 'dashboard.svg'
        },
        {
          'tab': const EventsView(),
          'name': LocaleKeys.tabs_events,
          'icon': 'events.svg'
        },
        {
          'tab': const NewsView(),
          'name': LocaleKeys.tabs_news,
          'icon': 'news.svg'
        },
        {
          'tab': const MyProfileView(),
          'name': LocaleKeys.tabs_my_profile,
          'icon': 'profile.svg'
        },
      ];

  getTab(int index) {
    return tabs[index]['tab'];
  }

  getTabIcon(int index) {

    return Padding(
      padding: const EdgeInsets.all(3),
      child: SvgPicture.asset(
        'assets/icons/${tabs[index]['icon']}',
      ),
    );
  }

  getTabActiveIcon(int index) {
    return Container(
      color: SystemColors.primary[50],
      padding: const EdgeInsets.all(3),
      child: SvgPicture.asset(
        'assets/icons/${tabs[index]['icon']}',
      ),
    );
  }

  getTabName(int index) {
    return tr('${tabs[index]['name']}');
  }


  changeTab(int index) async {
    setIndex(index);
  }
}

Expected behavior

No response

Screenshots

https://github.com/Stacked-Org/stacked/assets/10797121/5321dbad-e3a9-4970-8690-0603862daa5d

Additional Context

No response

bdairy avatar Aug 07 '23 15:08 bdairy

Hi @bdairy , how are you? What is the code below 👇 ? tr function 😲 ?

getTabName(int index) {
    return tr('${tabs[index]['name']}');
}

IMPORTANT You are using UI (Widgets) inside your ViewModel, that is not the way to go, you are breaking the conventions. All your UI code should stay in your View and all your presentation logic should be on your ViewModel.

ferrarafer avatar Aug 07 '23 15:08 ferrarafer

Hi @bdairy , how are you? What is the code below 👇 ? tr function 😲 ?


getTabName(int index) {

    return tr('${tabs[index]['name']}');

}

IMPORTANT

You are using UI (Widgets) inside your ViewModel, that is not the way to go, you are breaking the conventions. All your UI code should stay in your View and all your presentation logic should be on your ViewModel.

@ferrarafer thanks for the reply,

tr function is a translation function provided by a package called EeasyLocalization,, basically getting the translated text based on your locale and a provided key.

For the point you mentioned I agree . I should've created a widget in the view , but this is not the final code yet. I just posted hoping to focus on the transition issue. Thanks for the guidance

bdairy avatar Aug 07 '23 21:08 bdairy

@bdairy you can do something like below to solve your problem.

PageTransition

class PageTransition extends StatelessWidget {
  const PageTransition({
    Key? key,
    required this.child,
    required this.reverse,
  }) : super(key: key);

  final bool reverse;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return PageTransitionSwitcher(
      reverse: reverse,
      duration: const Duration(milliseconds: 300),
      transitionBuilder: (
        Widget child,
        Animation<double> primaryAnimation,
        Animation<double> secondaryAnimation,
      ) {
        return SharedAxisTransition(
          animation: primaryAnimation,
          secondaryAnimation: secondaryAnimation,
          transitionType: SharedAxisTransitionType.vertical,
          fillColor: Colors.red,
          child: child,
        );
      },
      child: child,
    );
  }
}

HomeView

class HomeView extends StackedView<HomeViewModel> {
  final int startingIndex;
  const HomeView({Key? key, this.startingIndex = 0}) : super(key: key);

  static const List<Map<String, dynamic>> tabs = [
        {
          'tab': const DashboardView(),
          'name': LocaleKeys.tabs_dashboard,
          'icon': 'dashboard.svg'
        },
        {
          'tab': const EventsView(),
          'name': LocaleKeys.tabs_events,
          'icon': 'events.svg'
        },
        {
          'tab': const NewsView(),
          'name': LocaleKeys.tabs_news,
          'icon': 'news.svg'
        },
        {
          'tab': const MyProfileView(),
          'name': LocaleKeys.tabs_my_profile,
          'icon': 'profile.svg'
        },
      ];

  @override
  Widget builder(
    BuildContext context,
    HomeViewModel viewModel,
    Widget? child,
  ) {
    return Scaffold(
      body: PageTransition(
        reverse: viewModel.reverse,
        child: tabs[viewModel.currentIndex]['tab'],
      ),
      bottomNavigationBar: BottomNavigationBar(
          type: BottomNavigationBarType.fixed,
          currentIndex: viewModel.currentIndex,
          selectedLabelStyle: const TextStyle(fontSize: 12),
          selectedItemColor: SystemColors.primary,
          unselectedItemColor: SystemColors.black,
          unselectedLabelStyle: const TextStyle(fontSize: 11),
          onTap: viewModel.setIndex,
          items: [
            ...tabs.asMap().entries.map((entry) {
              int index = entry.key;

              return BottomNavigationBarItem(
                icon: viewModel.getTabIcon(index),
                label: viewModel.getTabName(index),
                activeIcon: viewModel.getTabActiveIcon(index),
              );
            }).toList()
          ]),
    );
  }

  @override
  HomeViewModel viewModelBuilder(
    BuildContext context,
  ) =>
      HomeViewModel();
}

NOTE Move getTabIcon to the view, should not be on the viewmodel.

ferrarafer avatar Aug 08 '23 03:08 ferrarafer

@ferrarafer that worked like charm,,, So I guess because we are rebuilding the UI whenever we change the index,, the animation is lost,, MY BIG question,, what is the difference between the earlier versions of stacked where we use normal stateless widgets with reactive view models,, and now when we just use the StackedView???

Again thanks for the helo @ferrarafer

bdairy avatar Aug 08 '23 09:08 bdairy

No problem, glad to hear it worked.

Under the hood is the same, we just changed some styling methods and names because we think it's more comfortable and readable. It ends up in a functions with less nesting (indentation).

As you can see on the documentation, you can still pass v1 to the create view command on the stacked_cli to use the ViewModelBuilder but we set StackedView as the default style because is the one we like more.

ferrarafer avatar Aug 08 '23 12:08 ferrarafer

@ferrarafer awesome work but one problem I noticed is the millisecond white flicker or any background color behind the PageTransition or the fillColor shows before the transition execute, it's somehow hurts in the eye how to avoid that?

Dionnie123 avatar Dec 12 '23 12:12 Dionnie123

@Dionnie123 that is due to your surface color set in the theme.

You can change it to match the expected background or set it to transparent.

FilledStacks avatar Oct 04 '24 10:10 FilledStacks