forked from mgl_crew/Mitgliederladen
Compare commits
26 commits
feature/mo
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
fdabfcf741 | ||
|
b3274f1a2a | ||
|
8f6c4ac81a | ||
|
70818931aa | ||
|
3bfab0bcae | ||
|
f3aac41876 | ||
|
220025e109 | ||
7388527c43 | |||
f0415a22da | |||
496a1f2857 | |||
e0b571a808 | |||
e0aa77fb96 | |||
2f84e61ba1 | |||
df255d71b8 | |||
000f141b5a | |||
4da346f6d7 | |||
0ee26fedb0 | |||
7c9d449c63 | |||
4bbd70464f | |||
26d0735bcf | |||
ee0c6190a5 | |||
f0bb37c38c | |||
3bf0ec4ff8 | |||
9f9bf7205b | |||
d711eef20d | |||
793d3e02e1 |
15 changed files with 486 additions and 165 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,2 +1,4 @@
|
|||
.idea
|
||||
db/*
|
||||
backend/target/*
|
||||
|
||||
|
|
|
@ -14,3 +14,21 @@ A few resources to get you started if this is your first Flutter project:
|
|||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
|
||||
## toDo
|
||||
|
||||
- Einstellungen
|
||||
- darkmode
|
||||
- Secure Storage: https://pub.dev/packages/flutter_secure_storage
|
||||
- Einkauf
|
||||
- Finance
|
||||
- Einkaufskorb editieren
|
||||
- nur 1 Monat lang möglich
|
||||
- Speichern des veränderten Warenkorbs
|
||||
- redesign (statefull)
|
||||
- farbliche Hervorhebungen
|
||||
- ausstehende Beträge für monatliche Aufladungen
|
||||
- eventuell muss sample data angepasst werden
|
||||
- pre-Release:
|
||||
- semanticLabels, Screenreader-Support
|
||||
- Error-Management (throws)
|
|
@ -1,32 +1,75 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'sample_data.dart';
|
||||
|
||||
/* todo:
|
||||
- Flag für Ansicht/Bearbeitung
|
||||
- individuelle Icons je nach Kategorie
|
||||
- Pfand
|
||||
- Gesamtpreis
|
||||
*/
|
||||
|
||||
class ShowBasket extends StatelessWidget {
|
||||
ShowBasket({super.key});
|
||||
final Basket basket;
|
||||
final bool editable;
|
||||
const ShowBasket(this.basket, {this.editable = false, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView.builder(
|
||||
itemCount: null,
|
||||
itemBuilder: (context, index) {
|
||||
if (index < SampleData().basket.purchases.length) {
|
||||
return Card(
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.abc),
|
||||
title: Text(SampleData().basket.purchases.keys.elementAt(index).name),
|
||||
//trailing: Text(),
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: getBasket(basket),
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
leading: const Icon(
|
||||
Icons.euro,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
title: const Text(
|
||||
'Kosten:',
|
||||
),
|
||||
subtitle: Text(
|
||||
'${basket.price}',
|
||||
),
|
||||
trailing: editable
|
||||
? Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FloatingActionButton(
|
||||
backgroundColor: Colors.redAccent.shade100,
|
||||
child: const Icon(Icons.remove_shopping_cart),
|
||||
onPressed: () {}),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
FloatingActionButton(
|
||||
child: const Icon(Icons.shopping_cart_checkout),
|
||||
onPressed: () {}),
|
||||
],
|
||||
)
|
||||
: null),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
ListView getBasket(Basket basket) {
|
||||
return ListView.builder(
|
||||
itemCount: basket.purchases.length,
|
||||
itemBuilder: (context, index) {
|
||||
return Card(
|
||||
child: ListTile(
|
||||
leading: Text(
|
||||
basket.purchases.keys.elementAt(index).category.icon,
|
||||
style: const TextStyle(fontSize: 24),
|
||||
),
|
||||
title: Text(basket.purchases.keys.elementAt(index).name),
|
||||
subtitle: Text(
|
||||
'${basket.purchases.values.elementAt(index)} ${basket.purchases.keys.elementAt(index).unit}'),
|
||||
trailing: editable
|
||||
? SizedBox(
|
||||
width: 100,
|
||||
child: TextField(
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
FilteringTextInputFormatter.digitsOnly
|
||||
]),
|
||||
)
|
||||
: null),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,22 +4,13 @@ import 'package:intl/date_symbol_data_local.dart';
|
|||
import 'basket.dart';
|
||||
import 'sample_data.dart';
|
||||
|
||||
/*
|
||||
todo:
|
||||
- Einkauf und Settings
|
||||
- Warenkorb
|
||||
- Aufladungen?
|
||||
- farbliche Hervorhebungen
|
||||
- semanticLabels, Screenreader
|
||||
- monatliche Aufladung: ausstehende Beträge
|
||||
*/
|
||||
|
||||
class Finance extends StatelessWidget {
|
||||
const Finance({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
initializeDateFormatting('de_DE');
|
||||
Intl.defaultLocale = 'de_DE';
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
|
@ -38,50 +29,26 @@ class Finance extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
body: ListView.builder(
|
||||
itemCount: null,
|
||||
itemCount: SampleData().transactions.length,
|
||||
itemBuilder: (context, index) {
|
||||
if (index < SampleData().transactions.length) {
|
||||
return Card(
|
||||
child: ListTile(
|
||||
leading: getIcon(SampleData().transactions[index].type),
|
||||
title: Text(
|
||||
gettitle(SampleData().transactions[index].type)),
|
||||
subtitle: getSubtitle(index),
|
||||
trailing: SampleData().transactions[index].type ==
|
||||
TransaktionArt.einkauf
|
||||
? PopupMenuButton(
|
||||
onSelected: (value) {},
|
||||
itemBuilder: (BuildContext context) =>
|
||||
<PopupMenuEntry<String>>[
|
||||
const PopupMenuItem<String>(
|
||||
value: 'Option',
|
||||
child: ListTile(
|
||||
leading: Icon(Icons.manage_history),
|
||||
title: Text('Warenkorb bearbeiten')),
|
||||
),
|
||||
const PopupMenuItem<String>(
|
||||
value: 'Option',
|
||||
child: ListTile(
|
||||
leading:
|
||||
Icon(Icons.remove_shopping_cart),
|
||||
title: Text('Einkauf stornieren')),
|
||||
),
|
||||
],
|
||||
)
|
||||
: null,
|
||||
onTap: () {
|
||||
if (SampleData().transactions[index].type ==
|
||||
TransaktionArt.einkauf) {
|
||||
showBottomSheet(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return ShowBasket();
|
||||
});
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return Card(
|
||||
child: ListTile(
|
||||
leading: getIcon(SampleData().transactions[index].type),
|
||||
title:
|
||||
Text(gettitle(SampleData().transactions[index].type)),
|
||||
subtitle: getSubtitle(SampleData().transactions[index]),
|
||||
trailing: getTrailing(context, index),
|
||||
onTap: (SampleData().transactions[index].basket != null)
|
||||
? () {
|
||||
showBottomSheet(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return ShowBasket(SampleData()
|
||||
.transactions[index]
|
||||
.basket!);
|
||||
});
|
||||
}
|
||||
: null));
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -94,17 +61,18 @@ class Finance extends StatelessWidget {
|
|||
case TransaktionArt.korrektur:
|
||||
return 'Korrektur';
|
||||
case TransaktionArt.monatlBeitrag:
|
||||
return 'monatlicher Beitrag';
|
||||
return 'Monatlicher Beitrag';
|
||||
default:
|
||||
return 'Ein Error ist aufgetreten';
|
||||
}
|
||||
}
|
||||
|
||||
Text getSubtitle(int index) {
|
||||
String text = '${SampleData().transactions[index].amount / 100}';
|
||||
Text getSubtitle(Transaction transaction) {
|
||||
String text = '${transaction.amount / 100}';
|
||||
text += '€ ';
|
||||
text += DateFormat("EEEE, dd. MMMM yyyy HH:mm", 'de_DE')
|
||||
.format(SampleData().transactions[index].date);
|
||||
if (SampleData().transactions[index].description != null) {
|
||||
(text += '\n${SampleData().transactions[index].description}');
|
||||
text += DateFormat("EEEE, dd. MMMM yyyy HH:mm").format(transaction.date);
|
||||
if (transaction.description != null) {
|
||||
(text += '\n${transaction.description}');
|
||||
}
|
||||
|
||||
return Text(text);
|
||||
|
@ -116,10 +84,28 @@ class Finance extends StatelessWidget {
|
|||
return const Icon(Icons.savings);
|
||||
case TransaktionArt.einkauf:
|
||||
return const Icon(Icons.shopping_basket);
|
||||
case TransaktionArt.korrektur:
|
||||
return const Icon(Icons.priority_high);
|
||||
case TransaktionArt.monatlBeitrag:
|
||||
return const Icon(Icons.payment);
|
||||
default:
|
||||
return const Icon(Icons.priority_high);
|
||||
}
|
||||
}
|
||||
|
||||
getTrailing(BuildContext context, int index) {
|
||||
if (SampleData().transactions[index].type == TransaktionArt.einkauf) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.manage_history),
|
||||
onPressed: () {
|
||||
showBottomSheet(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return ShowBasket(SampleData().transactions[index].basket!,
|
||||
editable: true);
|
||||
});
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:mitgliederladen/shopping.dart';
|
||||
import 'package:mitgliederladen/settings.dart';
|
||||
import 'finance.dart';
|
||||
|
||||
void main() {
|
||||
|
@ -17,17 +19,19 @@ class MyApp extends StatelessWidget {
|
|||
//darkgreen:5f7c61, mediumgreen: 66906a, lightgreen: 9cbe96, yellow: f5de64
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.light,
|
||||
textTheme: Typography.englishLike2021,
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
brightness: Brightness.light,
|
||||
seedColor: const Color(0xff5f7c61))),
|
||||
darkTheme: ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
textTheme: Typography.englishLike2021,
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
brightness: Brightness.dark,
|
||||
seedColor: const Color(0xff5f7c61),
|
||||
)),
|
||||
themeMode: ThemeMode.dark,
|
||||
themeMode: ThemeMode.system,
|
||||
home: const MyHomePage(),
|
||||
);
|
||||
}
|
||||
|
@ -76,21 +80,9 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
],
|
||||
),
|
||||
body: <Widget>[
|
||||
Container(
|
||||
color: Colors.red,
|
||||
alignment: Alignment.center,
|
||||
child: Text('Page $test'),
|
||||
),
|
||||
Finance(),
|
||||
ListView(children: const <Widget>[
|
||||
ListTile(
|
||||
leading: Icon(Icons.dark_mode),
|
||||
title: Text(
|
||||
'Hier könnten Einstellungen zu Darkmode mit shared_preferences und riverpod sein',
|
||||
maxLines: 2,
|
||||
),
|
||||
)
|
||||
])
|
||||
const Shopping(),
|
||||
const Finance(),
|
||||
const Settings()
|
||||
][currentPageIndex],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
import 'package:uuid/uuid.dart';
|
||||
|
||||
final now = DateTime.now();
|
||||
final DateTime now = DateTime.now();
|
||||
|
||||
enum TransaktionArt { monatlBeitrag, aufladung, einkauf, korrektur }
|
||||
|
||||
enum Unit { stueck, menge }
|
||||
|
||||
enum Category {
|
||||
obstUndGemuese,
|
||||
brotCerealienUndAufstriche,
|
||||
getraenke,
|
||||
drogerieUndHaushalt,
|
||||
kochenUndBacken,
|
||||
oeleSossenUndGewuerze,
|
||||
suessesUndKnabbereien
|
||||
class Category {
|
||||
final String name;
|
||||
final String icon;
|
||||
const Category({required this.name, required this.icon});
|
||||
}
|
||||
|
||||
class Transaction {
|
||||
|
@ -21,36 +17,36 @@ class Transaction {
|
|||
int amount;
|
||||
TransaktionArt type;
|
||||
DateTime date;
|
||||
Basket? basket;
|
||||
String? description;
|
||||
Transaction(this.type, this.amount, this.date, [this.description]);
|
||||
Transaction(
|
||||
{required this.type,
|
||||
required this.amount,
|
||||
required this.date,
|
||||
this.description,
|
||||
this.basket});
|
||||
}
|
||||
|
||||
class Product {
|
||||
final int id = 0;
|
||||
final String name;
|
||||
final Unit unit;
|
||||
final double price; //pro Kilogramm oder Stück, d.h. pro unit
|
||||
final Unit unit; //pro Kg oder Stück
|
||||
final double price;
|
||||
final double vat;
|
||||
final Category category;
|
||||
const Product(this.name, this.unit, this.price, this.vat, this.category);
|
||||
}
|
||||
|
||||
class Purchase {
|
||||
Product product;
|
||||
int amount;
|
||||
Purchase(this.product, this.amount);
|
||||
}
|
||||
|
||||
class Basket {
|
||||
Map<Product, int> purchases;
|
||||
double price;
|
||||
String guid;
|
||||
|
||||
Basket(this.purchases, this.price): guid = const Uuid().v4();
|
||||
Basket(this.purchases, this.price) : guid = const Uuid().v4();
|
||||
|
||||
void addItem(Product product, int quantity) {
|
||||
if (purchases.containsKey(product)) {
|
||||
purchases.update(product, (existingQuantity) => existingQuantity + quantity);
|
||||
purchases.update(
|
||||
product, (existingQuantity) => existingQuantity + quantity);
|
||||
} else {
|
||||
purchases[product] = quantity;
|
||||
}
|
||||
|
@ -61,58 +57,167 @@ class Basket {
|
|||
}
|
||||
}
|
||||
|
||||
//sample data
|
||||
class SampleData {
|
||||
final List<Transaction> transactions = [
|
||||
Transaction(TransaktionArt.monatlBeitrag, 0, now),
|
||||
Transaction(TransaktionArt.aufladung, 2042, now),
|
||||
Transaction(
|
||||
TransaktionArt.einkauf, -2442, now.subtract(const Duration(days: 2))),
|
||||
Transaction(TransaktionArt.korrektur, 2332,
|
||||
now.subtract(const Duration(hours: 5)), 'Korrektur des Warenkorbs'),
|
||||
Transaction(TransaktionArt.monatlBeitrag, 0, now),
|
||||
Transaction(TransaktionArt.aufladung, 2042, now),
|
||||
Transaction(
|
||||
TransaktionArt.einkauf, -2442, now.subtract(const Duration(days: 2))),
|
||||
Transaction(TransaktionArt.korrektur, 2332,
|
||||
now.subtract(const Duration(hours: 5)), 'Korrektur des Warenkorbs'),
|
||||
Transaction(TransaktionArt.monatlBeitrag, 0, now),
|
||||
Transaction(TransaktionArt.aufladung, 2042, now),
|
||||
Transaction(
|
||||
TransaktionArt.einkauf, -2442, now.subtract(const Duration(days: 2))),
|
||||
Transaction(TransaktionArt.korrektur, 2332,
|
||||
now.subtract(const Duration(hours: 5)), 'Korrektur des Warenkorbs'),
|
||||
Transaction(TransaktionArt.monatlBeitrag, 0, now),
|
||||
Transaction(TransaktionArt.aufladung, 2042, now),
|
||||
Transaction(
|
||||
TransaktionArt.einkauf, -2442, now.subtract(const Duration(days: 2))),
|
||||
Transaction(TransaktionArt.korrektur, 2332,
|
||||
now.subtract(const Duration(hours: 5)), 'Korrektur des Warenkorbs'),
|
||||
Transaction(TransaktionArt.monatlBeitrag, 0, now),
|
||||
Transaction(TransaktionArt.aufladung, 2042, now),
|
||||
Transaction(
|
||||
TransaktionArt.einkauf, -2442, now.subtract(const Duration(days: 2))),
|
||||
Transaction(TransaktionArt.korrektur, 2332,
|
||||
now.subtract(const Duration(hours: 5)), 'Korrektur des Warenkorbs')
|
||||
static const List<Category> categories = [
|
||||
Category(name: 'Obst und Gemüse', icon: '🍒'),
|
||||
Category(name: 'Kochen und Backen', icon: '🍝'),
|
||||
Category(name: 'Brot, Cerealien & Aufstriche', icon: '🍞'),
|
||||
Category(name: 'Getränke und Pfand', icon: '🫖'),
|
||||
Category(name: 'Drogerie und Haushalt', icon: '🧼'),
|
||||
Category(name: 'Öl, Soßen und Gewürze', icon: '🫚'),
|
||||
Category(name: 'Süßes und Knabbereien', icon: '🍪')
|
||||
];
|
||||
|
||||
static const List<Product> products = [
|
||||
Product('Apfel', Unit.stueck, 0.23, 7, Category.obstUndGemuese),
|
||||
Product('Mehl', Unit.menge, 0.003, 19, Category.kochenUndBacken),
|
||||
Product('Brot', Unit.stueck, 1.23, 7, Category.brotCerealienUndAufstriche),
|
||||
Product('Milch', Unit.stueck, 2.23, 3, Category.getraenke),
|
||||
Product('Zahnpasta', Unit.stueck, 0.23, 7, Category.drogerieUndHaushalt),
|
||||
Product('Pfeffer', Unit.stueck, 0.23, 7, Category.oeleSossenUndGewuerze),
|
||||
Product('Schokolade', Unit.menge, 0.23, 7, Category.suessesUndKnabbereien)
|
||||
static List<Product> products = [
|
||||
Product('Apfel', Unit.stueck, 0.23, 7, categories[0]),
|
||||
Product('Mehl', Unit.menge, 0.003, 19, categories[1]),
|
||||
Product('Brot', Unit.stueck, 1.23, 7, categories[2]),
|
||||
Product('Milch', Unit.stueck, 2.23, 3, categories[3]),
|
||||
Product('Zahnpasta', Unit.stueck, 0.23, 7, categories[4]),
|
||||
Product('Pfeffer', Unit.stueck, 0.23, 7, categories[5]),
|
||||
Product('Schokolade', Unit.menge, 0.23, 7, categories[6]),
|
||||
Product('Flaschenpfand', Unit.stueck, -0.15, 0, categories[3])
|
||||
];
|
||||
|
||||
Basket basket = Basket({
|
||||
//such a basket can not exist later. It is for testing purposes
|
||||
// when scrolling through a long basket
|
||||
static Basket basket = Basket({
|
||||
products[0]: 20,
|
||||
products[1]: 2200,
|
||||
products[2]: 2,
|
||||
products[3]: 1,
|
||||
products[4]: 1,
|
||||
products[5]: 2,
|
||||
products[6]: 202
|
||||
}, 304);
|
||||
products[6]: 222,
|
||||
products[0]: 20,
|
||||
products[1]: 2200,
|
||||
products[2]: 2,
|
||||
products[3]: 1,
|
||||
products[4]: 1,
|
||||
products[5]: 2,
|
||||
products[6]: 222
|
||||
}, 27.9);
|
||||
|
||||
static Basket basket2 = Basket({
|
||||
products[0]: 22,
|
||||
products[1]: 2241,
|
||||
products[3]: 2,
|
||||
products[4]: 4,
|
||||
products[6]: 2,
|
||||
products[7]: 5,
|
||||
}, 34);
|
||||
|
||||
static Basket basket3 = Basket({
|
||||
products[0]: 2,
|
||||
products[1]: 21,
|
||||
products[3]: 4,
|
||||
products[4]: 1,
|
||||
products[6]: 5,
|
||||
}, -1);
|
||||
|
||||
List<Transaction> transactions = [
|
||||
Transaction(
|
||||
type: TransaktionArt.monatlBeitrag,
|
||||
amount: 0,
|
||||
date: now,
|
||||
),
|
||||
Transaction(
|
||||
type: TransaktionArt.aufladung,
|
||||
amount: 2042,
|
||||
date: now,
|
||||
),
|
||||
Transaction(
|
||||
type: TransaktionArt.einkauf,
|
||||
amount: -2442,
|
||||
date: now.subtract(const Duration(days: 2)),
|
||||
basket: basket),
|
||||
Transaction(
|
||||
type: TransaktionArt.korrektur,
|
||||
amount: 2332,
|
||||
date: now.subtract(const Duration(hours: 5)),
|
||||
description: 'Korrektur des Warenkorbs',
|
||||
basket: basket3,
|
||||
),
|
||||
Transaction(
|
||||
type: TransaktionArt.monatlBeitrag,
|
||||
amount: 0,
|
||||
date: now,
|
||||
),
|
||||
Transaction(
|
||||
type: TransaktionArt.aufladung,
|
||||
amount: 2042,
|
||||
date: now,
|
||||
),
|
||||
Transaction(
|
||||
type: TransaktionArt.einkauf,
|
||||
amount: -2442,
|
||||
date: now.subtract(const Duration(days: 2)),
|
||||
basket: basket2),
|
||||
Transaction(
|
||||
type: TransaktionArt.korrektur,
|
||||
amount: 2332,
|
||||
date: now.subtract(const Duration(hours: 5)),
|
||||
description: 'Korrektur des FinanzAK'),
|
||||
Transaction(
|
||||
type: TransaktionArt.monatlBeitrag,
|
||||
amount: 0,
|
||||
date: now,
|
||||
),
|
||||
Transaction(
|
||||
type: TransaktionArt.aufladung,
|
||||
amount: 2042,
|
||||
date: now,
|
||||
),
|
||||
Transaction(
|
||||
type: TransaktionArt.einkauf,
|
||||
amount: -2442,
|
||||
date: now.subtract(const Duration(days: 2)),
|
||||
basket: basket,
|
||||
),
|
||||
Transaction(
|
||||
type: TransaktionArt.korrektur,
|
||||
amount: 2332,
|
||||
date: now.subtract(const Duration(hours: 5)),
|
||||
description: 'Korrektur des Warenkorbs'),
|
||||
Transaction(
|
||||
type: TransaktionArt.monatlBeitrag,
|
||||
amount: 0,
|
||||
date: now,
|
||||
),
|
||||
Transaction(
|
||||
type: TransaktionArt.aufladung,
|
||||
amount: 2042,
|
||||
date: now,
|
||||
),
|
||||
Transaction(
|
||||
type: TransaktionArt.einkauf,
|
||||
amount: -2442,
|
||||
date: now.subtract(const Duration(days: 2)),
|
||||
basket: basket),
|
||||
Transaction(
|
||||
type: TransaktionArt.korrektur,
|
||||
amount: 2332,
|
||||
date: now.subtract(const Duration(hours: 5)),
|
||||
description: 'Korrektur des Warenkorbs'),
|
||||
Transaction(
|
||||
type: TransaktionArt.monatlBeitrag,
|
||||
amount: 0,
|
||||
date: now,
|
||||
),
|
||||
Transaction(
|
||||
type: TransaktionArt.aufladung,
|
||||
amount: 2042,
|
||||
date: now,
|
||||
),
|
||||
Transaction(
|
||||
type: TransaktionArt.einkauf,
|
||||
amount: -2442,
|
||||
date: now.subtract(const Duration(days: 2)),
|
||||
basket: basket2),
|
||||
Transaction(
|
||||
type: TransaktionArt.korrektur,
|
||||
amount: 2332,
|
||||
date: now.subtract(const Duration(hours: 5)),
|
||||
description: 'Korrektur des Warenkorbs'),
|
||||
];
|
||||
}
|
||||
|
|
19
Frontend-user/lib/settings.dart
Normal file
19
Frontend-user/lib/settings.dart
Normal file
|
@ -0,0 +1,19 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class Settings extends StatelessWidget {
|
||||
const Settings({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
//Hier in dem Return kann der Inhalt des Setting Tabs angepasst werden
|
||||
return ListView(children: const <Widget>[
|
||||
ListTile(
|
||||
leading: Icon(Icons.dark_mode),
|
||||
title: Text(
|
||||
'Hier könnten Einstellungen zu Darkmode sein',
|
||||
maxLines: 2,
|
||||
),
|
||||
)
|
||||
]);
|
||||
}
|
||||
}
|
41
Frontend-user/lib/shopping.dart
Normal file
41
Frontend-user/lib/shopping.dart
Normal file
|
@ -0,0 +1,41 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:mitgliederladen/sample_data.dart';
|
||||
|
||||
class Shopping extends StatelessWidget {
|
||||
const Shopping({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
floatingActionButton: FloatingActionButton(
|
||||
child: const Icon(Icons.shopping_cart),
|
||||
onPressed: () {},
|
||||
),
|
||||
body: GridView.builder(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2),
|
||||
itemCount: SampleData.categories.length,
|
||||
itemBuilder: ((context, index) {
|
||||
return Card(
|
||||
child: Column(children: [
|
||||
Expanded(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
SampleData.categories[index].icon,
|
||||
style: const TextStyle(fontSize: 400),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
SampleData.categories[index].name,
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
),
|
||||
),
|
||||
]),
|
||||
);
|
||||
})));
|
||||
}
|
||||
}
|
|
@ -28,7 +28,6 @@ environment:
|
|||
# the latest version available on pub.dev. To see which dependencies have newer
|
||||
# versions available, run `flutter pub outdated`.
|
||||
dependencies:
|
||||
uuid: ^3.0.4
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
|
@ -37,6 +36,7 @@ dependencies:
|
|||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.2
|
||||
intl: ^0.18.1
|
||||
uuid: ^3.0.7
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
34
README.md
34
README.md
|
@ -1,2 +1,36 @@
|
|||
# Mitgliederladen
|
||||
|
||||
|
||||
## Development branches
|
||||
|
||||
backend
|
||||
: Server component, using Rust/SQL, offering API (via HTTP/2?)
|
||||
|
||||
db_scripts
|
||||
: SQL scripts
|
||||
|
||||
main
|
||||
: frontend client as flutter app
|
||||
|
||||
|
||||
|
||||
## Tech used
|
||||
|
||||
### Backend
|
||||
|
||||
- Database connection using Rust-lang and SQLx
|
||||
|
||||
- RESTful (Level 2, JSON over RPC) API using Rust-lang (and probably (actix web framework)[https://actix.rs] plus (utoipa\[sic! 🍺 beer-branded punny name\])[https://github.com/juhaku/utoipa]
|
||||
|
||||
## Frontend
|
||||
|
||||
- Google Flutter using Dart.
|
||||
|
||||
Target platforms?
|
||||
|
||||
- probably Android[^1], iOS[^2].
|
||||
- Debug target could be desktop for fastest deployment. Production targets: Perhaps desktop and/or web, if wanted.
|
||||
|
||||
|
||||
[1]: via APK? Via Play Store or F-Droid? Then Registration as Play store developer (for charity or one time purchase with credit card?) needed.
|
||||
[2]: Registration as Apple developer for charity needed.
|
7
db_scripts/drop.sql
Normal file
7
db_scripts/drop.sql
Normal file
|
@ -0,0 +1,7 @@
|
|||
DROP TABLE IF EXISTS mitglied;
|
||||
DROP TABLE IF EXISTS transaktion_art;
|
||||
DROP TABLE IF EXISTS transaktion;
|
||||
DROP TABLE IF EXISTS monatlicher_beitrag;
|
||||
DROP TABLE IF EXISTS mwst;
|
||||
DROP TABLE IF EXISTS einheit;
|
||||
DROP TABLE IF EXISTS artikel;
|
21
server/.gitignore
vendored
Normal file
21
server/.gitignore
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
Cargo.lock
|
||||
target/
|
||||
guide/build/
|
||||
/gh-pages
|
||||
|
||||
*.so
|
||||
*.out
|
||||
*.pyc
|
||||
*.pid
|
||||
*.sock
|
||||
*~
|
||||
.DS_Store
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# Configuration directory generated by CLion
|
||||
.idea
|
||||
|
||||
# Configuration directory generated by VSCode
|
||||
.vscode
|
10
server/Cargo.toml
Normal file
10
server/Cargo.toml
Normal file
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "server"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
actix-web ="4"
|
||||
utoipa = { version = "3", features = ["actix_extras"] }
|
15
server/readme.md
Normal file
15
server/readme.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
# installation
|
||||
|
||||
## windows (step 1)
|
||||
|
||||
choco install -y
|
||||
|
||||
## Linux, macos (alernate step 1)
|
||||
|
||||
use apt, pacman, curl, brew or whatever.
|
||||
|
||||
## Always (steps 2 and so on)
|
||||
|
||||
- `rustup-init.sh` (Windows: rustup-init.ex, e.g. %AppData%\Local\Temp\chocolatey\rustup.install\1.25.1\rustup-init.exe)
|
||||
- select a good match in the dialog of this CLI installer
|
||||
|
28
server/src/main.rs
Normal file
28
server/src/main.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
|
||||
|
||||
#[get("/")]
|
||||
async fn hello() -> impl Responder {
|
||||
HttpResponse::Ok().body("Hello world!")
|
||||
}
|
||||
|
||||
#[post("/echo")]
|
||||
async fn echo(req_body: String) -> impl Responder {
|
||||
HttpResponse::Ok().body(req_body)
|
||||
}
|
||||
|
||||
async fn manual_hello() -> impl Responder {
|
||||
HttpResponse::Ok().body("Hey there!")
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
HttpServer::new(|| {
|
||||
App::new()
|
||||
.service(hello)
|
||||
.service(echo)
|
||||
.route("/hey", web::get().to(manual_hello))
|
||||
})
|
||||
.bind(("127.0.0.1", 8080))?
|
||||
.run()
|
||||
.await
|
||||
}
|
Loading…
Reference in a new issue