Skip to content
Draft
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
65 changes: 61 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,38 @@ CoffeeShop app preview:

![Four screenshots for different screens in the app: product catalog, cart, user profile and order history](./imgs/workshop_preview.png)

## Requirements:

### Required:
## What you'll learn

By completing this workshop you will be able to:

- Create a multi-screen Flutter app using `BottomNavigationBar`
- Build common UI layouts with widgets such as `Column`, `ListView`, `ListTile`, `Card`, and `CircleAvatar`
- Navigate between pages (both tab-based and stack-based with `Navigator.push`)
- Define simple Dart data models (classes with named constructors)
- Manage shared app state without an external library
- Use hot-reload to see live changes during development

## Project structure

```
app/
└── lib/
├── main.dart # App entry point and bottom-navigation host
├── coffee_manager.dart # Lightweight in-memory state container
├── models/
│ ├── product.dart # Product data model
│ ├── user.dart # User data model
│ └── order.dart # Order data model
└── pages/
├── catalog.dart # Product listing screen
├── cart.dart # Shopping cart screen
├── profile.dart # User profile screen
└── orders.dart # Order history screen
```

## Requirements

### Required

- Bring Your Own Device (BYOD)
- Basic/medium programming knowledge
Expand All @@ -30,7 +59,30 @@ To choose:
- Android emulator device
- Physical device (developer mode and USB debugging enabled)

## About code:
## Running the app

```bash
# Navigate to the app directory
cd app

# Fetch dependencies
flutter pub get

# Run on a connected device or emulator
flutter run
```

To add support for additional platforms:

```bash
# Web
flutter create --platforms=web .

# Linux
flutter create --platforms=linux .
```

## About the code

No state management libraries/tools/engines are used.

Expand All @@ -46,6 +98,11 @@ This workshop focuses on the development of UI, to learn the basics of Flutter,

