Skip to content

Commit

Permalink
feat(cookbook_app): add home view
Browse files Browse the repository at this point in the history
Signed-off-by: Nikolas Rimikis <[email protected]>
  • Loading branch information
Leptopoda committed Sep 29, 2024
1 parent 9de4340 commit 78a715e
Show file tree
Hide file tree
Showing 42 changed files with 1,240 additions and 19 deletions.
3 changes: 2 additions & 1 deletion .cspell/dart_flutter.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
arrowshape
autofocus
checkmark
cupertino
Expand All @@ -10,6 +11,7 @@ goldens
lerp
pubspec
qrcode
Relayout
steb
sublist
todos
Expand All @@ -18,4 +20,3 @@ unawaited
unfocus
writeln
xmark
arrowshape
7 changes: 7 additions & 0 deletions packages/neon_framework/example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,13 @@ packages:
relative: true
source: path
version: "1.0.0"
cookbook_recipe_repository:
dependency: "direct overridden"
description:
path: "../packages/cookbook_recipe_repository"
relative: true
source: path
version: "1.0.0"
cookie_store:
dependency: "direct overridden"
description:
Expand Down
4 changes: 3 additions & 1 deletion packages/neon_framework/example/pubspec_overrides.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# melos_managed_dependency_overrides: account_repository,cookbook_app,cookie_store,dashboard_app,dynamite_runtime,files_app,interceptor_http_client,neon_framework,neon_http_client,neon_lints,news_app,nextcloud,notes_app,notifications_app,sort_box,talk_app
# melos_managed_dependency_overrides: account_repository,cookbook_app,cookbook_recipe_repository,cookie_store,dashboard_app,dynamite_runtime,files_app,interceptor_http_client,neon_framework,neon_http_client,neon_lints,news_app,nextcloud,notes_app,notifications_app,sort_box,talk_app
dependency_overrides:
account_repository:
path: ../packages/account_repository
cookbook_app:
path: ../packages/cookbook_app
cookbook_recipe_repository:
path: ../packages/cookbook_recipe_repository
cookie_store:
path: ../../cookie_store
dashboard_app:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,40 @@
{
"@@locale": "en"
"@@locale": "en",
"recipeCreateButton": "Create Recipe",
"@recipeCreateButton": {
"type": "text",
"description": "Button to open the create recipe screen"
},
"recipeListTitle": "Category: {name}",
"@recipeListTitle": {
"type": "text",
"description": "Title of the category view.",
"placeholders": {
"name": {
"description": "The name of the category.",
"type": "String",
"example": "Vegan"
}
}
},
"noRecipes": "No recipes available.",
"errorLoadFailed": "Failed to load Recipe!",
"@errorLoadFailed": {
"type": "text",
"description": "Error message when fetching the recipes failed."
},
"categoryAll": "All Recipes",
"categoryUncategorized": "Uncategorized",
"categoryItems": "{count, plural, =0{no items} =1 {1 item} other {{count} items}}",
"@categoryItems": {
"type": "text",
"description": "Number of recipes in a category.",
"placeholders": {
"count": {
"description": "The number of recipes.",
"type": "int",
"example": "4"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,49 @@ abstract class CookbookLocalizations {
];

/// A list of this localizations delegate's supported locales.
static const List<Locale> supportedLocales = <Locale>[
Locale('en')
];
static const List<Locale> supportedLocales = <Locale>[Locale('en')];

/// Button to open the create recipe screen
///
/// In en, this message translates to:
/// **'Create Recipe'**
String get recipeCreateButton;

/// Title of the category view.
///
/// In en, this message translates to:
/// **'Category: {name}'**
String recipeListTitle(String name);

/// No description provided for @noRecipes.
///
/// In en, this message translates to:
/// **'No recipes available.'**
String get noRecipes;

/// Error message when fetching the recipes failed.
///
/// In en, this message translates to:
/// **'Failed to load Recipe!'**
String get errorLoadFailed;

/// No description provided for @categoryAll.
///
/// In en, this message translates to:
/// **'All Recipes'**
String get categoryAll;

/// No description provided for @categoryUncategorized.
///
/// In en, this message translates to:
/// **'Uncategorized'**
String get categoryUncategorized;

/// Number of recipes in a category.
///
/// In en, this message translates to:
/// **'{count, plural, =0{no items} =1 {1 item} other {{count} items}}'**
String categoryItems(int count);
}

class _CookbookLocalizationsDelegate extends LocalizationsDelegate<CookbookLocalizations> {
Expand All @@ -111,17 +150,14 @@ class _CookbookLocalizationsDelegate extends LocalizationsDelegate<CookbookLocal
}

CookbookLocalizations lookupCookbookLocalizations(Locale locale) {


// Lookup logic when only language code is specified.
switch (locale.languageCode) {
case 'en': return CookbookLocalizationsEn();
case 'en':
return CookbookLocalizationsEn();
}

throw FlutterError(
'CookbookLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
'an issue with the localizations generation tool. Please file an issue '
'on GitHub with a reproducible sample app and the gen-l10n configuration '
'that was used.'
);
throw FlutterError('CookbookLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
'an issue with the localizations generation tool. Please file an issue '
'on GitHub with a reproducible sample app and the gen-l10n configuration '
'that was used.');
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'package:intl/intl.dart' as intl;

import 'cookbook_localizations.dart';

// ignore_for_file: type=lint
Expand All @@ -6,5 +8,35 @@ import 'cookbook_localizations.dart';
class CookbookLocalizationsEn extends CookbookLocalizations {
CookbookLocalizationsEn([String locale = 'en']) : super(locale);

@override
String get recipeCreateButton => 'Create Recipe';

@override
String recipeListTitle(String name) {
return 'Category: $name';
}

@override
String get noRecipes => 'No recipes available.';

@override
String get errorLoadFailed => 'Failed to load Recipe!';

@override
String get categoryAll => 'All Recipes';

@override
String get categoryUncategorized => 'Uncategorized';

@override
String categoryItems(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count items',
one: '1 item',
zero: 'no items',
);
return '$_temp0';
}
}
15 changes: 15 additions & 0 deletions packages/neon_framework/packages/cookbook_app/lib/l10n/l10n.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,18 @@ extension AppLocalizationsX on BuildContext {
/// `CookbookLocalizations.of(this)`.
CookbookLocalizations get l10n => CookbookLocalizations.of(this);
}

/// Extension for custom localizations constructed from other ones.
extension CookbookLocalizationsX on CookbookLocalizations {
/// Translates the special categories '_' (all recipes) and '*' (uncategorized).
///
/// In en, this message translates to:
/// **'{name, select, _{[categoryAll]} *{[categoryUncategorized]} other{{name}}}'**
String categoryName(String name) {
return switch (name) {
'_' => categoryAll,
'*' => categoryUncategorized,
_ => name,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ final class CookbookApp extends AppImplementation<CookbookBloc, CookbookOptions>
CookbookBloc buildBloc(Account account) => CookbookBloc();

@override
final Widget page = const Placeholder();
final Widget page = const HomePage();

@override
final RouteBase route = $cookbookAppRoute;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import 'package:bloc/bloc.dart';
import 'package:built_collection/built_collection.dart';
import 'package:cookbook_recipe_repository/recipe_repository.dart';
import 'package:equatable/equatable.dart';

part 'categories_event.dart';
part 'categories_state.dart';

/// The bloc controlling the categories overview.
final class CategoriesBloc extends Bloc<_CategoriesEvent, CategoriesState> {
/// Creates a new categories bloc.
CategoriesBloc({
required RecipeRepository recipeRepository,
}) : _recipeRepository = recipeRepository,
super(CategoriesState()) {
on<RefreshCategories>(_onRefreshCategories);

add(const RefreshCategories());
}

final RecipeRepository _recipeRepository;

Future<void> _onRefreshCategories(
RefreshCategories event,
Emitter<CategoriesState> emit,
) async {
try {
emit(state.copyWith(status: CategoriesStatus.loading));

final categories = await _recipeRepository.readCategories();

emit(
state.copyWith(
categories: categories,
status: CategoriesStatus.success,
),
);
} on ReadCategoriesFailure {
emit(state.copyWith(status: CategoriesStatus.failure));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
part of 'categories_bloc.dart';

/// Events for the [CategoriesBloc].
sealed class _CategoriesEvent extends Equatable {
const _CategoriesEvent();

@override
List<Object> get props => [];
}

/// {@template RefreshCategories}
/// Event that triggers a reload of the categories.
/// {@endtemplate}
final class RefreshCategories extends _CategoriesEvent {
/// {@macro RefreshCategories}
const RefreshCategories();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
part of 'categories_bloc.dart';

/// The status of the [CategoriesState].
enum CategoriesStatus {
/// When no event has been handled.
initial,

/// When the categories are loading.
loading,

/// When the categories have been fetched successfully.
success,

/// When a failure occurred while loading the categories.
failure,
}

/// State of the [CategoriesBloc].
final class CategoriesState extends Equatable {
/// Creates a new state for managing the categories.
CategoriesState({
BuiltList<Category>? categories,
this.status = CategoriesStatus.initial,
}) : categories = categories ?? BuiltList();

/// The list of categories.
///
/// Defaults to an empty list.
final BuiltList<Category> categories;

/// The status of the state.
final CategoriesStatus status;

/// Creates a copies with mutated fields.
CategoriesState copyWith({
BuiltList<Category>? categories,
String? error,
CategoriesStatus? status,
}) {
return CategoriesState(
categories: categories ?? this.categories,
status: status ?? this.status,
);
}

@override
List<Object> get props => [
categories,
status,
];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export 'bloc/categories_bloc.dart';
export 'utils/utils.dart';
export 'view/view.dart';
export 'widgets/widgets.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import 'dart:math' as math;

import 'package:flutter/rendering.dart';

/// Controls the layout of the category cards in a grid.
class CategoryGridDelegate extends SliverGridDelegate {
/// Creates a delegate for the category card layout.
const CategoryGridDelegate({
this.extent = 0.0,
});

/// The height extend the card will take.
final double extent;

static const double _maxCrossAxisExtent = 250;
static const double _mainAxisSpacing = 8;
static const double _crossAxisSpacing = 8;

@override
SliverGridLayout getLayout(SliverConstraints constraints) {
var crossAxisCount = (constraints.crossAxisExtent / (_maxCrossAxisExtent + _crossAxisSpacing)).ceil();
// Ensure a minimum count of 1, can be zero and result in an infinite extent
// below when the window size is 0.
crossAxisCount = math.max(1, crossAxisCount);
final double usableCrossAxisExtent = math.max(
0,
constraints.crossAxisExtent - _crossAxisSpacing * (crossAxisCount - 1),
);
final childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount;
final childMainAxisExtent = childCrossAxisExtent + extent;

return SliverGridRegularTileLayout(
crossAxisCount: crossAxisCount,
mainAxisStride: childMainAxisExtent + _mainAxisSpacing,
crossAxisStride: childCrossAxisExtent + _crossAxisSpacing,
childMainAxisExtent: childMainAxisExtent,
childCrossAxisExtent: childCrossAxisExtent,
reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection),
);
}

@override
bool shouldRelayout(CategoryGridDelegate oldDelegate) => oldDelegate.extent != extent;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export 'category_grid_delegate.dart';
Loading

0 comments on commit 78a715e

Please sign in to comment.