forked from mgl_crew/Mitgliederladen
Compare commits
36 commits
db_scripts
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
fdabfcf741 | ||
|
b3274f1a2a | ||
|
8f6c4ac81a | ||
|
70818931aa | ||
|
3bfab0bcae | ||
|
f3aac41876 | ||
|
220025e109 | ||
7388527c43 | |||
f0415a22da | |||
496a1f2857 | |||
e0b571a808 | |||
e0aa77fb96 | |||
2f84e61ba1 | |||
df255d71b8 | |||
000f141b5a | |||
4da346f6d7 | |||
0ee26fedb0 | |||
7c9d449c63 | |||
4bbd70464f | |||
|
597db5d51f | ||
|
05b12991c2 | ||
26d0735bcf | |||
d9bf3c4fea | |||
d97f1fbb1c | |||
6c19b77768 | |||
cf21ead3e2 | |||
ee6bb34405 | |||
ebd05ca96c | |||
45f76eab17 | |||
3ab6cd5690 | |||
f083ab8cf0 | |||
893b421fe3 | |||
f343214df0 | |||
99282eb9da | |||
df0fabce0f | |||
5ad2ae0867 |
19 changed files with 1021 additions and 113 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,2 +1,4 @@
|
||||||
.idea
|
.idea
|
||||||
db/*
|
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
|
For help getting started with Flutter development, view the
|
||||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||||
samples, guidance on mobile development, and a full API reference.
|
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)
|
BIN
Frontend-user/assets/logo_sonako_4c_optimal.png
Normal file
BIN
Frontend-user/assets/logo_sonako_4c_optimal.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 713 KiB |
76
Frontend-user/lib/basket.dart
Normal file
76
Frontend-user/lib/basket.dart
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'sample_data.dart';
|
||||||
|
|
||||||
|
class ShowBasket extends StatelessWidget {
|
||||||
|
final Basket basket;
|
||||||
|
final bool editable;
|
||||||
|
const ShowBasket(this.basket, {this.editable = false, super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: getBasket(basket),
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(
|
||||||
|
Icons.euro,
|
||||||
|
),
|
||||||
|
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),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
60
Frontend-user/lib/expand.dart
Normal file
60
Frontend-user/lib/expand.dart
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
//this was used in a earlier version of the app and will possibly be removed in future versions.
|
||||||
|
|
||||||
|
class Expand extends StatefulWidget {
|
||||||
|
final Widget child;
|
||||||
|
final bool expand;
|
||||||
|
const Expand({super.key, this.expand = true, required this.child});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<Expand> createState() => _ExpandState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ExpandState extends State<Expand> with SingleTickerProviderStateMixin {
|
||||||
|
late AnimationController expandController;
|
||||||
|
late Animation<double> animation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
prepareAnimations();
|
||||||
|
_runExpandCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
///Setting up the animation
|
||||||
|
void prepareAnimations() {
|
||||||
|
expandController = AnimationController(
|
||||||
|
vsync: this, duration: const Duration(milliseconds: 500));
|
||||||
|
animation = CurvedAnimation(
|
||||||
|
parent: expandController,
|
||||||
|
curve: Curves.fastOutSlowIn,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _runExpandCheck() {
|
||||||
|
if (widget.expand) {
|
||||||
|
expandController.forward();
|
||||||
|
} else {
|
||||||
|
expandController.reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(Expand oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
_runExpandCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
expandController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizeTransition(
|
||||||
|
axisAlignment: 1.0, sizeFactor: animation, child: widget.child);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,103 +1,111 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:intl/date_symbol_data_local.dart';
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
|
import 'basket.dart';
|
||||||
/*
|
import 'sample_data.dart';
|
||||||
todo:
|
|
||||||
- Design
|
|
||||||
- Details zum Warenkorb und Beschreibung (Popup?)
|
|
||||||
- farbliche Hervorhebungen
|
|
||||||
*/
|
|
||||||
|
|
||||||
enum Art { monatlBeitrag, aufladung, einkauf, korrektur }
|
|
||||||
|
|
||||||
final now = DateTime.now();
|
|
||||||
|
|
||||||
class Transaction {
|
|
||||||
int betrag;
|
|
||||||
Art art;
|
|
||||||
DateTime datum;
|
|
||||||
String beschreibung;
|
|
||||||
Transaction(this.art, this.betrag, this.datum, this.beschreibung);
|
|
||||||
}
|
|
||||||
|
|
||||||
class Finance extends StatelessWidget {
|
class Finance extends StatelessWidget {
|
||||||
Finance({super.key});
|
const Finance({super.key});
|
||||||
|
|
||||||
final List<Transaction> transactions = [
|
|
||||||
Transaction(Art.monatlBeitrag, 0, now, ''),
|
|
||||||
Transaction(Art.aufladung, 2042, now, ''),
|
|
||||||
Transaction(Art.einkauf, -2442, now.subtract(const Duration(days: 2)), ''),
|
|
||||||
Transaction(Art.korrektur, 2332, now.subtract(const Duration(hours: 5)),
|
|
||||||
'Korrektur des Warenkorbs')
|
|
||||||
];
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
initializeDateFormatting('de_DE');
|
initializeDateFormatting('de_DE');
|
||||||
|
Intl.defaultLocale = 'de_DE';
|
||||||
|
|
||||||
return ListView.builder(
|
return Scaffold(
|
||||||
itemCount: null,
|
appBar: AppBar(
|
||||||
itemBuilder: (context, index) {
|
toolbarHeight: 75,
|
||||||
if (index == 0) {
|
title: const Card(
|
||||||
return const Card(
|
child: ListTile(
|
||||||
child: Padding(
|
leading: Icon(
|
||||||
padding: EdgeInsets.all(8.0),
|
Icons.euro,
|
||||||
child: Row(
|
),
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
title: Text(
|
||||||
children: [
|
'Aktuelles Guthaben:',
|
||||||
Icon(
|
),
|
||||||
Icons.euro,
|
subtitle: Text(
|
||||||
//color: Colors.black,
|
'-00,34€',
|
||||||
//semanticLabel: 'Text for screenreader',
|
)),
|
||||||
),
|
),
|
||||||
Column(
|
),
|
||||||
children: [
|
body: ListView.builder(
|
||||||
Text(
|
itemCount: SampleData().transactions.length,
|
||||||
'Aktuelles Guthaben:',
|
itemBuilder: (context, index) {
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
return Card(
|
||||||
),
|
child: ListTile(
|
||||||
Text(
|
leading: getIcon(SampleData().transactions[index].type),
|
||||||
'-00,34€',
|
title:
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
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) {
|
||||||
} else if (index <= transactions.length) {
|
return ShowBasket(SampleData()
|
||||||
return Card(
|
.transactions[index]
|
||||||
child: Padding(
|
.basket!);
|
||||||
padding: const EdgeInsets.all(8.0),
|
});
|
||||||
child: Row(
|
}
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
: null));
|
||||||
children: [
|
}));
|
||||||
Icon(transactions[index - 1].art == Art.korrektur
|
}
|
||||||
? Icons.error
|
|
||||||
: Icons.money),
|
String gettitle(TransaktionArt art) {
|
||||||
Column(
|
switch (art) {
|
||||||
children: [
|
case TransaktionArt.aufladung:
|
||||||
Row(
|
return 'Aufladung';
|
||||||
children: [
|
case TransaktionArt.einkauf:
|
||||||
Text(DateFormat("EEEE, dd. MMMM yyyy HH:mm", 'de_DE')
|
return 'Einkauf';
|
||||||
.format(now)),
|
case TransaktionArt.korrektur:
|
||||||
Text(
|
return 'Korrektur';
|
||||||
'${transactions[index - 1].art}: ${transactions[index - 1].betrag / 100}€'),
|
case TransaktionArt.monatlBeitrag:
|
||||||
],
|
return 'Monatlicher Beitrag';
|
||||||
),
|
default:
|
||||||
Text(transactions[index - 1].beschreibung)
|
return 'Ein Error ist aufgetreten';
|
||||||
],
|
}
|
||||||
),
|
}
|
||||||
],
|
|
||||||
),
|
Text getSubtitle(Transaction transaction) {
|
||||||
),
|
String text = '${transaction.amount / 100}';
|
||||||
);
|
text += '€ ';
|
||||||
} else {
|
text += DateFormat("EEEE, dd. MMMM yyyy HH:mm").format(transaction.date);
|
||||||
return null;
|
if (transaction.description != null) {
|
||||||
}
|
(text += '\n${transaction.description}');
|
||||||
},
|
}
|
||||||
);
|
|
||||||
|
return Text(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon getIcon(TransaktionArt art) {
|
||||||
|
switch (art) {
|
||||||
|
case TransaktionArt.aufladung:
|
||||||
|
return const Icon(Icons.savings);
|
||||||
|
case TransaktionArt.einkauf:
|
||||||
|
return const Icon(Icons.shopping_basket);
|
||||||
|
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:flutter/material.dart';
|
||||||
|
import 'package:mitgliederladen/shopping.dart';
|
||||||
|
import 'package:mitgliederladen/settings.dart';
|
||||||
import 'finance.dart';
|
import 'finance.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
@ -14,10 +16,21 @@ class MyApp extends StatelessWidget {
|
||||||
title: 'SoNaKo Demo App',
|
title: 'SoNaKo Demo App',
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
|
//darkgreen:5f7c61, mediumgreen: 66906a, lightgreen: 9cbe96, yellow: f5de64
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
|
brightness: Brightness.light,
|
||||||
|
textTheme: Typography.englishLike2021,
|
||||||
colorScheme: ColorScheme.fromSeed(
|
colorScheme: ColorScheme.fromSeed(
|
||||||
seedColor: Colors.green)), //9bbee6 //79,128,104
|
brightness: Brightness.light,
|
||||||
darkTheme: ThemeData.dark(),
|
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.system,
|
themeMode: ThemeMode.system,
|
||||||
home: const MyHomePage(),
|
home: const MyHomePage(),
|
||||||
);
|
);
|
||||||
|
@ -39,6 +52,8 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
leading:
|
||||||
|
const Image(image: AssetImage('assets/logo_sonako_4c_optimal.png')),
|
||||||
title: const Text('SoNaKo Demo App'),
|
title: const Text('SoNaKo Demo App'),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: NavigationBar(
|
bottomNavigationBar: NavigationBar(
|
||||||
|
@ -65,14 +80,9 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: <Widget>[
|
body: <Widget>[
|
||||||
Container(
|
const Shopping(),
|
||||||
color: Colors.red,
|
const Finance(),
|
||||||
alignment: Alignment.center,
|
const Settings()
|
||||||
child: Text('Page $test'),
|
|
||||||
),
|
|
||||||
Finance(),
|
|
||||||
const Text(
|
|
||||||
'Hier könnten Einstellungen zu Darkmode mit shared_preferences und riverpod sein')
|
|
||||||
][currentPageIndex],
|
][currentPageIndex],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
223
Frontend-user/lib/sample_data.dart
Normal file
223
Frontend-user/lib/sample_data.dart
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
final DateTime now = DateTime.now();
|
||||||
|
|
||||||
|
enum TransaktionArt { monatlBeitrag, aufladung, einkauf, korrektur }
|
||||||
|
|
||||||
|
enum Unit { stueck, menge }
|
||||||
|
|
||||||
|
class Category {
|
||||||
|
final String name;
|
||||||
|
final String icon;
|
||||||
|
const Category({required this.name, required this.icon});
|
||||||
|
}
|
||||||
|
|
||||||
|
class Transaction {
|
||||||
|
int id = 0;
|
||||||
|
int amount;
|
||||||
|
TransaktionArt type;
|
||||||
|
DateTime date;
|
||||||
|
Basket? basket;
|
||||||
|
String? 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; //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 Basket {
|
||||||
|
Map<Product, int> purchases;
|
||||||
|
double price;
|
||||||
|
String guid;
|
||||||
|
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);
|
||||||
|
} else {
|
||||||
|
purchases[product] = quantity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeItem(Product product) {
|
||||||
|
purchases.remove(product);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SampleData {
|
||||||
|
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 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])
|
||||||
|
];
|
||||||
|
|
||||||
|
//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]: 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,6 +41,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.1"
|
version: "1.17.1"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.3"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -184,6 +192,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.1"
|
version: "0.5.1"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.2"
|
||||||
|
uuid:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: uuid
|
||||||
|
sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.7"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -36,6 +36,7 @@ dependencies:
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.2
|
cupertino_icons: ^1.0.2
|
||||||
intl: ^0.18.1
|
intl: ^0.18.1
|
||||||
|
uuid: ^3.0.7
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
@ -60,9 +61,8 @@ flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
||||||
# To add assets to your application, add an assets section, like this:
|
# To add assets to your application, add an assets section, like this:
|
||||||
# assets:
|
assets:
|
||||||
# - images/a_dot_burr.jpeg
|
- assets/
|
||||||
# - images/a_dot_ham.jpeg
|
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||||
|
|
34
README.md
34
README.md
|
@ -1,2 +1,36 @@
|
||||||
# Mitgliederladen
|
# 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.
|
|
@ -13,9 +13,9 @@ CREATE TABLE IF NOT EXISTS transaktion_art (
|
||||||
art VARCHAR(50) NOT NULL
|
art VARCHAR(50) NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
INSERT INTO transaktion_art(art_id, art) VALUES (1, 'monatlicher_beitrag');
|
INSERT OR IGNORE INTO transaktion_art(art_id, art) VALUES (1, 'monatlicher_beitrag');
|
||||||
INSERT INTO transaktion_art(art_id, art) VALUES (2, 'aufladung');
|
INSERT OR IGNORE INTO transaktion_art(art_id, art) VALUES (2, 'aufladung');
|
||||||
INSERT INTO transaktion_art(art_id, art) VALUES (3, 'einkauf');
|
INSERT OR IGNORE INTO transaktion_art(art_id, art) VALUES (3, 'einkauf');
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS transaktion (
|
CREATE TABLE IF NOT EXISTS transaktion (
|
||||||
transaktion_id INT NOT NULL PRIMARY KEY,
|
transaktion_id INT NOT NULL PRIMARY KEY,
|
||||||
|
@ -41,16 +41,16 @@ CREATE TABLE IF NOT EXISTS mwst (
|
||||||
prozent INT NOT NULL
|
prozent INT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
INSERT INTO mwst(mwst_id, prozent) VALUES (1, 7);
|
INSERT OR IGNORE INTO mwst(mwst_id, prozent) VALUES (1, 7);
|
||||||
INSERT INTO mwst(mwst_id, prozent) VALUES (2, 19);
|
INSERT OR IGNORE INTO mwst(mwst_id, prozent) VALUES (2, 19);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS einheit(
|
CREATE TABLE IF NOT EXISTS einheit(
|
||||||
einheit_id INT NOT NULL PRIMARY KEY,
|
einheit_id INT NOT NULL PRIMARY KEY,
|
||||||
bezeichnung VARCHAR(50) NOT NULL
|
bezeichnung VARCHAR(50) NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
INSERT INTO einheit(einheit_id, bezeichnung) VALUES (1, 'stueck');
|
INSERT OR IGNORE INTO einheit(einheit_id, bezeichnung) VALUES (1, 'stueck');
|
||||||
INSERT INTO einheit(einheit_id, bezeichnung) VALUES (2, 'menge');
|
INSERT OR IGNORE INTO einheit(einheit_id, bezeichnung) VALUES (2, 'menge');
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS artikel (
|
CREATE TABLE IF NOT EXISTS artikel (
|
||||||
artikel_id INT NOT NULL PRIMARY KEY,
|
artikel_id INT NOT NULL PRIMARY KEY,
|
||||||
|
@ -59,4 +59,4 @@ CREATE TABLE IF NOT EXISTS artikel (
|
||||||
preis INT NOT NULL,
|
preis INT NOT NULL,
|
||||||
CONSTRAINT fk_mwst_artikel FOREIGN KEY (mwst_id) REFERENCES mwst(mwst_id),
|
CONSTRAINT fk_mwst_artikel FOREIGN KEY (mwst_id) REFERENCES mwst(mwst_id),
|
||||||
CONSTRAINT fk_einheit_artikel FOREIGN KEY (einheit_id) REFERENCES einheit(einheit_id)
|
CONSTRAINT fk_einheit_artikel FOREIGN KEY (einheit_id) REFERENCES einheit(einheit_id)
|
||||||
);
|
);
|
||||||
|
|
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
|
||||||
|
}
|
319
shared_assets/sonako.svg
Normal file
319
shared_assets/sonako.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 622 KiB |
Loading…
Reference in a new issue