Clone this repo and checkout to [`workshop`](https://github.com/Beelzenef/workshop_flutter/tree/workshop) branch.

> 💡 Don't want to use Git? Download the starter project directly from
> [download-directory.github.io](https://download-directory.github.io/?url=https%3A%2F%2Fgithub.com%2Fzelonware%2Fworkshop_flutter%2Ftree%2Fworkshop%2Fapp).

For a detailed step-by-step guide see [`docs/guide.md`](./docs/guide.md).

## More info

General:
Expand Down
13 changes: 12 additions & 1 deletion app/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
# coffee_shop

A new Flutter app!
# CoffeeShop – Flutter Workshop App

A beginner-friendly Flutter application built during a hands-on workshop. It demonstrates core Flutter concepts including multi-screen navigation, stateful widgets, and simple in-memory state management.

## Getting started

```bash
flutter pub get
flutter run
```

See the [root README](../README.md) and [workshop guide](../docs/guide.md) for full setup instructions and the step-by-step workshop walkthrough.
22 changes: 22 additions & 0 deletions app/lib/coffee_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,39 @@ import 'package:coffee_shop/models/order.dart';
import 'package:coffee_shop/models/product.dart';
import 'package:coffee_shop/models/user.dart';

/// Lightweight in-memory state container for the CoffeeShop app.
///
/// [CoffeeManager] holds all mutable application data – the current user,
/// the shopping cart, and the order history – and exposes simple methods to
/// manipulate that data. A single instance is created in [CoffeeShopApp] and
/// shared across all pages through the static [CoffeeShopApp.manager] accessor.
///
/// > **Note:** This class does **not** use any state management library and
/// > does **not** persist data between app restarts. It is intentionally simple
/// > for workshop / learning purposes.
class CoffeeManager {
/// The currently logged-in user. Pre-populated with placeholder data.
final User user =
User(name: 'Awesome', mail: 'you@awesome.com', phone: '666-666-66');

/// Products that have been added to the cart but not yet paid for.
final List<Product> productsInCart = [];

/// Completed orders placed during the current session.
final List<Order> orders = [];

/// Running total of all items currently in [productsInCart], in whole
/// currency units. Updated automatically whenever the cart changes.
int total = 0;

/// Adds [product] to the cart and recalculates [total].
void addToCart(Product product) {
productsInCart.add(product);
_calculateTotal();
}

/// Recalculates [total] by summing the prices of all items in [productsInCart].
/// Resets [total] to 0 when the cart is empty.
void _calculateTotal() {
if (productsInCart.isNotEmpty) {
var prices = productsInCart.map((e) => e.price).toList();
Expand All @@ -25,6 +44,8 @@ class CoffeeManager {
}
}

/// Converts the current cart into a new [Order], appends it to [orders],
/// and clears the cart. Does nothing if the cart is empty.
void makeOrder() {
if (productsInCart.isNotEmpty) {
int howManyProducts = productsInCart.length;
Expand All @@ -37,6 +58,7 @@ class CoffeeManager {
}
}

/// Removes all products from the cart and resets [total] to 0.
void clearCart() {
productsInCart.clear();
_calculateTotal();
Expand Down
15 changes: 15 additions & 0 deletions app/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@ void main() {
runApp(const CoffeeShopApp());
}

/// Root widget of the CoffeeShop application.
///
/// Configures [MaterialApp] with the app theme and sets [HomePage] as the
/// initial route. A single shared [CoffeeManager] instance is stored here and
/// accessed by child widgets via `CoffeeShopApp.manager`.
class CoffeeShopApp extends StatelessWidget {
const CoffeeShopApp({super.key});

/// Shared state manager accessible from any widget in the tree.
static final manager = CoffeeManager();

@override
Expand All @@ -28,18 +34,27 @@ class CoffeeShopApp extends StatelessWidget {
}
}

/// The main scaffold of the app, hosting the [BottomNavigationBar] and
/// switching between the three top-level pages.
class HomePage extends StatefulWidget {
const HomePage({super.key, required this.title});

/// Title displayed in the [AppBar].
final String title;

@override
State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
/// Index of the currently selected bottom-navigation tab.
int index = 0;

/// Returns the page widget that corresponds to the selected [index].
///
/// - 0 → [CatalogPage]
/// - 1 → [CartPage]
/// - 2 → [ProfilePage]
Widget navigateToPage() {
switch (index) {
case 1:
Expand Down
8 changes: 8 additions & 0 deletions app/lib/models/order.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
/// Represents a completed purchase order.
class Order {
/// Creates an [Order] with the number of [products] purchased,
/// the [total] price paid, and the [orderedAt] timestamp.
Order({required this.products, required this.total, required this.orderedAt});

/// Number of products included in this order.
final int products;

/// Total cost of the order in whole currency units.
final int total;

/// Date and time at which the order was placed.
final DateTime orderedAt;
}
8 changes: 8 additions & 0 deletions app/lib/models/product.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
/// Represents a coffee product available in the catalog.
class Product {
/// Creates a [Product] with a [name], [price] in whole currency units,
/// and a [pic] URL (may be empty in the workshop starter).
Product({required this.name, required this.price, required this.pic});

/// Display name of the product (e.g. "Espresso").
final String name;

/// Price of the product in whole currency units (e.g. 2 for €2).
final int price;

/// URL or asset path for the product image. May be an empty string.
final String pic;
}
7 changes: 7 additions & 0 deletions app/lib/models/user.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
/// Represents the logged-in user of the coffee shop app.
class User {
/// Creates a [User] with a display [name], [mail] address and [phone] number.
User({required this.name, required this.mail, required this.phone});

/// Display name shown in the profile and catalog greeting.
final String name;

/// Email address of the user.
final String mail;

/// Phone number of the user.
final String phone;
}
7 changes: 7 additions & 0 deletions app/lib/pages/cart.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ import 'package:coffee_shop/main.dart';

import 'package:flutter/material.dart';

/// Displays the products currently in the shopping cart together with the
/// running total. Provides **PAY** and **REMOVE ORDERS** actions.
///
/// - **PAY** calls [CoffeeManager.makeOrder], which converts the cart into a
/// completed [Order] and clears it.
/// - **REMOVE ORDERS** calls [CoffeeManager.clearCart], discarding all items
/// without creating an order.
class CartPage extends StatefulWidget {
const CartPage({super.key});

Expand Down
7 changes: 7 additions & 0 deletions app/lib/pages/catalog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import 'package:coffee_shop/models/product.dart';

import 'package:flutter/material.dart';

/// Displays the list of available coffee products and allows the user to add
/// items to the cart by tapping them.
class CatalogPage extends StatefulWidget {
const CatalogPage({super.key});

Expand Down Expand Up @@ -43,6 +45,10 @@ class _CatalogPageState extends State<CatalogPage> {
);
}

/// Builds a single [ListTile] for [p].
///
/// Tapping the tile calls [CoffeeManager.addToCart] and triggers a
/// `setState` so that any UI depending on the cart is rebuilt.
ListTile buildCatalogItem(Product p) {
String price = p.price.toString();

Expand All @@ -56,6 +62,7 @@ class _CatalogPageState extends State<CatalogPage> {
);
}

/// Shows a [SnackBar] confirming that a product was added to the cart.
void showToast(BuildContext context) {
final scaffold = ScaffoldMessenger.of(context);
scaffold.showSnackBar(
Expand Down
8 changes: 8 additions & 0 deletions app/lib/pages/orders.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,16 @@ import 'package:coffee_shop/models/order.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

/// Shows the full list of completed orders for the current session.
///
/// Receives the [orders] list from [ProfilePage] and renders each entry as a
/// coloured card showing the number of products, total cost, and the formatted
/// date/time of the purchase.
class OrdersPage extends StatelessWidget {
/// The list of orders to display.
final List<Order> orders;

/// Formatter used to display [Order.orderedAt] as `dd-MM-yyyy (HH:mm)`.
final DateFormat formatter = DateFormat('dd-MM-yyyy (HH:mm)');

OrdersPage({super.key, required this.orders});
Expand Down
5 changes: 5 additions & 0 deletions app/lib/pages/profile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import 'package:coffee_shop/main.dart';

import 'package:flutter/material.dart';

/// Displays the current user's profile information.
///
/// Shows a placeholder avatar, the user's name, email address, and phone
/// number read from [CoffeeManager.user]. When at least one order has been
/// placed, an **All orders** button appears that navigates to [OrdersPage].
class ProfilePage extends StatefulWidget {
const ProfilePage({super.key});

Expand Down
Loading