tab navigation

This commit is contained in:
AlcalaJulian
2025-11-17 00:15:31 +01:00
parent 5ca37d2822
commit 4225f7510b
48 changed files with 801 additions and 231 deletions

View File

@@ -1,2 +1,2 @@
export 'src/presentation/dashboard_screen.dart';
export 'src/presentation/main_shell_screen.dart';
export 'dashboard_builder.dart';

View File

@@ -1,59 +0,0 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:home/home.dart';
import 'package:notifications/notifications.dart';
import 'package:profile/profile.dart';
// import 'package:provider/provider.dart';
import 'package:navigation/navigation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class DashboardScreen extends ConsumerWidget {
final NavigationContract navigationContract;
DashboardScreen({super.key, required this.navigationContract});
int currentPageIndex = 3;
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themePortProvider);
final bodies = [
HomeScreen(),
ActivityScreen(),
AlertScreen(),
ProfileScreen(),
];
return Scaffold(
bottomNavigationBar: NavigationBar(
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
onDestinationSelected: (int index) {
// setState(() {
// currentPageIndex = index;
// });
},
selectedIndex: currentPageIndex,
destinations: [
NavigationDestination(
icon: Icon(Icons.home_outlined),
label: "Inicio",
),
NavigationDestination(
icon: Icon(Icons.watch_outlined),
label: "Movimientos",
),
NavigationDestination(
icon: Icon(Icons.notifications_outlined),
label: "Alertas",
),
NavigationDestination(
icon: Icon(Icons.person_outline_outlined),
label: "Mi perfil",
),
],
),
body: bodies[currentPageIndex],
);
}
}

View File

@@ -0,0 +1,58 @@
import 'package:dashboard_shell/src/presentation/main_shell_view_model.dart';
import 'package:dashboard_shell/src/presentation/main_shell_view_state.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod/legacy.dart';
import 'package:go_router/go_router.dart';
import 'package:navigation/navigation.dart';
final mainShellViewModelProvider =
StateNotifierProvider.autoDispose<MainShellViewModel, MainShellViewState>(
(ref) => MainShellViewModel(ref: ref),
);
class DashboardScreen extends ConsumerWidget {
final NavigationContract navigationContract;
final StatefulNavigationShell navigationShell;
const DashboardScreen({
super.key,
required this.navigationContract,
required this.navigationShell,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themePortProvider);
final viewState = ref.watch(mainShellViewModelProvider);
final viewModel = ref.read(mainShellViewModelProvider.notifier);
return Scaffold(
body: navigationShell,
bottomNavigationBar: NavigationBar(
backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary),
selectedIndex: viewState.selectedIndex,
onDestinationSelected: (index) {
viewModel.onTabChanged(index);
navigationShell.goBranch(index);
},
destinations: const [
NavigationDestination(icon: Icon(Icons.home_outlined), label: "Home"),
NavigationDestination(
icon: Icon(Icons.watch_outlined),
label: "Activity",
),
NavigationDestination(
icon: Icon(Icons.notifications_outlined),
label: "Notifications",
),
NavigationDestination(
icon: Icon(Icons.person_outline_outlined),
label: "Profile",
),
],
),
);
}
}

View File

@@ -0,0 +1,13 @@
import 'package:dashboard_shell/src/presentation/main_shell_view_state.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod/legacy.dart';
class MainShellViewModel extends StateNotifier<MainShellViewState> {
MainShellViewModel({required this.ref}) : super(MainShellViewState());
final Ref ref;
void onTabChanged(int index) {
state = state.copyWith(selectedIndex: index);
}
}

View File

@@ -0,0 +1,16 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'main_shell_view_state.freezed.dart';
@freezed
abstract class MainShellViewState with _$MainShellViewState {
const factory MainShellViewState({
@Default(0) int selectedIndex,
@Default(false) bool isLoading,
@Default(false) bool isComplete,
String? error,
}) = _MainShellViewState;
}

View File

