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

feat: custom tab order #106

Merged
merged 1 commit into from
Dec 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 48 additions & 9 deletions lib/cubits/settings_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:kitchenowl/config.dart';
import 'package:kitchenowl/enums/views_enum.dart';
import 'package:kitchenowl/models/server_settings.dart';
import 'package:kitchenowl/services/api/api_service.dart';
import 'package:kitchenowl/services/storage/storage.dart';
Expand Down Expand Up @@ -45,7 +46,8 @@ class SettingsCubit extends Cubit<SettingsState> {
));

if (ApiService.getInstance().serverSettings.featureExpenses != null ||
ApiService.getInstance().serverSettings.featurePlanner != null) {
ApiService.getInstance().serverSettings.featurePlanner != null ||
ApiService.getInstance().serverSettings.viewOrdering != null) {
serverSettings = ApiService.getInstance().serverSettings;
}

Expand All @@ -68,18 +70,44 @@ class SettingsCubit extends Cubit<SettingsState> {
emit(state.copyWith(dynamicAccentColor: dynamicAccentColor));
}

void setFeaturePlanner(bool featurePlanner) {
void setView(ViewsEnum view, bool value) {
if (view == ViewsEnum.mealPlanner) {
final settings = state.serverSettings.copyWith(featurePlanner: value);
emit(state.copyWith(serverSettings: settings));
PreferenceStorage.getInstance()
.write(key: 'serverSettings', value: jsonEncode(settings.toJson()));
ApiService.getInstance()
.setSettings(ServerSettings(featurePlanner: value));
}
if (view == ViewsEnum.balances) {
final settings = state.serverSettings.copyWith(featureExpenses: value);
emit(state.copyWith(serverSettings: settings));
PreferenceStorage.getInstance()
.write(key: 'serverSettings', value: jsonEncode(settings.toJson()));
ApiService.getInstance()
.setSettings(ServerSettings(featureExpenses: value));
}
}

void reorderView(int oldIndex, int newIndex) {
final l = List.of(state.serverSettings.viewOrdering!);
l.insert(newIndex, l.removeAt(oldIndex));
final settings = state.serverSettings.copyWith(viewOrdering: l);
emit(state.copyWith(serverSettings: settings));
PreferenceStorage.getInstance()
.writeBool(key: 'featurePlanner', value: featurePlanner);
ApiService.getInstance()
.setSettings(ServerSettings(featurePlanner: featurePlanner));
.write(key: 'serverSettings', value: jsonEncode(settings.toJson()));
ApiService.getInstance().setSettings(ServerSettings(viewOrdering: l));
}

void setFeatureExpenses(bool featureExpenses) {
void resetViewOrder() {
final settings =
state.serverSettings.copyWith(viewOrdering: ViewsEnum.values);
emit(state.copyWith(serverSettings: settings));
PreferenceStorage.getInstance()
.writeBool(key: 'featureExpenses', value: featureExpenses);
ApiService.getInstance()
.setSettings(ServerSettings(featureExpenses: featureExpenses));
.write(key: 'serverSettings', value: jsonEncode(settings.toJson()));
ApiService.getInstance().setSettings(const ServerSettings(
viewOrdering: ViewsEnum.values,
));
}
}

Expand Down Expand Up @@ -108,4 +136,15 @@ class SettingsState extends Equatable {

@override
List<Object?> get props => [themeMode, serverSettings, dynamicAccentColor];

bool isViewActive(ViewsEnum view) {
if (view == ViewsEnum.mealPlanner) {
return serverSettings.featurePlanner ?? true;
}
if (view == ViewsEnum.balances) {
return serverSettings.featureExpenses ?? true;
}

return true;
}
}
72 changes: 72 additions & 0 deletions lib/enums/views_enum.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import 'package:flutter/material.dart';
import 'package:kitchenowl/kitchenowl.dart';

enum ViewsEnum {
shoppingList,
recipes,
mealPlanner,
balances,
profile;

String toLocalizedString(BuildContext context) {
final loc = AppLocalizations.of(context)!;

return [
loc.shoppingList,
loc.recipes,
loc.mealPlanner,
loc.balances,
loc.profile,
][index];
}

IconData toIcon() {
return const [
Icons.shopping_bag_outlined,
Icons.receipt,
Icons.calendar_today_rounded,
Icons.account_balance_rounded,
Icons.person,
][index];
}

bool isOptional() {
return const [
false,
false,
true,
true,
false,
][index];
}

/// Adds all missing views
static List<ViewsEnum> addMissing(Iterable<ViewsEnum> iterable) {
final l = iterable.toList();
l.addAll(ViewsEnum.values.where((e) => !iterable.contains(e)));

return l;
}

static ViewsEnum? parse(String str) {
switch (str) {
case 'shoppingList':
return ViewsEnum.shoppingList;
case 'recipes':
return ViewsEnum.recipes;
case 'mealPlanner':
return ViewsEnum.mealPlanner;
case 'balances':
return ViewsEnum.balances;
case 'profile':
return ViewsEnum.profile;
default:
return null;
}
}

@override
String toString() {
return name;
}
}
2 changes: 2 additions & 0 deletions lib/l10n/app_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@
"@mealPlanner": {},
"minutesAbbrev": "Min",
"@minutesAbbrev": {},
"more": "Mehr",
"@more": {},
"name": "Name",
"@name": {},
"next": "Weiter",
Expand Down
2 changes: 2 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@
"@mealPlanner": {},
"minutesAbbrev": "min",
"@minutesAbbrev": {},
"more": "More",
"@more": {},
"name": "Name",
"@name": {},
"next": "Next",
Expand Down
35 changes: 29 additions & 6 deletions lib/models/server_settings.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
import 'package:kitchenowl/enums/views_enum.dart';

import 'model.dart';

class ServerSettings extends Model {
final bool? featurePlanner;
final bool? featureExpenses;
final List<ViewsEnum>? viewOrdering;

const ServerSettings({this.featurePlanner, this.featureExpenses});
const ServerSettings({
this.featurePlanner,
this.featureExpenses,
this.viewOrdering,
});

factory ServerSettings.fromJson(Map<String, dynamic> map) => ServerSettings(
featurePlanner: map['planner_feature'] ?? false,
featureExpenses: map['expenses_feature'] ?? false,
);
factory ServerSettings.fromJson(Map<String, dynamic> map) {
List<ViewsEnum> viewOrdering = ViewsEnum.values;
if (map.containsKey('view_ordering')) {
viewOrdering = ViewsEnum.addMissing(List.from(map['view_ordering']
.map((e) => ViewsEnum.parse(e))
.where((e) => e != null)));
}

return ServerSettings(
featurePlanner: map['planner_feature'] ?? false,
featureExpenses: map['expenses_feature'] ?? false,
viewOrdering: viewOrdering,
);
}

@override
List<Object?> get props => [featurePlanner, featureExpenses];
List<Object?> get props => [featurePlanner, featureExpenses, viewOrdering];

@override
Map<String, dynamic> toJson() {
Expand All @@ -23,21 +40,27 @@ class ServerSettings extends Model {
if (featureExpenses != null) {
data['expenses_feature'] = featureExpenses;
}
if (viewOrdering != null) {
data['view_ordering'] = viewOrdering!.map((e) => e.toString()).toList();
}

return data;
}

ServerSettings copyWith({
bool? featurePlanner,
bool? featureExpenses,
List<ViewsEnum>? viewOrdering,
}) =>
ServerSettings(
featurePlanner: featurePlanner ?? this.featurePlanner,
featureExpenses: featureExpenses ?? this.featureExpenses,
viewOrdering: viewOrdering ?? this.viewOrdering,
);

ServerSettings copyFrom(ServerSettings serverSettings) => ServerSettings(
featurePlanner: serverSettings.featurePlanner ?? featurePlanner,
featureExpenses: serverSettings.featureExpenses ?? featureExpenses,
viewOrdering: serverSettings.viewOrdering ?? viewOrdering,
);
}
52 changes: 35 additions & 17 deletions lib/pages/home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:kitchenowl/cubits/planner_cubit.dart';
import 'package:kitchenowl/cubits/recipe_list_cubit.dart';
import 'package:kitchenowl/cubits/settings_cubit.dart';
import 'package:kitchenowl/cubits/shoppinglist_cubit.dart';
import 'package:kitchenowl/enums/views_enum.dart';
import 'package:kitchenowl/pages/home_page/_export.dart';
import 'package:responsive_builder/responsive_builder.dart';

Expand All @@ -18,14 +19,15 @@ class HomePage extends StatefulWidget {
}

class _HomePageState extends State<HomePage> {
static const int _bottomAppBarSize = 5;

final ShoppinglistCubit shoppingListCubit = ShoppinglistCubit();
final RecipeListCubit recipeListCubit = RecipeListCubit();
final PlannerCubit plannerCubit = PlannerCubit();
final ExpenseListCubit expenseCubit = ExpenseListCubit();

late List<HomePageItem> pages;
int _selectedIndex = 0;
int offset = 0;

@override
void initState() {
Expand Down Expand Up @@ -73,16 +75,40 @@ class _HomePageState extends State<HomePage> {
final _offset =
(state.serverSettings.featurePlanner ?? false ? 0 : 1) +
(state.serverSettings.featureExpenses ?? false ? 0 : 1);
_selectedIndex = (_selectedIndex *
(pages.length - _offset) /
(pages.length - offset))
.clamp(0, pages.length - _offset)
.round();

offset = _offset;
_selectedIndex =
_selectedIndex.clamp(0, pages.length - 1 - _offset);
},
builder: (context, state) {
final _pages = pages.where((e) => e.isActive(context)).toList();
List<HomePageItem> _pages =
(state.serverSettings.viewOrdering ?? ViewsEnum.values)
.map<HomePageItem?>((e) {
final i = pages.indexWhere(
(page) => page.type() == e,
);

return i >= 0 ? pages[i] : null;
})
.where((e) => e != null && e.isActive(context))
.cast<HomePageItem>()
.toList();

final bool useBottomNavigationBar = getValueForScreenType<bool>(
context: context,
mobile: true,
tablet: false,
desktop: false,
);

if (useBottomNavigationBar && _bottomAppBarSize < _pages.length) {
_selectedIndex = _selectedIndex.clamp(0, _bottomAppBarSize - 1);
_pages.insert(
_bottomAppBarSize - 1,
OverflowPage(
pages: _pages.sublist(_bottomAppBarSize - 1, _pages.length),
),
);
_pages = _pages.sublist(0, _bottomAppBarSize);
}

Widget body = PageTransitionSwitcher(
transitionBuilder: (
Expand All @@ -106,13 +132,6 @@ class _HomePageState extends State<HomePage> {
),
);

final bool useBottomNavigationBar = getValueForScreenType<bool>(
context: context,
mobile: true,
tablet: false,
desktop: false,
);

if (!useBottomNavigationBar) {
final bool extendedRail = getValueForScreenType<bool>(
context: context,
Expand Down Expand Up @@ -151,7 +170,6 @@ class _HomePageState extends State<HomePage> {
labelBehavior:
NavigationDestinationLabelBehavior.onlyShowSelected,
destinations: _pages
.where((e) => e.isActive(context))
.map((e) => NavigationDestination(
icon: Icon(e.icon(context)),
label: e.label(context),
Expand Down
1 change: 1 addition & 0 deletions lib/pages/home_page/_export.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export 'shoppinglist.dart';
export 'planner.dart';
export 'expense_list.dart';
export 'home_page_item.dart';
export 'overflow.dart';
6 changes: 2 additions & 4 deletions lib/pages/home_page/expense_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:kitchenowl/cubits/expense_list_cubit.dart';
import 'package:kitchenowl/cubits/settings_cubit.dart';
import 'package:kitchenowl/enums/expenselist_sorting.dart';
import 'package:kitchenowl/enums/update_enum.dart';
import 'package:kitchenowl/enums/views_enum.dart';
import 'package:kitchenowl/kitchenowl.dart';
import 'package:kitchenowl/models/user.dart';
import 'package:kitchenowl/pages/expense_add_update_page.dart';
Expand All @@ -26,10 +27,7 @@ class ExpenseListPage extends StatefulWidget with HomePageItem {
_ExpensePageState createState() => _ExpensePageState();

@override
IconData icon(BuildContext context) => Icons.account_balance_rounded;

@override
String label(BuildContext context) => AppLocalizations.of(context)!.balances;
ViewsEnum type() => ViewsEnum.balances;

@override
void onSelected(BuildContext context, bool alreadySelected) {
Expand Down
6 changes: 4 additions & 2 deletions lib/pages/home_page/home_page_item.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import 'package:flutter/material.dart';
import 'package:kitchenowl/enums/views_enum.dart';

mixin HomePageItem on Widget {
// ignore: no-empty-block
void onSelected(BuildContext context, bool alreadySelected) {}
IconData icon(BuildContext context);
String label(BuildContext context);
ViewsEnum type();
String label(BuildContext context) => type().toLocalizedString(context);
IconData icon(BuildContext context) => type().toIcon();
Widget? floatingActionButton(BuildContext context) => null;
bool isActive(BuildContext context) => true;
}
Loading