diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9bea433 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +.DS_Store diff --git a/assignment/README.md b/assignment/README.md new file mode 100644 index 0000000..fb497a5 --- /dev/null +++ b/assignment/README.md @@ -0,0 +1,68 @@ +# 🚀 Overview + +This Flutter app is designed for the Enterprise Software Development (ESD) workshop to introduce the basics of Flutter and Dart programming. The app, titled "My Favorite Things", allows users to create a list of their favorite items, view details, and add new items. It demonstrates fundamental Flutter concepts like navigation, state management, and UI design. + +# 🛠️ Task +For the interactive part of the workshop you can clone this repo on [Flutlab.io](https://flutlab.io) and try to understand or even extend the functionalities. This is your playground to be creative. +The app is designed for ease of understanding and feature richness, rather than strict adherence to best practices. + +## 💡 Ideas +Feel free to explore any additions or modifications you desire. If you're looking for inspiration, here are some suggestions: +- **Shopping List Transformation:** 🛒 Convert the app into a shopping list. Implement features like item deletion, marking items as purchased, and sorting by priority or categories. +- **Personal Diary:** 📔 Turn it into a personal diary or journal app. Introduce features to add entries with dates, edit entries, and perhaps include mood icons or weather. +- **Task Manager:** 📋 Morph it into a task management app. Add functionalities for setting deadlines, reminders, categorizing tasks, and marking them as complete. +- **Photo Album:** 📸 Convert the app into a photo album. Allow users to add photos, captions, and organize them into albums or by dates. +- **Recipe Book:** 🍳 Transform it into a recipe book where users can add, view, and categorize recipes. Include features for ingredients, cooking steps, and preparation time. + +# 📖 Explanation + +## 🌈 Functionalities +- **List Display:** 📝 The home screen shows a list of favorite items. +- **Navigation:** 🚦 Users can navigate between the home screen, details screen, and add item screen. +- **Add New Item:** ➕ Users can add new items to the list which will be displayed on the home screen. +- **View Details:** 🔍 Users can view more details about an item by tapping on it. + +## 📁 File Structure +The project is organized into several directories for clarity and maintainability: + +
+lib/ 
+├── main.dart                           # The entry point of the application.
+├── models/
+│   └── favorite_item.dart              # Defines the FavoriteItem class.
+├── screens/
+│   ├── home_screen.dart                # Home page, displays the list of favorite items.
+│   ├── details_screen.dart             # Page showing detailed info about a selected item.
+│   └── add_item_screen.dart            # Page for adding a new favorite item.
+└── widgets/
+    └── favorite_item_card.dart         # Reusable widget for displaying an item in the list.
+
+ + +## 🔧 Code Structure +
+├── main.dart
+│   ▶️ Sets up the MaterialApp
+│   ▶️ Defines routes
+│   ▶️ Applies the app theme
+│
+├── models/
+│   └── favorite_item.dart
+│       ▶️ Contains the model for favorite items
+│
+├── screens/
+│   ├── home_screen.dart
+│   │   ▶️ Displays a list of items using ListView.builder
+│   │   ▶️ Implements navigation to the details and add item screens
+│   │
+│   ├── details_screen.dart
+│   │   ▶️ Shows details of a selected item, passed through navigation
+│   │
+│   └── add_item_screen.dart
+│       ▶️ Contains a form to add new items and passes them back to the home screen
+│
+└── widgets/
+    └── favorite_item_card.dart
+        ▶️ Custom widget to display each item in a card format
+
+ diff --git a/assignment/app/analysis_options.yaml b/assignment/app/analysis_options.yaml new file mode 100644 index 0000000..a7acf24 --- /dev/null +++ b/assignment/app/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/assignment/app/lib/main.dart b/assignment/app/lib/main.dart new file mode 100644 index 0000000..9ba283a --- /dev/null +++ b/assignment/app/lib/main.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'screens/home_screen.dart'; +import 'screens/details_screen.dart'; +import 'screens/add_item_screen.dart'; +import 'models/favorite_item.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'My Favorite Things', + theme: ThemeData(primarySwatch: Colors.deepPurple), + home: const HomeScreen(), + // Static routes + routes: { + HomeScreen.routeName: (context) => const HomeScreen(), + AddItemScreen.routeName: (context) => const AddItemScreen(), + }, + // Routes with args + onGenerateRoute: (settings) { + if (settings.name == DetailsScreen.routeName) { + final args = settings.arguments as FavoriteItem; + return MaterialPageRoute( + builder: (context) { + return DetailsScreen(item: args); + }, + ); + } + assert(false, 'Need to implement ${settings.name}'); + return null; + }, + ); + } +} diff --git a/assignment/app/lib/models/favorite_item.dart b/assignment/app/lib/models/favorite_item.dart new file mode 100644 index 0000000..7c2c78e --- /dev/null +++ b/assignment/app/lib/models/favorite_item.dart @@ -0,0 +1,6 @@ +class FavoriteItem { + String title; + String description; + + FavoriteItem({required this.title, this.description = ''}); +} diff --git a/assignment/app/lib/screens/add_item_screen.dart b/assignment/app/lib/screens/add_item_screen.dart new file mode 100644 index 0000000..62e236c --- /dev/null +++ b/assignment/app/lib/screens/add_item_screen.dart @@ -0,0 +1,75 @@ +import 'package:app/models/favorite_item.dart'; +import 'package:flutter/material.dart'; + +class AddItemScreen extends StatefulWidget { + static const routeName = "/addItemScreen"; + + const AddItemScreen({Key? key}) : super(key: key); + + @override + State createState() => _AddItemScreenState(); +} + +class _AddItemScreenState extends State { + final _formKey = GlobalKey(); + final _titleController = TextEditingController(); + final _descriptionController = TextEditingController(); + + void _saveItem() { + if (_formKey.currentState!.validate()) { + final newItem = FavoriteItem( + title: _titleController.text, + description: _descriptionController.text, + ); + + // Pop the screen and pass back the new item + Navigator.of(context).pop(newItem); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Add a New Favorite Thing'), + ), + body: Form( + key: _formKey, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextFormField( + controller: _titleController, + decoration: const InputDecoration(labelText: 'Title'), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a title'; + } + return null; + }, + ), + TextFormField( + controller: _descriptionController, + decoration: const InputDecoration(labelText: 'Description'), + ), + ElevatedButton( + onPressed: _saveItem, + child: const Text('Save'), + ), + ], + ), + ), + ), + ); + } + + @override + void dispose() { + // Clean up the controller when the widget is disposed. + _titleController.dispose(); + _descriptionController.dispose(); + super.dispose(); + } +} diff --git a/assignment/app/lib/screens/details_screen.dart b/assignment/app/lib/screens/details_screen.dart new file mode 100644 index 0000000..3e3ddfc --- /dev/null +++ b/assignment/app/lib/screens/details_screen.dart @@ -0,0 +1,22 @@ +import 'package:app/models/favorite_item.dart'; +import 'package:flutter/material.dart'; + +class DetailsScreen extends StatelessWidget { + static const routeName = "/detailsScreen"; + final FavoriteItem item; + + const DetailsScreen({Key? key, required this.item}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(item.title), + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Text(item.description), + ), + ); + } +} diff --git a/assignment/app/lib/screens/home_screen.dart b/assignment/app/lib/screens/home_screen.dart new file mode 100644 index 0000000..298f7e9 --- /dev/null +++ b/assignment/app/lib/screens/home_screen.dart @@ -0,0 +1,48 @@ +import 'package:app/models/favorite_item.dart'; +import 'package:app/screens/add_item_screen.dart'; +import 'package:app/widgets/favorite_item_card.dart'; +import 'package:flutter/material.dart'; + +class HomeScreen extends StatefulWidget { + static const routeName = "/homeScreen"; + + const HomeScreen({Key? key}) : super(key: key); + + @override + State createState() => _HomeScreenState(); +} + +class _HomeScreenState extends State { + final List items = [ + FavoriteItem(title: 'Pizza', description: 'Cheesy and delicious'), + ]; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('My Favorite Things'), + ), + body: ListView( + children: [...items.map((e) => FavoriteItemCard(item: e))], + ), + floatingActionButton: FloatingActionButton( + onPressed: () async { + // Navigate to AddItemScreen and wait for the result + final result = await Navigator.push( + context, + MaterialPageRoute(builder: (context) => const AddItemScreen()), + ); + + // Check if the result is a FavoriteItem and add it to the list + if (result is FavoriteItem) { + setState(() { + items.add(result); + }); + } + }, + child: const Icon(Icons.add), + ), + ); + } +} diff --git a/assignment/app/lib/widgets/favorite_item_card.dart b/assignment/app/lib/widgets/favorite_item_card.dart new file mode 100644 index 0000000..8e53502 --- /dev/null +++ b/assignment/app/lib/widgets/favorite_item_card.dart @@ -0,0 +1,38 @@ +import 'package:app/models/favorite_item.dart'; +import 'package:app/screens/details_screen.dart'; +import 'package:flutter/material.dart'; + +class FavoriteItemCard extends StatelessWidget { + final FavoriteItem item; + + const FavoriteItemCard({Key? key, required this.item}) : super(key: key); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + Navigator.pushNamed( + context, + DetailsScreen.routeName, + arguments: item, + ); + }, + child: Container( + height: 50, + decoration: BoxDecoration( + color: Theme.of(context).cardColor, + border: Border.all(color: Colors.black12)), + child: Row( + children: [ + Expanded( + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 10), + child: Text(item.title, + style: const TextStyle(fontSize: 25)))), + const Icon(Icons.arrow_right) + ], + ), + ), + ); + } +} diff --git a/assignment/app/pubspec.yaml b/assignment/app/pubspec.yaml new file mode 100644 index 0000000..e70b65a --- /dev/null +++ b/assignment/app/pubspec.yaml @@ -0,0 +1,33 @@ +name: app +description: Simple example app for the ESD course. + +version: 1.0.0+1 + +environment: + sdk: ">=2.19.0 <4.0.0" + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.2 + +flutter: + uses-material-design: true + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 diff --git a/assignment/app/web/favicon.png b/assignment/app/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/assignment/app/web/favicon.png differ diff --git a/assignment/app/web/icons/Icon-192.png b/assignment/app/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/assignment/app/web/icons/Icon-192.png differ diff --git a/assignment/app/web/icons/Icon-512.png b/assignment/app/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/assignment/app/web/icons/Icon-512.png differ diff --git a/assignment/app/web/icons/Icon-maskable-192.png b/assignment/app/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/assignment/app/web/icons/Icon-maskable-192.png differ diff --git a/assignment/app/web/icons/Icon-maskable-512.png b/assignment/app/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/assignment/app/web/icons/Icon-maskable-512.png differ diff --git a/assignment/app/web/index.html b/assignment/app/web/index.html new file mode 100644 index 0000000..ddea15c --- /dev/null +++ b/assignment/app/web/index.html @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + hello_world + + + + + + + diff --git a/assignment/app/web/manifest.json b/assignment/app/web/manifest.json new file mode 100644 index 0000000..5c1f368 --- /dev/null +++ b/assignment/app/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "hello_world", + "short_name": "hello_world", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/assignment/docs/app_preview_mockup.png b/assignment/docs/app_preview_mockup.png new file mode 100644 index 0000000..b8efdfc Binary files /dev/null and b/assignment/docs/app_preview_mockup.png differ