@@ -0,0 +1,280 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'main_shell_view_state.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$MainShellViewState {
int get selectedIndex; bool get isLoading; bool get isComplete; String? get error;
/// Create a copy of MainShellViewState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$MainShellViewStateCopyWith<MainShellViewState> get copyWith => _$MainShellViewStateCopyWithImpl<MainShellViewState>(this as MainShellViewState, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is MainShellViewState&&(identical(other.selectedIndex, selectedIndex) || other.selectedIndex == selectedIndex)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isComplete, isComplete) || other.isComplete == isComplete)&&(identical(other.error, error) || other.error == error));
}
@override
int get hashCode => Object.hash(runtimeType,selectedIndex,isLoading,isComplete,error);
@override
String toString() {
return 'MainShellViewState(selectedIndex: $selectedIndex, isLoading: $isLoading, isComplete: $isComplete, error: $error)';
}
}
/// @nodoc
abstract mixin class $MainShellViewStateCopyWith<$Res> {
factory $MainShellViewStateCopyWith(MainShellViewState value, $Res Function(MainShellViewState) _then) = _$MainShellViewStateCopyWithImpl;
@useResult
$Res call({
int selectedIndex, bool isLoading, bool isComplete, String? error
});
}
/// @nodoc
class _$MainShellViewStateCopyWithImpl<$Res>
implements $MainShellViewStateCopyWith<$Res> {
_$MainShellViewStateCopyWithImpl(this._self, this._then);
final MainShellViewState _self;
final $Res Function(MainShellViewState) _then;
/// Create a copy of MainShellViewState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? selectedIndex = null,Object? isLoading = null,Object? isComplete = null,Object? error = freezed,}) {
return _then(_self.copyWith(
selectedIndex: null == selectedIndex ? _self.selectedIndex : selectedIndex // ignore: cast_nullable_to_non_nullable
as int,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,isComplete: null == isComplete ? _self.isComplete : isComplete // ignore: cast_nullable_to_non_nullable
as bool,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// Adds pattern-matching-related methods to [MainShellViewState].
extension MainShellViewStatePatterns on MainShellViewState {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _MainShellViewState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _MainShellViewState() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _MainShellViewState value) $default,){
final _that = this;
switch (_that) {
case _MainShellViewState():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _MainShellViewState value)? $default,){
final _that = this;
switch (_that) {
case _MainShellViewState() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int selectedIndex, bool isLoading, bool isComplete, String? error)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _MainShellViewState() when $default != null:
return $default(_that.selectedIndex,_that.isLoading,_that.isComplete,_that.error);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int selectedIndex, bool isLoading, bool isComplete, String? error) $default,) {final _that = this;
switch (_that) {
case _MainShellViewState():
return $default(_that.selectedIndex,_that.isLoading,_that.isComplete,_that.error);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int selectedIndex, bool isLoading, bool isComplete, String? error)? $default,) {final _that = this;
switch (_that) {
case _MainShellViewState() when $default != null:
return $default(_that.selectedIndex,_that.isLoading,_that.isComplete,_that.error);case _:
return null;
}
}
}
/// @nodoc
class _MainShellViewState implements MainShellViewState {
const _MainShellViewState({this.selectedIndex = 0, this.isLoading = false, this.isComplete = false, this.error});
@override@JsonKey() final int selectedIndex;
@override@JsonKey() final bool isLoading;
@override@JsonKey() final bool isComplete;
@override final String? error;
/// Create a copy of MainShellViewState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$MainShellViewStateCopyWith<_MainShellViewState> get copyWith => __$MainShellViewStateCopyWithImpl<_MainShellViewState>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _MainShellViewState&&(identical(other.selectedIndex, selectedIndex) || other.selectedIndex == selectedIndex)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isComplete, isComplete) || other.isComplete == isComplete)&&(identical(other.error, error) || other.error == error));
}
@override
int get hashCode => Object.hash(runtimeType,selectedIndex,isLoading,isComplete,error);
@override
String toString() {
return 'MainShellViewState(selectedIndex: $selectedIndex, isLoading: $isLoading, isComplete: $isComplete, error: $error)';
}
}
/// @nodoc
abstract mixin class _$MainShellViewStateCopyWith<$Res> implements $MainShellViewStateCopyWith<$Res> {
factory _$MainShellViewStateCopyWith(_MainShellViewState value, $Res Function(_MainShellViewState) _then) = __$MainShellViewStateCopyWithImpl;
@override @useResult
$Res call({
int selectedIndex, bool isLoading, bool isComplete, String? error
});
}
/// @nodoc
class __$MainShellViewStateCopyWithImpl<$Res>
implements _$MainShellViewStateCopyWith<$Res> {
__$MainShellViewStateCopyWithImpl(this._self, this._then);
final _MainShellViewState _self;
final $Res Function(_MainShellViewState) _then;
/// Create a copy of MainShellViewState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? selectedIndex = null,Object? isLoading = null,Object? isComplete = null,Object? error = freezed,}) {
return _then(_MainShellViewState(
selectedIndex: null == selectedIndex ? _self.selectedIndex : selectedIndex // ignore: cast_nullable_to_non_nullable
as int,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,isComplete: null == isComplete ? _self.isComplete : isComplete // ignore: cast_nullable_to_non_nullable
as bool,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
// dart format on

View File

@@ -29,11 +29,17 @@ dependencies:
get_it: ^9.0.5
go_router: ^17.0.0
flutter_riverpod: ^3.0.3
freezed_annotation: ^3.1.0
freezed: ^3.2.3
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
riverpod_generator: ^3.0.3
build_runner: ^2.7.1
riverpod_lint: ^3.0.3
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec