Merge pull request #96 from itsscb:rf/app-from-scratch-with-backend-service
Rf/app-from-scratch-with-backend-service
This commit is contained in:
commit
bc80e10775
@ -13,9 +13,7 @@ class Session {
|
||||
this.refreshToken,
|
||||
this.refreshTokenExpiresAt,
|
||||
this.accountId,
|
||||
}) {
|
||||
_init();
|
||||
}
|
||||
});
|
||||
|
||||
String? sessionId;
|
||||
String? accessToken;
|
||||
@ -40,6 +38,12 @@ class Session {
|
||||
);
|
||||
Session s = Session();
|
||||
s._database = db;
|
||||
final sessions = await s.getSessions();
|
||||
if (sessions.isNotEmpty) {
|
||||
final session = sessions[0];
|
||||
session._database = db;
|
||||
return session;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@ -90,6 +94,41 @@ class Session {
|
||||
return 'Session{accountId: $accountId, sessionId: $sessionId, accessToken: $accessToken, accessTokenExpiresAt: ${accessTokenExpiresAt.toString()}, refreshToken: $refreshToken, refreshTokenExpiresAt: ${refreshTokenExpiresAt.toString()}}';
|
||||
}
|
||||
|
||||
static newSession(Session session) async {
|
||||
final db = await openDatabase(
|
||||
join(await getDatabasesPath(), 'df_database.db'),
|
||||
onCreate: (db, version) {
|
||||
return db.execute(
|
||||
'CREATE TABLE sessions(accountId INTEGER PRIMARY KEY, sessionId TEXT, accessToken TEXT, accessTokenExpiresAt TEXT, refreshToken TEXT, refreshTokenExpiresAt TEXT)',
|
||||
);
|
||||
},
|
||||
version: 1,
|
||||
);
|
||||
|
||||
await db.insert(
|
||||
'sessions',
|
||||
session.toMap(),
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<Session> updateToken(Session s) async {
|
||||
final db = await openDatabase(
|
||||
join(await getDatabasesPath(), 'df_database.db'),
|
||||
onCreate: (db, version) {
|
||||
return db.execute(
|
||||
'CREATE TABLE sessions(accountId INTEGER PRIMARY KEY, sessionId TEXT, accessToken TEXT, accessTokenExpiresAt TEXT, refreshToken TEXT, refreshTokenExpiresAt TEXT)',
|
||||
);
|
||||
},
|
||||
version: 1,
|
||||
);
|
||||
await db.update(
|
||||
'sessions',
|
||||
s.toMap(),
|
||||
);
|
||||
return s; //await getSession(s.accountId!);
|
||||
}
|
||||
|
||||
Future<void> insertSession(Session session) async {
|
||||
// print('INSERTING SESSION: ${session.sessionId}');
|
||||
final db = _database;
|
||||
@ -101,6 +140,17 @@ class Session {
|
||||
// print('INSERT RESULT: $result');
|
||||
}
|
||||
|
||||
Future<Session> updateSession(Session session) async {
|
||||
sessionId = session.sessionId;
|
||||
accessToken = session.accessToken;
|
||||
accessTokenExpiresAt = session.accessTokenExpiresAt;
|
||||
refreshToken = session.refreshToken;
|
||||
refreshTokenExpiresAt = session.refreshTokenExpiresAt;
|
||||
final db = _database;
|
||||
await db.update('sessions', session.toMap());
|
||||
return session;
|
||||
}
|
||||
|
||||
Future<void> removeSession(String sessionId) async {
|
||||
final db = _database;
|
||||
await db.delete('sessions', where: 'sessionId = ?', whereArgs: [sessionId]);
|
||||
@ -116,12 +166,14 @@ class Session {
|
||||
final db = await database;
|
||||
|
||||
final List<Map<String, Object?>> maps = await db.query('sessions');
|
||||
|
||||
// print(maps);
|
||||
final List<Session> sessions = List.generate(
|
||||
maps.length,
|
||||
(i) {
|
||||
// print('GOT MAP: ${maps[i]}');
|
||||
|
||||
if (maps[i]['sessionId'] == null) {
|
||||
return Session();
|
||||
}
|
||||
return Session(
|
||||
sessionId: maps[i]['sessionId'] as String,
|
||||
accessToken: maps[i]['accessToken'] as String,
|
||||
|
@ -1,6 +1,4 @@
|
||||
import 'package:app/gapi/client.dart';
|
||||
import 'package:app/pages/start_page.dart';
|
||||
import 'package:app/widgets/background.dart';
|
||||
import 'package:app/pages/home_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void main() async {
|
||||
@ -43,10 +41,8 @@ void main() async {
|
||||
backgroundColor: Colors.black,
|
||||
foregroundColor: Colors.white,
|
||||
)),
|
||||
home: Background(
|
||||
child: StartPage(
|
||||
client: await GClient.client,
|
||||
),
|
||||
home: HomePage(
|
||||
loggedOut: false,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
17
frontend/app/lib/model/apis/api_response.dart
Normal file
17
frontend/app/lib/model/apis/api_response.dart
Normal file
@ -0,0 +1,17 @@
|
||||
class ApiResponse<T> {
|
||||
Status status;
|
||||
T? data;
|
||||
String? message;
|
||||
|
||||
ApiResponse.initial(this.message) : status = Status.INITIAL;
|
||||
ApiResponse.loading(this.message) : status = Status.LOADING;
|
||||
ApiResponse.completed(this.data) : status = Status.COMPLETED;
|
||||
ApiResponse.error(this.message) : status = Status.ERROR;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "Status : $status \n Message: $message \n Data : $data";
|
||||
}
|
||||
}
|
||||
|
||||
enum Status { INITIAL, LOADING, COMPLETED, ERROR }
|
32
frontend/app/lib/model/apis/app_exception.dart
Normal file
32
frontend/app/lib/model/apis/app_exception.dart
Normal file
@ -0,0 +1,32 @@
|
||||
class AppException implements Exception {
|
||||
final _message;
|
||||
final _prefix;
|
||||
|
||||
AppException([this._message, this._prefix]);
|
||||
|
||||
String toString() {
|
||||
return "$_prefix$_message";
|
||||
}
|
||||
}
|
||||
|
||||
class FetchDataException extends AppException {
|
||||
FetchDataException([String? message])
|
||||
: super(message, 'Fehler bei der Kommunikation: ');
|
||||
}
|
||||
|
||||
class BadRequestException extends AppException {
|
||||
BadRequestException([message]) : super(message, 'Ungültige Anfrage; ');
|
||||
}
|
||||
|
||||
class UnauthorizedException extends AppException {
|
||||
UnauthorizedException([message])
|
||||
: super(message, 'Nicht authorisierte Anfrage; ');
|
||||
}
|
||||
|
||||
class InvalidInputException extends AppException {
|
||||
InvalidInputException([message]) : super(message, 'Ungültige Eingabe; ');
|
||||
}
|
||||
|
||||
class InternalException extends AppException {
|
||||
InternalException([message]) : super(message, 'Interner Fehler; ');
|
||||
}
|
345
frontend/app/lib/model/services/backend_service.dart
Normal file
345
frontend/app/lib/model/services/backend_service.dart
Normal file
@ -0,0 +1,345 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:app/model/apis/app_exception.dart';
|
||||
import 'package:app/pb/account.pb.dart';
|
||||
import 'package:app/pb/account_info.pb.dart';
|
||||
import 'package:app/pb/google/protobuf/timestamp.pb.dart';
|
||||
import 'package:app/pb/person.pb.dart';
|
||||
import 'package:app/data/database.dart';
|
||||
import 'package:app/pb/rpc_create_account.pb.dart';
|
||||
import 'package:app/pb/rpc_create_person.pb.dart';
|
||||
import 'package:app/pb/rpc_get_account.pb.dart';
|
||||
import 'package:app/pb/rpc_get_account_info.pb.dart';
|
||||
import 'package:app/pb/rpc_get_person.pb.dart';
|
||||
import 'package:app/pb/rpc_list_persons.pb.dart';
|
||||
import 'package:app/pb/rpc_login.pb.dart';
|
||||
import 'package:app/pb/rpc_refresh_token.pb.dart';
|
||||
import 'package:app/pb/service_df.pbgrpc.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:grpc/grpc.dart';
|
||||
|
||||
class BackendService {
|
||||
BackendService() {
|
||||
_init();
|
||||
}
|
||||
final String baseUrl = '10.0.0.2';
|
||||
final String port = '9090';
|
||||
|
||||
late Session _session;
|
||||
|
||||
final dfClient _client = dfClient(
|
||||
ClientChannel('10.0.2.2',
|
||||
port: 9090,
|
||||
options: const ChannelOptions(
|
||||
credentials: ChannelCredentials.insecure(),
|
||||
)),
|
||||
options: CallOptions(
|
||||
timeout: const Duration(seconds: 5),
|
||||
),
|
||||
);
|
||||
|
||||
static get client => dfClient(
|
||||
ClientChannel('10.0.2.2',
|
||||
port: 9090,
|
||||
options: const ChannelOptions(
|
||||
credentials: ChannelCredentials.insecure(),
|
||||
)),
|
||||
options: CallOptions(
|
||||
timeout: const Duration(seconds: 5),
|
||||
),
|
||||
);
|
||||
|
||||
void _init() {
|
||||
Session.session.then((value) => _session = value);
|
||||
}
|
||||
|
||||
Future<Session?> _isLoggedIn() async {
|
||||
Session session = await Session.session;
|
||||
if (session.accessToken == null ||
|
||||
session.refreshToken == null ||
|
||||
session.accountId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (session.accessTokenExpiresAt == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (session.refreshTokenExpiresAt == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (session.refreshTokenExpiresAt!.toDateTime().isBefore(DateTime.now())) {
|
||||
return null;
|
||||
} else {
|
||||
if (session.refreshToken != null &&
|
||||
session.accessTokenExpiresAt!.toDateTime().isBefore(DateTime.now())) {
|
||||
session = await refreshToken(session);
|
||||
}
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
static Future<bool> get isLoggedIn async {
|
||||
Session session = await Session.session;
|
||||
if (session.accessToken == null ||
|
||||
session.refreshToken == null ||
|
||||
session.accountId == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (session.accessTokenExpiresAt == null) {
|
||||
await logout();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (session.refreshTokenExpiresAt == null) {
|
||||
await logout();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (session.refreshTokenExpiresAt!.toDateTime().isBefore(DateTime.now())) {
|
||||
await logout();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (session.accessTokenExpiresAt!.toDateTime().isBefore(DateTime.now())) {
|
||||
Session s = await BackendService.refreshToken(session);
|
||||
if (s == session) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static Future<bool> createAccount(
|
||||
{required String email, required String password}) async {
|
||||
try {
|
||||
await BackendService.client.createAccount(CreateAccountRequest(
|
||||
email: email,
|
||||
password: password,
|
||||
));
|
||||
|
||||
return await login(email: email, password: password);
|
||||
} on SocketException {
|
||||
throw FetchDataException('Keine Internet Verbindung');
|
||||
} on GrpcError catch (err) {
|
||||
throw FetchDataException(err.message);
|
||||
} catch (err) {
|
||||
throw InternalException(err.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<Account> getAccount() async {
|
||||
Session? session = await _isLoggedIn();
|
||||
if (session == null) {
|
||||
throw UnauthorizedException('Sitzung ist abgelaufen');
|
||||
}
|
||||
try {
|
||||
final GetAccountResponse response = await _client.getAccount(
|
||||
GetAccountRequest(id: session.accountId),
|
||||
options: CallOptions(
|
||||
metadata: {'Authorization': 'Bearer ${session.accessToken}'}));
|
||||
return response.account;
|
||||
} on SocketException {
|
||||
throw FetchDataException('Keine Internet Verbindung');
|
||||
} on GrpcError catch (err) {
|
||||
if (err.code == 16) {
|
||||
throw UnauthorizedException(err.message);
|
||||
}
|
||||
throw FetchDataException(err.message);
|
||||
} catch (err) {
|
||||
throw InternalException(err.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<AccountInfo> getAccountInfo() async {
|
||||
Session session = await Session.session;
|
||||
if (session.accessTokenExpiresAt == null) {
|
||||
throw UnauthorizedException('Keine Siztung gefunden');
|
||||
}
|
||||
if (session.accessTokenExpiresAt!.toDateTime().isBefore(DateTime.now())) {
|
||||
session = await refreshToken(session);
|
||||
if (session.accessTokenExpiresAt!.toDateTime().isBefore(DateTime.now())) {
|
||||
throw UnauthorizedException('Sitzung ist abgelaufen');
|
||||
}
|
||||
}
|
||||
try {
|
||||
final GetAccountInfoResponse response = await _client
|
||||
.getAccountInfo(GetAccountInfoRequest(accountId: _session.accountId));
|
||||
return response.accountInfo;
|
||||
} on SocketException {
|
||||
throw FetchDataException('Keine Internet Verbindung');
|
||||
} on GrpcError catch (err) {
|
||||
if (err.code == 16) {
|
||||
await refreshToken(session);
|
||||
return getAccountInfo();
|
||||
}
|
||||
throw FetchDataException(err.message);
|
||||
} catch (err) {
|
||||
throw InternalException(err.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<Person> getPerson(Int64 personId) async {
|
||||
Session session = await Session.session;
|
||||
if (session.accessTokenExpiresAt == null) {
|
||||
throw UnauthorizedException('Keine Siztung gefunden');
|
||||
}
|
||||
if (session.accessTokenExpiresAt!.toDateTime().isBefore(DateTime.now())) {
|
||||
session = await refreshToken(session);
|
||||
if (session.accessTokenExpiresAt == null) {
|
||||
throw UnauthorizedException('Sitzung ist abgelaufen');
|
||||
}
|
||||
}
|
||||
try {
|
||||
final GetPersonResponse response =
|
||||
await _client.getPerson(GetPersonRequest(id: personId));
|
||||
return response.person;
|
||||
} on SocketException {
|
||||
throw FetchDataException('Keine Internet Verbindung');
|
||||
} on GrpcError catch (err) {
|
||||
throw FetchDataException(err.message);
|
||||
} catch (err) {
|
||||
throw InternalException(err.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<Person> createPerson(
|
||||
{required String firstname,
|
||||
required String lastname,
|
||||
required String street,
|
||||
required String zip,
|
||||
required String city,
|
||||
required String country,
|
||||
required DateTime birthday}) async {
|
||||
Session session = await Session.session;
|
||||
if (session.accessTokenExpiresAt == null) {
|
||||
throw UnauthorizedException('Keine Siztung gefunden');
|
||||
}
|
||||
if (session.accessTokenExpiresAt!.toDateTime().isBefore(DateTime.now())) {
|
||||
session = await refreshToken(session);
|
||||
if (session.accessTokenExpiresAt == null) {
|
||||
throw UnauthorizedException('Sitzung ist abgelaufen');
|
||||
}
|
||||
}
|
||||
try {
|
||||
final CreatePersonResponse response = await _client.createPerson(
|
||||
CreatePersonRequest(
|
||||
accountId: session.accountId,
|
||||
lastname: lastname,
|
||||
firstname: firstname,
|
||||
street: street,
|
||||
zip: zip,
|
||||
country: country,
|
||||
birthday: Timestamp.fromDateTime(birthday),
|
||||
),
|
||||
options: CallOptions(
|
||||
metadata: {'Authorization': 'Bearer ${session.accessToken}'}));
|
||||
return response.person;
|
||||
} on SocketException {
|
||||
throw FetchDataException('Keine Internet Verbindung');
|
||||
} on GrpcError catch (err) {
|
||||
throw FetchDataException(err.message);
|
||||
} catch (err) {
|
||||
throw InternalException(err.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<Person>> listPersons() async {
|
||||
Session session = await Session.session;
|
||||
if (session.accessTokenExpiresAt == null) {
|
||||
throw UnauthorizedException('Keine Siztung gefunden');
|
||||
}
|
||||
if (session.accessTokenExpiresAt!.toDateTime().isBefore(DateTime.now())) {
|
||||
session = await refreshToken(session);
|
||||
if (session.accessTokenExpiresAt == null) {
|
||||
throw UnauthorizedException('Sitzung ist abgelaufen');
|
||||
}
|
||||
}
|
||||
try {
|
||||
final ListPersonsResponse response = await _client.listPersons(
|
||||
ListPersonsRequest(
|
||||
accountId: session.accountId,
|
||||
),
|
||||
options: CallOptions(
|
||||
metadata: {'Authorization': 'Bearer ${session.accessToken}'}));
|
||||
return response.persons;
|
||||
} on SocketException {
|
||||
throw FetchDataException('Keine Internet Verbindung');
|
||||
} on GrpcError catch (err) {
|
||||
throw FetchDataException(err.message);
|
||||
} catch (err) {
|
||||
throw InternalException(err.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Future<List<Person>> listPersons() async {
|
||||
// if (_session.accessToken == null) {
|
||||
// refreshToken();
|
||||
// }
|
||||
// try {
|
||||
// ListPersonsResponse response =
|
||||
// await _client.listPersons(ListPersonsRequest(accountId: _session.accountId));
|
||||
// return response.persons;
|
||||
// } on SocketException {
|
||||
// throw FetchDataException('Keine Internet Verbindung');
|
||||
// } on GrpcError catch (err) {
|
||||
// throw FetchDataException(err.message);
|
||||
// } catch (err) {
|
||||
// throw InternalException(err.toString());
|
||||
// }
|
||||
// }
|
||||
static Future<bool> login(
|
||||
{required String email, required String password}) async {
|
||||
try {
|
||||
final LoginResponse response = await BackendService.client.login(
|
||||
LoginRequest(
|
||||
email: email,
|
||||
password: password,
|
||||
),
|
||||
);
|
||||
Session s = Session(
|
||||
accessToken: response.accessToken,
|
||||
sessionId: response.sessionId,
|
||||
accessTokenExpiresAt: response.accessTokenExpiresAt,
|
||||
refreshToken: response.refreshToken,
|
||||
refreshTokenExpiresAt: response.refreshTokenExpiresAt,
|
||||
accountId: response.accountId,
|
||||
);
|
||||
await Session.newSession(s);
|
||||
return response.accessToken != '';
|
||||
} on SocketException {
|
||||
throw FetchDataException('Keine Internet Verbindung');
|
||||
} on GrpcError catch (err) {
|
||||
throw FetchDataException(err.message);
|
||||
} catch (err) {
|
||||
throw InternalException(err.toString());
|
||||
}
|
||||
}
|
||||
|
||||
static Future<Session> refreshToken(Session session) async {
|
||||
try {
|
||||
final RefreshTokenResponse response = await BackendService.client
|
||||
.refreshToken(
|
||||
RefreshTokenRequest(refreshToken: session.refreshToken));
|
||||
session.accessToken = response.accessToken;
|
||||
session.accessTokenExpiresAt = response.accessTokenExpiresAt;
|
||||
session = await Session.updateToken(session);
|
||||
return session;
|
||||
} on SocketException {
|
||||
throw FetchDataException('Keine Internet Verbindung');
|
||||
} on GrpcError catch (err) {
|
||||
throw FetchDataException(err.message);
|
||||
} catch (err) {
|
||||
throw InternalException(err.toString());
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> logout() async {
|
||||
Session session = await Session.session;
|
||||
session.reset();
|
||||
}
|
||||
}
|
32
frontend/app/lib/model/view_model/account_vm.dart
Normal file
32
frontend/app/lib/model/view_model/account_vm.dart
Normal file
@ -0,0 +1,32 @@
|
||||
import 'package:app/model/apis/api_response.dart';
|
||||
import 'package:app/model/services/backend_service.dart';
|
||||
import 'package:app/model/view_model/base_vm.dart';
|
||||
import 'package:app/pb/account.pb.dart';
|
||||
|
||||
class AccountViewModel extends BaseViewModel {
|
||||
AccountViewModel() {
|
||||
_init();
|
||||
}
|
||||
ApiResponse _apiResponse = ApiResponse.initial('Keine Daten');
|
||||
|
||||
final BackendService _service = BackendService();
|
||||
Account? _account;
|
||||
|
||||
ApiResponse get response {
|
||||
return _apiResponse;
|
||||
}
|
||||
|
||||
Account? get account {
|
||||
return _account;
|
||||
}
|
||||
|
||||
void _init() async {
|
||||
super.init();
|
||||
// try {
|
||||
// _apiResponse = ApiResponse.completed(await _service.getAccount());
|
||||
// } catch (e) {
|
||||
// _apiResponse = ApiResponse.error(e.toString());
|
||||
// }
|
||||
// notifyListeners();
|
||||
}
|
||||
}
|
254
frontend/app/lib/model/view_model/base_vm.dart
Normal file
254
frontend/app/lib/model/view_model/base_vm.dart
Normal file
@ -0,0 +1,254 @@
|
||||
import 'package:app/model/apis/api_response.dart';
|
||||
import 'package:app/model/services/backend_service.dart';
|
||||
import 'package:app/pages/home_page.dart';
|
||||
import 'package:app/util/colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BaseViewModel with ChangeNotifier {
|
||||
BaseViewModel() {
|
||||
init();
|
||||
}
|
||||
ApiResponse _apiResponse = ApiResponse.initial('Keine Daten');
|
||||
|
||||
final BackendService _service = BackendService();
|
||||
|
||||
ApiResponse get response {
|
||||
return _apiResponse;
|
||||
}
|
||||
|
||||
void init() async {
|
||||
// if (await BackendService.isLoggedIn) {
|
||||
try {
|
||||
_apiResponse = ApiResponse.completed(await _service.getAccount());
|
||||
} catch (e) {
|
||||
_apiResponse = ApiResponse.error(e.toString());
|
||||
}
|
||||
notifyListeners();
|
||||
// }
|
||||
}
|
||||
|
||||
Future<bool> isLoggedIn(BuildContext context) async {
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
final navigator = Navigator.of(context);
|
||||
bool loggedIn = false;
|
||||
try {
|
||||
loggedIn = await BackendService.isLoggedIn;
|
||||
} catch (err) {
|
||||
if (err.toString().contains('session is blocked')) {
|
||||
_apiResponse = ApiResponse.error('Sitzung ist abgelaufen');
|
||||
navigator.pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (builder) => HomePage(
|
||||
loggedOut: true,
|
||||
)),
|
||||
(route) => false);
|
||||
messenger.showSnackBar(SnackBar(
|
||||
backgroundColor: CustomColors.error,
|
||||
content: const Text(
|
||||
'Sitzung ist abgelaufen',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
// action: SnackBarAction(
|
||||
// label: 'Details',
|
||||
// onPressed: () {
|
||||
// if (context.mounted) {
|
||||
// showDialog(
|
||||
// context: context,
|
||||
// builder: (context) => AlertDialog(
|
||||
// backgroundColor: Colors.black,
|
||||
// icon: Icon(
|
||||
// Icons.error,
|
||||
// color: CustomColors.error,
|
||||
// ),
|
||||
// content: Text(
|
||||
// err.toString(),
|
||||
// textAlign: TextAlign.center,
|
||||
// ),
|
||||
// ));
|
||||
// }
|
||||
// },
|
||||
// ),
|
||||
));
|
||||
}
|
||||
}
|
||||
return loggedIn;
|
||||
}
|
||||
|
||||
Future<void> getAccount(BuildContext context) async {
|
||||
_apiResponse = ApiResponse.loading('Lade Daten');
|
||||
notifyListeners();
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
try {
|
||||
_apiResponse = ApiResponse.completed(await _service.getAccount());
|
||||
} catch (e) {
|
||||
if (e.toString().contains('session is blocked')) {
|
||||
_apiResponse = ApiResponse.error('Sitzung ist abgelaufen');
|
||||
messenger.showSnackBar(SnackBar(
|
||||
backgroundColor: CustomColors.error,
|
||||
content: const Text(
|
||||
'Sitzung ist abgelaufen',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
action: SnackBarAction(
|
||||
label: 'Details',
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: Colors.black,
|
||||
icon: Icon(
|
||||
Icons.error,
|
||||
color: CustomColors.error,
|
||||
),
|
||||
content: Text(
|
||||
e.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
));
|
||||
},
|
||||
),
|
||||
));
|
||||
}
|
||||
messenger.showSnackBar(SnackBar(
|
||||
backgroundColor: CustomColors.error,
|
||||
content: const Text(
|
||||
'Sitzung ist abgelaufen',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
action: SnackBarAction(
|
||||
label: 'Details',
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: Colors.black,
|
||||
icon: Icon(
|
||||
Icons.error,
|
||||
color: CustomColors.error,
|
||||
),
|
||||
content: Text(
|
||||
e.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
));
|
||||
},
|
||||
),
|
||||
));
|
||||
_apiResponse = ApiResponse.error(e.toString());
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
_apiResponse = ApiResponse.loading('Logge aus');
|
||||
notifyListeners();
|
||||
try {
|
||||
await BackendService.logout();
|
||||
_apiResponse = ApiResponse.completed(true);
|
||||
} catch (e) {
|
||||
_apiResponse = ApiResponse.error(e.toString());
|
||||
}
|
||||
print(_apiResponse.message);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<bool> login(BuildContext context,
|
||||
{required String email, required String password}) async {
|
||||
bool resp = false;
|
||||
_apiResponse = ApiResponse.loading('Logge ein');
|
||||
notifyListeners();
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
try {
|
||||
resp = await BackendService.login(email: email, password: password);
|
||||
_apiResponse = ApiResponse.completed(resp);
|
||||
messenger.showSnackBar(SnackBar(
|
||||
backgroundColor: CustomColors.success,
|
||||
content: const Text(
|
||||
'Erfolgreich eingeloggt',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
));
|
||||
} catch (e) {
|
||||
messenger.showSnackBar(SnackBar(
|
||||
backgroundColor: CustomColors.error,
|
||||
content: const Text(
|
||||
'Login fehlgeschlagen',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
action: SnackBarAction(
|
||||
label: 'Details',
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: Colors.black,
|
||||
icon: Icon(
|
||||
Icons.error,
|
||||
color: CustomColors.error,
|
||||
),
|
||||
content: Text(
|
||||
e.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
));
|
||||
},
|
||||
),
|
||||
));
|
||||
_apiResponse = ApiResponse.error(e.toString());
|
||||
}
|
||||
print(_apiResponse.message);
|
||||
notifyListeners();
|
||||
return resp;
|
||||
}
|
||||
|
||||
Future<bool> createAccount(BuildContext context,
|
||||
{required String email, required String password}) async {
|
||||
bool resp = false;
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
|
||||
_apiResponse = ApiResponse.loading('Logge ein');
|
||||
notifyListeners();
|
||||
try {
|
||||
resp =
|
||||
await BackendService.createAccount(email: email, password: password);
|
||||
messenger.showSnackBar(SnackBar(
|
||||
backgroundColor: CustomColors.success,
|
||||
content: const Text(
|
||||
'Account angelegt',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
));
|
||||
_apiResponse = ApiResponse.completed(resp);
|
||||
} catch (e) {
|
||||
messenger.showSnackBar(SnackBar(
|
||||
backgroundColor: CustomColors.error,
|
||||
content: const Text(
|
||||
'Account anlegen fehlgeschlagen',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
action: SnackBarAction(
|
||||
label: 'Details',
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: Colors.black,
|
||||
icon: Icon(
|
||||
Icons.error,
|
||||
color: CustomColors.error,
|
||||
),
|
||||
content: Text(
|
||||
e.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
));
|
||||
},
|
||||
),
|
||||
));
|
||||
_apiResponse = ApiResponse.error(e.toString());
|
||||
}
|
||||
print(_apiResponse.message);
|
||||
notifyListeners();
|
||||
return resp;
|
||||
}
|
||||
}
|
57
frontend/app/lib/model/view_model/persons_vm.dart
Normal file
57
frontend/app/lib/model/view_model/persons_vm.dart
Normal file
@ -0,0 +1,57 @@
|
||||
import 'package:app/model/apis/api_response.dart';
|
||||
import 'package:app/model/services/backend_service.dart';
|
||||
import 'package:app/pb/person.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PersonsViewModel with ChangeNotifier {
|
||||
PersonsViewModel() {
|
||||
listPersons();
|
||||
}
|
||||
ApiResponse _apiResponse = ApiResponse.initial('Keine Daten');
|
||||
|
||||
final BackendService _service = BackendService();
|
||||
|
||||
ApiResponse get response {
|
||||
return _apiResponse;
|
||||
}
|
||||
|
||||
Future<List<Person>> listPersons() async {
|
||||
List<Person> persons = [];
|
||||
_apiResponse = ApiResponse.loading('Lade Daten');
|
||||
try {
|
||||
persons = await _service.listPersons();
|
||||
_apiResponse = ApiResponse.completed(persons);
|
||||
} catch (e) {
|
||||
_apiResponse = ApiResponse.error(e.toString());
|
||||
}
|
||||
notifyListeners();
|
||||
return persons;
|
||||
}
|
||||
|
||||
Future<Person> createPerson(BuildContext context,
|
||||
{required String firstname,
|
||||
required String lastname,
|
||||
required String street,
|
||||
required String zip,
|
||||
required String city,
|
||||
required String country,
|
||||
required DateTime birthday}) async {
|
||||
Person person = Person();
|
||||
_apiResponse = ApiResponse.loading('Erstelle Person');
|
||||
try {
|
||||
person = await _service.createPerson(
|
||||
firstname: firstname,
|
||||
lastname: lastname,
|
||||
street: street,
|
||||
zip: zip,
|
||||
city: city,
|
||||
country: country,
|
||||
birthday: birthday);
|
||||
_apiResponse = ApiResponse.completed(person);
|
||||
} catch (err) {
|
||||
_apiResponse = ApiResponse.error(err.toString());
|
||||
}
|
||||
notifyListeners();
|
||||
return person;
|
||||
}
|
||||
}
|
@ -1,302 +0,0 @@
|
||||
import 'package:app/gapi/client.dart';
|
||||
import 'package:app/pages/start_page.dart';
|
||||
import 'package:app/pb/account_info.pb.dart';
|
||||
import 'package:app/pb/rpc_get_account_info.pb.dart';
|
||||
import 'package:app/widgets/background.dart';
|
||||
import 'package:app/widgets/bottom_bar.dart';
|
||||
import 'package:app/widgets/loading_widget.dart';
|
||||
import 'package:app/widgets/side_drawer.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DashboardPage extends StatefulWidget {
|
||||
const DashboardPage({
|
||||
super.key,
|
||||
required this.client,
|
||||
});
|
||||
|
||||
final GClient client;
|
||||
|
||||
@override
|
||||
State<DashboardPage> createState() => _DashboardPageState();
|
||||
}
|
||||
|
||||
class _DashboardPageState extends State<DashboardPage> {
|
||||
bool _loading = false;
|
||||
late AccountInfo accountInfo;
|
||||
|
||||
void _setLoading(bool loading) {
|
||||
setState(() {
|
||||
_loading = loading;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_setLoading(true);
|
||||
widget.client.getAccountInfo(
|
||||
GetAccountInfoRequest(
|
||||
accountId: widget.client.session.accountId,
|
||||
),
|
||||
onError: ({String? msg}) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: const Text('AccountInfo konnte nicht geladen werden'),
|
||||
action: msg != null
|
||||
? SnackBarAction(
|
||||
label: 'Details',
|
||||
textColor: Colors.grey,
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
content: Text(
|
||||
msg,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: Colors.black),
|
||||
),
|
||||
icon: const Icon(
|
||||
Icons.warning,
|
||||
color: Colors.red,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
})
|
||||
: null,
|
||||
),
|
||||
);
|
||||
},
|
||||
).then((value) {
|
||||
accountInfo = value.accountInfo;
|
||||
_setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
flexibleSpace: Image.asset(
|
||||
'lib/assets/logo_300x200.png',
|
||||
height: 80,
|
||||
),
|
||||
),
|
||||
drawer: Builder(builder: (context) {
|
||||
return SideDrawer(
|
||||
children: [
|
||||
const Spacer(),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Scaffold.of(context).closeDrawer();
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Text(
|
||||
'About',
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Icons.question_answer,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Scaffold.of(context).closeDrawer();
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Text(
|
||||
'Datenschutz',
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Icons.privacy_tip,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Scaffold.of(context).closeDrawer();
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Text(
|
||||
'Impressum',
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Icons.apartment,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (widget.client.session.accessToken != null)
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
widget.client.session.accessToken = null;
|
||||
widget.client.session
|
||||
.removeSession(widget.client.session.sessionId!);
|
||||
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
StartPage(client: widget.client),
|
||||
),
|
||||
(route) => false);
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Text(
|
||||
'Log out',
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Icons.logout,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 250,
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
bottomNavigationBar: Builder(
|
||||
builder: (context) {
|
||||
return BottomBar(
|
||||
children: widget.client.session.accessToken != null
|
||||
? [
|
||||
BottomNavigationBarItem(
|
||||
backgroundColor: Colors.white,
|
||||
label: 'Personen',
|
||||
icon: Column(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () =>
|
||||
Scaffold.of(context).openDrawer(),
|
||||
icon: const Icon(
|
||||
Icons.group,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const Text(
|
||||
'Personen',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
backgroundColor: Colors.white,
|
||||
label: 'Home',
|
||||
icon: Column(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => StartPage(
|
||||
client: widget.client,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.home,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const Text(
|
||||
'Home',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
backgroundColor: Colors.white,
|
||||
label: 'Menu',
|
||||
icon: IconButton(
|
||||
onPressed: () {
|
||||
Scaffold.of(context).openDrawer();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.menu,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
)
|
||||
]
|
||||
: [
|
||||
BottomNavigationBarItem(
|
||||
label: 'back',
|
||||
backgroundColor: Colors.white,
|
||||
icon: IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(
|
||||
Icons.arrow_back,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
backgroundColor: Colors.white,
|
||||
label: 'Menu',
|
||||
icon: IconButton(
|
||||
onPressed: () => Scaffold.of(context).openDrawer(),
|
||||
icon: const Icon(
|
||||
Icons.menu,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
body: !_loading
|
||||
? Background(
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 48,
|
||||
),
|
||||
Text(
|
||||
'Willkommen ${accountInfo.firstname} ${accountInfo.lastname}!',
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: const LoadingWidget(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
218
frontend/app/lib/pages/home_page.dart
Normal file
218
frontend/app/lib/pages/home_page.dart
Normal file
@ -0,0 +1,218 @@
|
||||
import 'package:app/model/services/backend_service.dart';
|
||||
import 'package:app/model/view_model/account_vm.dart';
|
||||
import 'package:app/pages/login_overlay.dart';
|
||||
import 'package:app/pages/persons_page.dart';
|
||||
import 'package:app/widgets/background.dart';
|
||||
import 'package:app/widgets/bottom_navigation.dart';
|
||||
import 'package:app/widgets/bottom_navigation_item.dart';
|
||||
import 'package:app/widgets/drawer.dart';
|
||||
import 'package:app/widgets/side_drawer_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class HomePage extends StatefulWidget {
|
||||
HomePage({super.key, required this.loggedOut});
|
||||
|
||||
bool loggedOut;
|
||||
|
||||
@override
|
||||
State<HomePage> createState() => _HomePageState();
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_init();
|
||||
}
|
||||
|
||||
AccountViewModel vm = AccountViewModel();
|
||||
void _init() async {
|
||||
// _setLoading(true);
|
||||
// _setLoading(widget.loggedOut);
|
||||
// _loading = widget.loggedOut;
|
||||
// _loggedin = await BackendService.isLoggedIn;
|
||||
// if (!_loggedin) {
|
||||
// await BackendService.logout();
|
||||
// final navigator = Navigator.of(context);
|
||||
// navigator.pushAndRemoveUntil(
|
||||
// MaterialPageRoute(
|
||||
// builder: (builder) => HomePage(
|
||||
// loggedOut: true,
|
||||
// )),
|
||||
// (route) => false);
|
||||
// }
|
||||
_setLoading(false);
|
||||
}
|
||||
|
||||
_isLoggedIn(BuildContext context) async {
|
||||
bool logged = await vm.isLoggedIn(context);
|
||||
_loggedin = logged;
|
||||
}
|
||||
|
||||
void _setLoading(bool loading) {
|
||||
setState(() {
|
||||
_loading = loading;
|
||||
});
|
||||
}
|
||||
|
||||
bool _loading = true;
|
||||
bool _loggedin = false;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
// flexibleSpace: Image.asset(
|
||||
// 'lib/assets/logo_300x200.png',
|
||||
// // height: 400,
|
||||
// ),
|
||||
),
|
||||
drawer: SideDrawer(
|
||||
children: [
|
||||
const Spacer(
|
||||
flex: 3,
|
||||
),
|
||||
SideDrawerItem(
|
||||
onPressed: () {},
|
||||
icon: Icons.question_answer,
|
||||
color: Colors.white,
|
||||
label: 'About',
|
||||
),
|
||||
SideDrawerItem(
|
||||
onPressed: () {},
|
||||
icon: Icons.privacy_tip,
|
||||
color: Colors.white,
|
||||
label: 'Datenschutz',
|
||||
),
|
||||
SideDrawerItem(
|
||||
onPressed: () {},
|
||||
icon: Icons.apartment,
|
||||
color: Colors.white,
|
||||
label: 'Impressum',
|
||||
),
|
||||
const Spacer(
|
||||
flex: 1,
|
||||
),
|
||||
if (_loggedin)
|
||||
SideDrawerItem(
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
_loading = true;
|
||||
});
|
||||
await BackendService.logout();
|
||||
// ignore: use_build_context_synchronously
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (builder) => HomePage(
|
||||
loggedOut: true,
|
||||
)),
|
||||
(route) => false);
|
||||
setState(() {
|
||||
_loggedin = false;
|
||||
_loading = false;
|
||||
});
|
||||
},
|
||||
icon: Icons.logout,
|
||||
color: Colors.white,
|
||||
label: 'Logout',
|
||||
),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: BottomNavigation(
|
||||
children: [
|
||||
if (!_loggedin) ...[
|
||||
BottomNavigationItem(
|
||||
onPressed: () async {
|
||||
final bool res = await showLogin(context, registration: true);
|
||||
setState(() {
|
||||
_loggedin = res;
|
||||
});
|
||||
},
|
||||
icon: Icons.person_add_alt,
|
||||
color: Colors.white,
|
||||
label: 'Registrieren',
|
||||
),
|
||||
BottomNavigationItem(
|
||||
onPressed: () async {
|
||||
await showLogin(context);
|
||||
setState(() {
|
||||
vm.isLoggedIn(context);
|
||||
});
|
||||
},
|
||||
icon: Icons.login,
|
||||
color: Colors.white,
|
||||
label: 'Login',
|
||||
),
|
||||
] else
|
||||
BottomNavigationItem(
|
||||
onPressed: () async {
|
||||
final navigator = Navigator.of(context);
|
||||
if (await vm.isLoggedIn(context)) {
|
||||
navigator.push(MaterialPageRoute(
|
||||
builder: (builder) => const PersonsPage()));
|
||||
} else {
|
||||
navigator.pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (builder) => const PersonsPage()),
|
||||
(route) => false);
|
||||
}
|
||||
},
|
||||
icon: Icons.person_search,
|
||||
color: Colors.white,
|
||||
label: 'Personen',
|
||||
),
|
||||
BottomNavigationItem(
|
||||
onPressed: () {},
|
||||
icon: Icons.dashboard,
|
||||
color: Colors.white,
|
||||
label: 'Dashboard',
|
||||
),
|
||||
...[]
|
||||
],
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 45, 16, 16),
|
||||
child: Center(
|
||||
child: ChangeNotifierProvider<AccountViewModel>(
|
||||
create: (context) => AccountViewModel(),
|
||||
child:
|
||||
Consumer<AccountViewModel>(builder: (context, value, child) {
|
||||
// _checkResponse(value.response);
|
||||
if (!widget.loggedOut) {
|
||||
_isLoggedIn(context);
|
||||
}
|
||||
return _loading
|
||||
? const CircularProgressIndicator(
|
||||
color: Colors.grey,
|
||||
)
|
||||
: Column(
|
||||
children: [
|
||||
Image.asset(
|
||||
'lib/assets/logo_300x200.png',
|
||||
),
|
||||
const SizedBox(
|
||||
height: 40,
|
||||
),
|
||||
Text(
|
||||
'Digitale Spuren auf Knopfdruck entfernen'
|
||||
.toUpperCase(),
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'sans-serif',
|
||||
fontSize: 24,
|
||||
height: 1.6,
|
||||
fontWeight: FontWeight.normal,
|
||||
letterSpacing: 6,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
})),
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
239
frontend/app/lib/pages/login_overlay.dart
Normal file
239
frontend/app/lib/pages/login_overlay.dart
Normal file
@ -0,0 +1,239 @@
|
||||
import 'package:app/model/view_model/base_vm.dart';
|
||||
import 'package:app/widgets/background.dart';
|
||||
import 'package:app/widgets/bottom_navigation.dart';
|
||||
import 'package:app/widgets/bottom_navigation_item.dart';
|
||||
import 'package:app/widgets/side_drawer.dart';
|
||||
import 'package:app/widgets/side_drawer_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:app/util/validation.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
Future<bool> showLogin(BuildContext context,
|
||||
{bool registration = false}) async {
|
||||
final formKey = GlobalKey<FormState>();
|
||||
final mailController = TextEditingController();
|
||||
final passwordController = TextEditingController();
|
||||
|
||||
BaseViewModel vm = BaseViewModel();
|
||||
bool submitted = false;
|
||||
bool loggedin = false;
|
||||
void login(BuildContext context) {
|
||||
if (formKey.currentState!.validate()) {
|
||||
submitted = true;
|
||||
FocusScope.of(context).unfocus();
|
||||
vm
|
||||
.login(context,
|
||||
email: mailController.text, password: passwordController.text)
|
||||
.then(
|
||||
(r) {
|
||||
if (r) {
|
||||
loggedin = r;
|
||||
Navigator.pop(context, true);
|
||||
}
|
||||
},
|
||||
);
|
||||
passwordController.clear();
|
||||
submitted = false;
|
||||
}
|
||||
}
|
||||
|
||||
void register(BuildContext context) {
|
||||
if (formKey.currentState!.validate()) {
|
||||
submitted = true;
|
||||
vm
|
||||
.createAccount(
|
||||
context,
|
||||
email: mailController.text,
|
||||
password: passwordController.text,
|
||||
)
|
||||
.then(
|
||||
(r) {
|
||||
if (r) {
|
||||
loggedin = r;
|
||||
Navigator.pop(context, true);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await showModalBottomSheet(
|
||||
useSafeArea: true,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.black,
|
||||
context: context,
|
||||
builder: (builder) {
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
drawer: SideDrawer(
|
||||
children: [
|
||||
const Spacer(
|
||||
flex: 3,
|
||||
),
|
||||
SideDrawerItem(
|
||||
onPressed: () {},
|
||||
icon: Icons.question_answer,
|
||||
color: Colors.white,
|
||||
label: 'About',
|
||||
),
|
||||
SideDrawerItem(
|
||||
onPressed: () {},
|
||||
icon: Icons.privacy_tip,
|
||||
color: Colors.white,
|
||||
label: 'Datenschutz',
|
||||
),
|
||||
SideDrawerItem(
|
||||
onPressed: () {},
|
||||
icon: Icons.apartment,
|
||||
color: Colors.white,
|
||||
label: 'Impressum',
|
||||
),
|
||||
const Spacer(
|
||||
flex: 1,
|
||||
),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: BottomNavigation(
|
||||
children: [
|
||||
BottomNavigationItem(
|
||||
onPressed: () {
|
||||
Navigator.pop(context, false);
|
||||
},
|
||||
icon: Icons.arrow_back,
|
||||
color: Colors.white,
|
||||
label: 'Zurück',
|
||||
),
|
||||
BottomNavigationItem(
|
||||
onPressed: () {
|
||||
Navigator.pop(context, false);
|
||||
},
|
||||
icon: Icons.home,
|
||||
color: Colors.white,
|
||||
label: 'Home',
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 50,
|
||||
),
|
||||
const Image(
|
||||
width: 180,
|
||||
image: AssetImage(
|
||||
'lib/assets/logo_300x200.png',
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 30,
|
||||
),
|
||||
Text(
|
||||
registration ? 'Registrieren' : 'Login',
|
||||
style: const TextStyle(
|
||||
fontFamily: 'sans-serif',
|
||||
fontSize: 24,
|
||||
height: 1.6,
|
||||
fontWeight: FontWeight.normal,
|
||||
letterSpacing: 6,
|
||||
),
|
||||
),
|
||||
ChangeNotifierProvider<BaseViewModel>(
|
||||
create: (context) => BaseViewModel(),
|
||||
child: Consumer<BaseViewModel>(
|
||||
builder: (context, value, child) => Form(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 40,
|
||||
),
|
||||
TextFormField(
|
||||
// autofocus: true,
|
||||
// inputFormatters: [
|
||||
// FilteringTextInputFormatter.allow(
|
||||
// emailRegExp,
|
||||
// ),
|
||||
// ],
|
||||
controller: mailController,
|
||||
decoration: const InputDecoration(
|
||||
fillColor: Color.fromARGB(30, 255, 255, 255),
|
||||
filled: true,
|
||||
hintStyle: TextStyle(
|
||||
color: Colors.white38,
|
||||
),
|
||||
hintText: 'E-Mail Adresse',
|
||||
),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
validator: (value) {
|
||||
if (value == null || !value.isValidEmail) {
|
||||
return 'Bitte eine gültige E-Mail Adresse eingeben';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
TextFormField(
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
// inputFormatters: [
|
||||
// FilteringTextInputFormatter.allow(
|
||||
// passwordRegExp,
|
||||
// ),
|
||||
// ],
|
||||
controller: passwordController,
|
||||
decoration: const InputDecoration(
|
||||
fillColor: Color.fromARGB(30, 255, 255, 255),
|
||||
filled: true,
|
||||
hintStyle: TextStyle(
|
||||
color: Colors.white38,
|
||||
),
|
||||
hintText: 'Passwort',
|
||||
),
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
obscureText: true,
|
||||
validator: (value) {
|
||||
if (value == null || !value.isValidPassword) {
|
||||
return 'Bitte geben Sie Ihr Passwort ein';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: !submitted
|
||||
? !registration
|
||||
? () {
|
||||
login(context);
|
||||
}
|
||||
: () {
|
||||
register(context);
|
||||
}
|
||||
: null,
|
||||
child: const Icon(Icons.login),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
return loggedin;
|
||||
}
|
@ -1 +0,0 @@
|
||||
enum Pages { start, login, about, persons, dashboard }
|
184
frontend/app/lib/pages/persons_page.dart
Normal file
184
frontend/app/lib/pages/persons_page.dart
Normal file
@ -0,0 +1,184 @@
|
||||
import 'package:app/model/apis/api_response.dart';
|
||||
import 'package:app/model/services/backend_service.dart';
|
||||
import 'package:app/model/view_model/persons_vm.dart';
|
||||
import 'package:app/pages/home_page.dart';
|
||||
import 'package:app/pb/person.pb.dart';
|
||||
import 'package:app/widgets/background.dart';
|
||||
import 'package:app/widgets/bottom_navigation.dart';
|
||||
import 'package:app/widgets/bottom_navigation_item.dart';
|
||||
import 'package:app/widgets/side_drawer.dart';
|
||||
import 'package:app/widgets/side_drawer_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class PersonsPage extends StatefulWidget {
|
||||
const PersonsPage({super.key});
|
||||
|
||||
@override
|
||||
State<PersonsPage> createState() => _PersonsPageState();
|
||||
}
|
||||
|
||||
class _PersonsPageState extends State<PersonsPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_init();
|
||||
}
|
||||
|
||||
void _init() async {
|
||||
_setLoading(true);
|
||||
_loggedin = await BackendService.isLoggedIn;
|
||||
_setLoading(false);
|
||||
}
|
||||
|
||||
void _setLoading(bool loading) {
|
||||
setState(() {
|
||||
_loading = loading;
|
||||
});
|
||||
}
|
||||
|
||||
void _checkResponse(ApiResponse response) {
|
||||
if (response.status == Status.ERROR &&
|
||||
response.message!.contains('unauthenticated')) {
|
||||
BackendService.logout();
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (builder) => HomePage(
|
||||
loggedOut: true,
|
||||
)),
|
||||
(route) => false);
|
||||
}
|
||||
}
|
||||
|
||||
bool _loading = true;
|
||||
bool _loggedin = false;
|
||||
List<Person> persons = [];
|
||||
|
||||
PersonsViewModel vm = PersonsViewModel();
|
||||
|
||||
void listPersons(BuildContext context) async {
|
||||
persons = await vm.listPersons();
|
||||
}
|
||||
|
||||
List<Widget> _personsList(List<Person> persons) {
|
||||
final List<Widget> list = [];
|
||||
for (var p in persons) {
|
||||
list.add(Card(
|
||||
color: Colors.black,
|
||||
child: Text(
|
||||
'${p.firstname} ${p.lastname}',
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () {},
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
drawer: SideDrawer(
|
||||
children: [
|
||||
const Spacer(
|
||||
flex: 3,
|
||||
),
|
||||
SideDrawerItem(
|
||||
onPressed: () {},
|
||||
icon: Icons.question_answer,
|
||||
color: Colors.white,
|
||||
label: 'About',
|
||||
),
|
||||
SideDrawerItem(
|
||||
onPressed: () {},
|
||||
icon: Icons.privacy_tip,
|
||||
color: Colors.white,
|
||||
label: 'Datenschutz',
|
||||
),
|
||||
SideDrawerItem(
|
||||
onPressed: () {},
|
||||
icon: Icons.apartment,
|
||||
color: Colors.white,
|
||||
label: 'Impressum',
|
||||
),
|
||||
const Spacer(
|
||||
flex: 1,
|
||||
),
|
||||
if (_loggedin)
|
||||
SideDrawerItem(
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
_loading = true;
|
||||
});
|
||||
await BackendService.logout();
|
||||
// ignore: use_build_context_synchronously
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (builder) => HomePage(
|
||||
loggedOut: true,
|
||||
)),
|
||||
(route) => false);
|
||||
setState(() {
|
||||
_loggedin = false;
|
||||
_loading = false;
|
||||
});
|
||||
},
|
||||
icon: Icons.logout,
|
||||
color: Colors.white,
|
||||
label: 'Logout',
|
||||
),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: BottomNavigation(
|
||||
children: [
|
||||
BottomNavigationItem(
|
||||
onPressed: () {},
|
||||
icon: Icons.dashboard,
|
||||
color: Colors.white,
|
||||
label: 'Dashboard',
|
||||
),
|
||||
BottomNavigationItem(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
icon: Icons.home,
|
||||
color: Colors.white,
|
||||
label: 'Home',
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Center(
|
||||
child: ChangeNotifierProvider<PersonsViewModel>(
|
||||
create: (context) => PersonsViewModel(),
|
||||
child: Consumer<PersonsViewModel>(
|
||||
builder: (context, value, child) {
|
||||
_checkResponse(value.response);
|
||||
listPersons(context);
|
||||
return _loading
|
||||
? const CircularProgressIndicator(
|
||||
color: Colors.grey,
|
||||
)
|
||||
: value.response.status == Status.COMPLETED
|
||||
? value.response.data.length > 0
|
||||
? ListView(children: _personsList(persons))
|
||||
: const Text('Noch keine Personen angelegt')
|
||||
: const Text('Lade Daten...');
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,360 +0,0 @@
|
||||
import 'package:app/gapi/client.dart';
|
||||
import 'package:app/pages/start_page.dart';
|
||||
import 'package:app/widgets/background.dart';
|
||||
import 'package:app/widgets/bottom_bar.dart';
|
||||
import 'package:app/widgets/loading_widget.dart';
|
||||
import 'package:app/widgets/side_drawer.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:grpc/grpc.dart';
|
||||
|
||||
class RegisterPage extends StatefulWidget {
|
||||
const RegisterPage({
|
||||
super.key,
|
||||
required this.client,
|
||||
});
|
||||
|
||||
final GClient client;
|
||||
|
||||
@override
|
||||
State<RegisterPage> createState() => _RegisterPageState();
|
||||
}
|
||||
|
||||
class _RegisterPageState extends State<RegisterPage> {
|
||||
bool _loading = false;
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final mailController = TextEditingController();
|
||||
final passwordController = TextEditingController();
|
||||
|
||||
void _setLoading(bool loading) {
|
||||
setState(() {
|
||||
_loading = loading;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
flexibleSpace: Image.asset(
|
||||
'lib/assets/logo_300x200.png',
|
||||
height: 80,
|
||||
),
|
||||
),
|
||||
drawer: Builder(builder: (context) {
|
||||
return SideDrawer(
|
||||
children: [
|
||||
const Spacer(),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Scaffold.of(context).closeDrawer();
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Text(
|
||||
'About',
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Icons.question_answer,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Scaffold.of(context).closeDrawer();
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Text(
|
||||
'Datenschutz',
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Icons.privacy_tip,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Scaffold.of(context).closeDrawer();
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Text(
|
||||
'Impressum',
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Icons.apartment,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 250,
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
bottomNavigationBar: BottomBar(
|
||||
children: [
|
||||
BottomNavigationBarItem(
|
||||
label: 'back',
|
||||
backgroundColor: Colors.white,
|
||||
icon: IconButton(
|
||||
onPressed: () => Navigator.of(context).pop(widget.client),
|
||||
icon: const Icon(
|
||||
Icons.arrow_back,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
backgroundColor: Colors.white,
|
||||
label: 'Menu',
|
||||
icon: IconButton(
|
||||
onPressed: () => Scaffold.of(context).openDrawer(),
|
||||
icon: const Icon(
|
||||
Icons.menu,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: !_loading
|
||||
? Form(
|
||||
key: _formKey,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 100, 16, 16),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Image(
|
||||
width: 180,
|
||||
image: AssetImage(
|
||||
'lib/assets/logo_300x200.png',
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 30,
|
||||
),
|
||||
const Text(
|
||||
'Registrieren',
|
||||
style: TextStyle(
|
||||
fontFamily: 'sans-serif',
|
||||
fontSize: 24,
|
||||
height: 1.6,
|
||||
fontWeight: FontWeight.normal,
|
||||
letterSpacing: 6,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
TextFormField(
|
||||
controller: mailController,
|
||||
decoration: const InputDecoration(
|
||||
fillColor: Color.fromARGB(30, 255, 255, 255),
|
||||
filled: true,
|
||||
hintStyle: TextStyle(
|
||||
color: Colors.white38,
|
||||
),
|
||||
hintText: 'E-Mail Adresse',
|
||||
),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Bitte eine gültige E-Mail Adresse eingeben';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
TextFormField(
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
controller: passwordController,
|
||||
decoration: const InputDecoration(
|
||||
fillColor: Color.fromARGB(30, 255, 255, 255),
|
||||
filled: true,
|
||||
hintStyle: TextStyle(
|
||||
color: Colors.white38,
|
||||
),
|
||||
hintText: 'Passwort',
|
||||
),
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
obscureText: true,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Bitte geben Sie Ihr Passwort ein';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
_setLoading(true);
|
||||
widget.client
|
||||
.createAccount(
|
||||
email: mailController.text,
|
||||
password: passwordController.text,
|
||||
onError: ({GrpcError? error}) {
|
||||
_setLoading(false);
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(SnackBar(
|
||||
content: const Text(
|
||||
'Login fehlgeschlagen',
|
||||
),
|
||||
action: SnackBarAction(
|
||||
textColor: Colors.grey,
|
||||
label: 'Details',
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
content: error != null
|
||||
? Text(
|
||||
'Fehler: ${error.message}',
|
||||
textAlign:
|
||||
TextAlign
|
||||
.center,
|
||||
style: const TextStyle(
|
||||
color: Colors
|
||||
.black),
|
||||
)
|
||||
: const Text(
|
||||
'Interner Fehler',
|
||||
textAlign:
|
||||
TextAlign
|
||||
.center,
|
||||
style: TextStyle(
|
||||
color: Colors
|
||||
.black),
|
||||
),
|
||||
icon: const Icon(
|
||||
Icons.error,
|
||||
color: Colors.black,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
));
|
||||
})
|
||||
.then(
|
||||
(r) {
|
||||
if (r.account.secretKey != '') {
|
||||
widget.client
|
||||
.login(
|
||||
email: mailController.text,
|
||||
password:
|
||||
passwordController.text,
|
||||
onError: (
|
||||
{GrpcError? error}) {},
|
||||
onSuccess: () {})
|
||||
.then((resp) {
|
||||
widget.client.getAccount(
|
||||
accountId: r.account.id,
|
||||
onError: ({GrpcError? err}) {
|
||||
_setLoading(false);
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(
|
||||
SnackBar(
|
||||
content: const Text(
|
||||
'Login fehlgeschlagen',
|
||||
),
|
||||
action: SnackBarAction(
|
||||
textColor: Colors.grey,
|
||||
label: 'Details',
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
content: err !=
|
||||
null
|
||||
? Text(
|
||||
'Hoppla! Da ist etwas schief gelaufen..\n\n${err.message}',
|
||||
textAlign:
|
||||
TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color:
|
||||
Colors.black),
|
||||
)
|
||||
: const Text(
|
||||
'Interner Fehler',
|
||||
textAlign:
|
||||
TextAlign.center,
|
||||
style: TextStyle(
|
||||
color:
|
||||
Colors.black),
|
||||
),
|
||||
icon:
|
||||
const Icon(
|
||||
Icons.error,
|
||||
color: Colors
|
||||
.black,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
Navigator.pushAndRemoveUntil(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (builder) => StartPage(
|
||||
client: widget.client)),
|
||||
(route) => false);
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (builder) {
|
||||
return const AlertDialog(
|
||||
content: Text(
|
||||
'Account wurde angelegt',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.black),
|
||||
),
|
||||
icon: Icon(
|
||||
Icons.done,
|
||||
size: 50,
|
||||
),
|
||||
iconColor: Colors.green,
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const Icon(Icons.login))
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const LoadingWidget()),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,344 +0,0 @@
|
||||
import 'package:app/gapi/client.dart';
|
||||
import 'package:app/pages/dashboard_page.dart';
|
||||
import 'package:app/pages/login_page.dart';
|
||||
import 'package:app/pages/register_page.dart';
|
||||
import 'package:app/widgets/background.dart';
|
||||
import 'package:app/widgets/bottom_bar.dart';
|
||||
import 'package:app/widgets/side_drawer.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:core';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class StartPage extends StatefulWidget {
|
||||
StartPage({
|
||||
super.key,
|
||||
required this.client,
|
||||
});
|
||||
|
||||
GClient client;
|
||||
|
||||
@override
|
||||
State<StartPage> createState() => _StartPageState();
|
||||
}
|
||||
|
||||
class _StartPageState extends State<StartPage> {
|
||||
final List<BottomNavigationBarItem> bottombarButtons = [];
|
||||
|
||||
void _updateClient(GClient c) {
|
||||
setState(() {
|
||||
widget.client = c;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
drawer: Builder(builder: (context) {
|
||||
return SideDrawer(children: [
|
||||
const Spacer(),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Scaffold.of(context).closeDrawer();
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Text(
|
||||
'About',
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Icons.question_answer,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Scaffold.of(context).closeDrawer();
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Text(
|
||||
'Datenschutz',
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Icons.privacy_tip,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Scaffold.of(context).closeDrawer();
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Text(
|
||||
'Impressum',
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Icons.apartment,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
widget.client.session.accessToken = null;
|
||||
widget.client.session
|
||||
.removeSession(widget.client.session.sessionId!);
|
||||
});
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Text(
|
||||
'Log out',
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Icons.logout,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 250,
|
||||
)
|
||||
]);
|
||||
}),
|
||||
bottomNavigationBar: Builder(builder: (context) {
|
||||
return BottomBar(
|
||||
// onTap: (value) => _bottomBarAction(value),
|
||||
children: widget.client.session.accessToken != null
|
||||
? [
|
||||
BottomNavigationBarItem(
|
||||
backgroundColor: Colors.white,
|
||||
label: 'Personen',
|
||||
icon: Column(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => Scaffold.of(context).openDrawer(),
|
||||
icon: const Icon(
|
||||
Icons.group,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const Text(
|
||||
'Personen',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
backgroundColor: Colors.white,
|
||||
label: 'Dashboard',
|
||||
icon: Column(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
if (widget.client.session.accessTokenExpiresAt!
|
||||
.toDateTime()
|
||||
.isBefore(DateTime.now())) {
|
||||
await widget.client.refreshToken();
|
||||
}
|
||||
if (!widget.client.isLoggedIn &&
|
||||
context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Sitzung ist abgelaufen.'),
|
||||
),
|
||||
);
|
||||
if (context.mounted) {
|
||||
Navigator.pushAndRemoveUntil(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
LoginPage(client: widget.client)),
|
||||
(route) => false);
|
||||
}
|
||||
} else {
|
||||
final ctx = context;
|
||||
// ignore: use_build_context_synchronously
|
||||
GClient c = await Navigator.push(
|
||||
ctx,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => DashboardPage(
|
||||
client: widget.client,
|
||||
),
|
||||
),
|
||||
);
|
||||
_updateClient(c);
|
||||
}
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.dashboard,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const Text(
|
||||
'Dashboard',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
backgroundColor: Colors.white,
|
||||
label: 'Menu',
|
||||
icon: IconButton(
|
||||
onPressed: () {
|
||||
Scaffold.of(context).openDrawer();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.menu,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
)
|
||||
]
|
||||
: [
|
||||
BottomNavigationBarItem(
|
||||
label: 'register',
|
||||
backgroundColor: Colors.white,
|
||||
icon: Column(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
widget.client = await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => RegisterPage(
|
||||
client: widget.client,
|
||||
)));
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.login,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const Text(
|
||||
'Registrieren',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
label: 'login',
|
||||
backgroundColor: Colors.white,
|
||||
icon: Column(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
final c = await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => LoginPage(
|
||||
client: widget.client,
|
||||
),
|
||||
));
|
||||
_updateClient(c);
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.login,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const Text(
|
||||
'Login',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
backgroundColor: Colors.white,
|
||||
label: 'Menu',
|
||||
icon: IconButton(
|
||||
onPressed: () => Scaffold.of(context).openDrawer(),
|
||||
icon: const Icon(
|
||||
Icons.menu,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Image(
|
||||
image: AssetImage(
|
||||
'lib/assets/logo_300x200.png',
|
||||
),
|
||||
),
|
||||
if (widget.client.account != null &&
|
||||
!widget.client.account!.emailVerified)
|
||||
Container(
|
||||
height: 120,
|
||||
width: double.infinity,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 20, horizontal: 10),
|
||||
child: Card(
|
||||
color: Colors.brown.shade300,
|
||||
child: const Text(
|
||||
'Deine E-Mail Adresse ist noch nicht validiert.'),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 40,
|
||||
),
|
||||
Text(
|
||||
'Digitale Spuren auf Knopfdruck entfernen'.toUpperCase(),
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'sans-serif',
|
||||
fontSize: 24,
|
||||
height: 1.6,
|
||||
fontWeight: FontWeight.normal,
|
||||
letterSpacing: 6,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
302
frontend/app/lib/pages_old/dashboard_page.dart
Normal file
302
frontend/app/lib/pages_old/dashboard_page.dart
Normal file
@ -0,0 +1,302 @@
|
||||
import 'package:app/gapi/client.dart';
|
||||
import 'package:app/pages_old/start_page.dart';
|
||||
import 'package:app/pb/account_info.pb.dart';
|
||||
import 'package:app/pb/rpc_get_account_info.pb.dart';
|
||||
import 'package:app/widgets/background.dart';
|
||||
import 'package:app/widgets/bottom_bar.dart';
|
||||
import 'package:app/widgets/loading_widget.dart';
|
||||
import 'package:app/widgets/side_drawer.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DashboardPage extends StatefulWidget {
|
||||
const DashboardPage({
|
||||
super.key,
|
||||
// required this.client,
|
||||
});
|
||||
|
||||
// final GClient client;
|
||||
|
||||
@override
|
||||
State<DashboardPage> createState() => _DashboardPageState();
|
||||
}
|
||||
|
||||
class _DashboardPageState extends State<DashboardPage> {
|
||||
bool _loading = false;
|
||||
late AccountInfo accountInfo;
|
||||
|
||||
void _setLoading(bool loading) {
|
||||
setState(() {
|
||||
_loading = loading;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_setLoading(true);
|
||||
// widget.client.getAccountInfo(
|
||||
// GetAccountInfoRequest(
|
||||
// accountId: widget.client.session.accountId,
|
||||
// ),
|
||||
// onError: ({String? msg}) {
|
||||
// ScaffoldMessenger.of(context).showSnackBar(
|
||||
// SnackBar(
|
||||
// content: const Text('AccountInfo konnte nicht geladen werden'),
|
||||
// action: msg != null
|
||||
// ? SnackBarAction(
|
||||
// label: 'Details',
|
||||
// textColor: Colors.grey,
|
||||
// onPressed: () {
|
||||
// showDialog(
|
||||
// context: context,
|
||||
// builder: (context) {
|
||||
// return AlertDialog(
|
||||
// content: Text(
|
||||
// msg,
|
||||
// textAlign: TextAlign.center,
|
||||
// style: const TextStyle(color: Colors.black),
|
||||
// ),
|
||||
// icon: const Icon(
|
||||
// Icons.warning,
|
||||
// color: Colors.red,
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// })
|
||||
// : null,
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// ).then((value) {
|
||||
// accountInfo = value.accountInfo;
|
||||
// _setLoading(false);
|
||||
// });
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
flexibleSpace: Image.asset(
|
||||
'lib/assets/logo_300x200.png',
|
||||
height: 80,
|
||||
),
|
||||
),
|
||||
drawer: Builder(builder: (context) {
|
||||
return SideDrawer(
|
||||
children: [
|
||||
const Spacer(),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Scaffold.of(context).closeDrawer();
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Text(
|
||||
'About',
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Icons.question_answer,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Scaffold.of(context).closeDrawer();
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Text(
|
||||
'Datenschutz',
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Icons.privacy_tip,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Scaffold.of(context).closeDrawer();
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Text(
|
||||
'Impressum',
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Icons.apartment,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// if (widget.client.session.accessToken != null)
|
||||
// TextButton(
|
||||
// onPressed: () {
|
||||
// widget.client.session.accessToken = null;
|
||||
// widget.client.session
|
||||
// .removeSession(widget.client.session.sessionId!);
|
||||
|
||||
// Navigator.of(context).pushAndRemoveUntil(
|
||||
// MaterialPageRoute(
|
||||
// builder: (context) =>
|
||||
// StartPage(client: widget.client),
|
||||
// ),
|
||||
// (route) => false);
|
||||
// },
|
||||
// child: const Row(
|
||||
// children: [
|
||||
// Text(
|
||||
// 'Log out',
|
||||
// style: TextStyle(fontSize: 20),
|
||||
// ),
|
||||
// Spacer(),
|
||||
// Icon(
|
||||
// Icons.logout,
|
||||
// color: Colors.white,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
const SizedBox(
|
||||
height: 250,
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
// bottomNavigationBar: Builder(
|
||||
// builder: (context) {
|
||||
// return BottomBar(
|
||||
// children: widget.client.session.accessToken != null
|
||||
// ? [
|
||||
// BottomNavigationBarItem(
|
||||
// backgroundColor: Colors.white,
|
||||
// label: 'Personen',
|
||||
// icon: Column(
|
||||
// children: [
|
||||
// IconButton(
|
||||
// onPressed: () =>
|
||||
// Scaffold.of(context).openDrawer(),
|
||||
// icon: const Icon(
|
||||
// Icons.group,
|
||||
// color: Colors.white,
|
||||
// ),
|
||||
// ),
|
||||
// const Text(
|
||||
// 'Personen',
|
||||
// style: TextStyle(
|
||||
// color: Colors.white,
|
||||
// fontSize: 16,
|
||||
// ),
|
||||
// )
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// BottomNavigationBarItem(
|
||||
// backgroundColor: Colors.white,
|
||||
// label: 'Home',
|
||||
// icon: Column(
|
||||
// children: [
|
||||
// IconButton(
|
||||
// onPressed: () {
|
||||
// Navigator.of(context).push(
|
||||
// MaterialPageRoute(
|
||||
// builder: (context) => StartPage(
|
||||
// client: widget.client,
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// icon: const Icon(
|
||||
// Icons.home,
|
||||
// color: Colors.white,
|
||||
// ),
|
||||
// ),
|
||||
// const Text(
|
||||
// 'Home',
|
||||
// style: TextStyle(
|
||||
// color: Colors.white,
|
||||
// fontSize: 16,
|
||||
// ),
|
||||
// )
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// BottomNavigationBarItem(
|
||||
// backgroundColor: Colors.white,
|
||||
// label: 'Menu',
|
||||
// icon: IconButton(
|
||||
// onPressed: () {
|
||||
// Scaffold.of(context).openDrawer();
|
||||
// },
|
||||
// icon: const Icon(
|
||||
// Icons.menu,
|
||||
// color: Colors.white,
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
// ]
|
||||
// : [
|
||||
// BottomNavigationBarItem(
|
||||
// label: 'back',
|
||||
// backgroundColor: Colors.white,
|
||||
// icon: IconButton(
|
||||
// onPressed: () {},
|
||||
// icon: const Icon(
|
||||
// Icons.arrow_back,
|
||||
// color: Colors.white,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// BottomNavigationBarItem(
|
||||
// backgroundColor: Colors.white,
|
||||
// label: 'Menu',
|
||||
// icon: IconButton(
|
||||
// onPressed: () => Scaffold.of(context).openDrawer(),
|
||||
// icon: const Icon(
|
||||
// Icons.menu,
|
||||
// color: Colors.white,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
body: !_loading
|
||||
? Background(
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 48,
|
||||
),
|
||||
Text(
|
||||
'Willkommen ${accountInfo.firstname} ${accountInfo.lastname}!',
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: const LoadingWidget(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import 'package:app/gapi/client.dart';
|
||||
import 'package:app/pages/start_page.dart';
|
||||
import 'package:app/model/services/backend_service.dart';
|
||||
import 'package:app/pages_old/start_page.dart';
|
||||
import 'package:app/widgets/background.dart';
|
||||
import 'package:app/widgets/bottom_bar.dart';
|
||||
import 'package:app/widgets/loading_widget.dart';
|
||||
@ -12,11 +13,11 @@ import 'package:grpc/grpc.dart';
|
||||
class LoginPage extends StatefulWidget {
|
||||
const LoginPage({
|
||||
super.key,
|
||||
required this.client,
|
||||
// required this.client,
|
||||
// required this.onChangePage,
|
||||
});
|
||||
|
||||
final GClient client;
|
||||
// final GClient client;
|
||||
// void Function(Pages page) onChangePage;
|
||||
|
||||
@override
|
||||
@ -87,7 +88,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
label: 'back',
|
||||
backgroundColor: Colors.white,
|
||||
icon: IconButton(
|
||||
onPressed: () => Navigator.of(context).pop(widget.client),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
icon: const Icon(
|
||||
Icons.arrow_back,
|
||||
color: Colors.white,
|
||||
@ -250,73 +251,23 @@ class _LoginPageState extends State<LoginPage> {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
// final navigator = Navigator.of(context);
|
||||
_setLoading(true);
|
||||
widget.client
|
||||
.login(
|
||||
BackendService.login(
|
||||
email: mailController.text,
|
||||
password: passwordController.text,
|
||||
onError: ({GrpcError? error}) {
|
||||
_setLoading(false);
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(SnackBar(
|
||||
content: const Text(
|
||||
'Login fehlgeschlagen',
|
||||
),
|
||||
action: SnackBarAction(
|
||||
textColor: Colors.grey,
|
||||
label: 'Details',
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
content: error != null
|
||||
? Text(
|
||||
'Fehler: ${error.message}',
|
||||
textAlign:
|
||||
TextAlign.center,
|
||||
style:
|
||||
const TextStyle(
|
||||
color: Colors
|
||||
.black),
|
||||
)
|
||||
: const Text(
|
||||
'Interner Fehler',
|
||||
textAlign:
|
||||
TextAlign.center,
|
||||
style: TextStyle(
|
||||
color:
|
||||
Colors.black),
|
||||
),
|
||||
icon: const Icon(
|
||||
Icons.error,
|
||||
color: Colors.black,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
));
|
||||
},
|
||||
onSuccess: () {
|
||||
// _setLoading(false);
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(const SnackBar(
|
||||
content: Text('Login erfolgreich'),
|
||||
));
|
||||
},
|
||||
)
|
||||
.then(
|
||||
).then(
|
||||
(r) {
|
||||
if (r.accessToken != '') {
|
||||
Navigator.pushAndRemoveUntil(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (ctx) => StartPage(
|
||||
client: widget.client,
|
||||
),
|
||||
),
|
||||
(ctx) => false,
|
||||
);
|
||||
if (r) {
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context);
|
||||
// Navigator.pushAndRemoveUntil(
|
||||
// context,
|
||||
// MaterialPageRoute(
|
||||
// builder: (ctx) => const StartPage(
|
||||
// // client: widget.client,
|
||||
// ),
|
||||
// ),
|
||||
// (ctx) => false,
|
||||
// );
|
||||
// widget.onChangePage(
|
||||
// Pages.dashboard,
|
||||
// );
|
224
frontend/app/lib/pages_old/register_page.dart
Normal file
224
frontend/app/lib/pages_old/register_page.dart
Normal file
@ -0,0 +1,224 @@
|
||||
import 'package:app/gapi/client.dart';
|
||||
import 'package:app/pages_old/start_page.dart';
|
||||
import 'package:app/widgets/background.dart';
|
||||
import 'package:app/widgets/bottom_bar.dart';
|
||||
import 'package:app/widgets/loading_widget.dart';
|
||||
import 'package:app/widgets/side_drawer.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:grpc/grpc.dart';
|
||||
|
||||
class RegisterPage extends StatefulWidget {
|
||||
const RegisterPage({
|
||||
super.key,
|
||||
// required this.client,
|
||||
});
|
||||
|
||||
// final GClient client;
|
||||
|
||||
@override
|
||||
State<RegisterPage> createState() => _RegisterPageState();
|
||||
}
|
||||
|
||||
class _RegisterPageState extends State<RegisterPage> {
|
||||
bool _loading = false;
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final mailController = TextEditingController();
|
||||
final passwordController = TextEditingController();
|
||||
|
||||
void _setLoading(bool loading) {
|
||||
setState(() {
|
||||
_loading = loading;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
flexibleSpace: Image.asset(
|
||||
'lib/assets/logo_300x200.png',
|
||||
height: 80,
|
||||
),
|
||||
),
|
||||
drawer: Builder(builder: (context) {
|
||||
return SideDrawer(
|
||||
children: [
|
||||
const Spacer(),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Scaffold.of(context).closeDrawer();
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Text(
|
||||
'About',
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Icons.question_answer,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Scaffold.of(context).closeDrawer();
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Text(
|
||||
'Datenschutz',
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Icons.privacy_tip,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Scaffold.of(context).closeDrawer();
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Text(
|
||||
'Impressum',
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Icons.apartment,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 250,
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
bottomNavigationBar: BottomBar(
|
||||
children: [
|
||||
BottomNavigationBarItem(
|
||||
label: 'back',
|
||||
backgroundColor: Colors.white,
|
||||
icon: IconButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
icon: const Icon(
|
||||
Icons.arrow_back,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
backgroundColor: Colors.white,
|
||||
label: 'Menu',
|
||||
icon: IconButton(
|
||||
onPressed: () => Scaffold.of(context).openDrawer(),
|
||||
icon: const Icon(
|
||||
Icons.menu,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: !_loading
|
||||
? Form(
|
||||
key: _formKey,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 100, 16, 16),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Image(
|
||||
width: 180,
|
||||
image: AssetImage(
|
||||
'lib/assets/logo_300x200.png',
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 30,
|
||||
),
|
||||
const Text(
|
||||
'Registrieren',
|
||||
style: TextStyle(
|
||||
fontFamily: 'sans-serif',
|
||||
fontSize: 24,
|
||||
height: 1.6,
|
||||
fontWeight: FontWeight.normal,
|
||||
letterSpacing: 6,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
TextFormField(
|
||||
controller: mailController,
|
||||
decoration: const InputDecoration(
|
||||
fillColor: Color.fromARGB(30, 255, 255, 255),
|
||||
filled: true,
|
||||
hintStyle: TextStyle(
|
||||
color: Colors.white38,
|
||||
),
|
||||
hintText: 'E-Mail Adresse',
|
||||
),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Bitte eine gültige E-Mail Adresse eingeben';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
TextFormField(
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
controller: passwordController,
|
||||
decoration: const InputDecoration(
|
||||
fillColor: Color.fromARGB(30, 255, 255, 255),
|
||||
filled: true,
|
||||
hintStyle: TextStyle(
|
||||
color: Colors.white38,
|
||||
),
|
||||
hintText: 'Passwort',
|
||||
),
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
obscureText: true,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Bitte geben Sie Ihr Passwort ein';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
// _setLoading(true);
|
||||
}
|
||||
},
|
||||
child: const Icon(Icons.login))
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const LoadingWidget()),
|
||||
);
|
||||
}
|
||||
}
|
373
frontend/app/lib/pages_old/start_page.dart
Normal file
373
frontend/app/lib/pages_old/start_page.dart
Normal file
@ -0,0 +1,373 @@
|
||||
import 'package:app/gapi/client.dart';
|
||||
import 'package:app/model/apis/api_response.dart';
|
||||
import 'package:app/model/view_model/account_vm.dart';
|
||||
import 'package:app/pages_old/dashboard_page.dart';
|
||||
import 'package:app/pages_old/login_page.dart';
|
||||
import 'package:app/pages_old/register_page.dart';
|
||||
import 'package:app/pb/account.pb.dart';
|
||||
import 'package:app/widgets/background.dart';
|
||||
import 'package:app/widgets/bottom_bar.dart';
|
||||
import 'package:app/widgets/side_drawer.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:core';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class StartPage extends StatefulWidget {
|
||||
const StartPage({
|
||||
super.key,
|
||||
// required this.client,
|
||||
});
|
||||
|
||||
// GClient client;
|
||||
|
||||
@override
|
||||
State<StartPage> createState() => _StartPageState();
|
||||
}
|
||||
|
||||
class _StartPageState extends State<StartPage> {
|
||||
final List<BottomNavigationBarItem> bottombarButtons = [];
|
||||
|
||||
// void _updateClient(GClient c) {
|
||||
// setState(() {
|
||||
// widget.client = c;
|
||||
// });
|
||||
// }
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
SnackBar _snackBar(BuildContext context, String message, String label,
|
||||
void Function() action) {
|
||||
ScaffoldMessenger.of(context).removeCurrentSnackBar();
|
||||
// ScaffoldMessenger.of(context).clearSnackBars();
|
||||
return SnackBar(
|
||||
content: Text(
|
||||
message,
|
||||
style: const TextStyle(color: Colors.black),
|
||||
),
|
||||
backgroundColor: Colors.white,
|
||||
action: SnackBarAction(
|
||||
label: label,
|
||||
onPressed: action,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Background(
|
||||
child: ChangeNotifierProvider<AccountViewModel>(
|
||||
create: (context) => AccountViewModel(),
|
||||
child: Consumer<AccountViewModel>(builder: (context, value, child) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
drawer: Builder(builder: (context) {
|
||||
return SideDrawer(children: [
|
||||
const Spacer(),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Scaffold.of(context).closeDrawer();
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Text(
|
||||
'About',
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Icons.question_answer,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Scaffold.of(context).closeDrawer();
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Text(
|
||||
'Datenschutz',
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Icons.privacy_tip,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Scaffold.of(context).closeDrawer();
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Text(
|
||||
'Impressum',
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Icons.apartment,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
value.logout();
|
||||
// ScaffoldMessenger.of(context).clearSnackBars();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
_snackBar(
|
||||
context,
|
||||
value.response.message != null
|
||||
? value.response.message!
|
||||
: value.response.status.toString(),
|
||||
value.response.status.toString(),
|
||||
() {
|
||||
print('asdf');
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Row(
|
||||
children: [
|
||||
Text(
|
||||
'Log out',
|
||||
style: TextStyle(fontSize: 20),
|
||||
),
|
||||
Spacer(),
|
||||
Icon(
|
||||
Icons.logout,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 250,
|
||||
)
|
||||
]);
|
||||
}),
|
||||
bottomNavigationBar: Builder(builder: (context) {
|
||||
return BottomBar(
|
||||
// onTap: (value) => _bottomBarAction(value),
|
||||
children: value.response.data != null
|
||||
? [
|
||||
BottomNavigationBarItem(
|
||||
backgroundColor: Colors.white,
|
||||
label: 'Personen',
|
||||
icon: Column(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () =>
|
||||
Scaffold.of(context).openDrawer(),
|
||||
icon: const Icon(
|
||||
Icons.group,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const Text(
|
||||
'Personen',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
backgroundColor: Colors.white,
|
||||
label: 'Dashboard',
|
||||
icon: Column(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(
|
||||
Icons.dashboard,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const Text(
|
||||
'Dashboard',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
backgroundColor: Colors.white,
|
||||
label: 'Menu',
|
||||
icon: IconButton(
|
||||
onPressed: () {
|
||||
Scaffold.of(context).openDrawer();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.menu,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
)
|
||||
]
|
||||
: [
|
||||
BottomNavigationBarItem(
|
||||
label: 'register',
|
||||
backgroundColor: Colors.white,
|
||||
icon: Column(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(
|
||||
Icons.login,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const Text(
|
||||
'Registrieren',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
label: 'login',
|
||||
backgroundColor: Colors.white,
|
||||
icon: Column(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (builder) =>
|
||||
const LoginPage()));
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.login,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const Text(
|
||||
'Login',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
backgroundColor: Colors.white,
|
||||
label: 'Menu',
|
||||
icon: IconButton(
|
||||
onPressed: () => Scaffold.of(context).openDrawer(),
|
||||
icon: const Icon(
|
||||
Icons.menu,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
body: Column(
|
||||
children: [
|
||||
if (value.response.status == Status.COMPLETED &&
|
||||
value.response.data != null &&
|
||||
!(value.response.data as Account).emailVerified)
|
||||
Card(
|
||||
color: Colors.orange,
|
||||
child: Center(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
const Text(
|
||||
'E-Mail ist noch nicht verifiziert.',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: 'sans-serif',
|
||||
fontSize: 14),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(
|
||||
Icons.restore,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Image(
|
||||
image: AssetImage(
|
||||
'lib/assets/logo_300x200.png',
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 40,
|
||||
),
|
||||
Text(
|
||||
'Digitale Spuren auf Knopfdruck entfernen'
|
||||
.toUpperCase(),
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'sans-serif',
|
||||
fontSize: 24,
|
||||
height: 1.6,
|
||||
fontWeight: FontWeight.normal,
|
||||
letterSpacing: 6,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// ScaffoldMessenger.of(context).clearSnackBars();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
_snackBar(
|
||||
context,
|
||||
value.response.message != null
|
||||
? value.response.message!
|
||||
: value.response.status.toString(),
|
||||
value.response.status.toString(),
|
||||
() {
|
||||
print('asdf');
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('asdf'))
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
11
frontend/app/lib/util/colors.dart
Normal file
11
frontend/app/lib/util/colors.dart
Normal file
@ -0,0 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CustomColors {
|
||||
static Color get error {
|
||||
return const Color.fromARGB(200, 255, 90, 90);
|
||||
}
|
||||
|
||||
static Color get success {
|
||||
return const Color.fromARGB(200, 55, 125, 55);
|
||||
}
|
||||
}
|
27
frontend/app/lib/util/validation.dart
Normal file
27
frontend/app/lib/util/validation.dart
Normal file
@ -0,0 +1,27 @@
|
||||
final emailRegExp = RegExp(r"^[a-zA-Z0-9.]+@[a-zA-Z0-9]+\.[a-zA-Z]+");
|
||||
final nameRegExp =
|
||||
RegExp(r"^\s*([A-Za-z]{1,}([\.,] |[-']| ))+[A-Za-z]+\.?\s*$");
|
||||
final phoneRegExp = RegExp(r"^\+?0[0-9]{10}$");
|
||||
final passwordRegExp = RegExp(r'^.+$');
|
||||
|
||||
extension valString on String {
|
||||
bool get isValidEmail {
|
||||
return emailRegExp.hasMatch(this);
|
||||
}
|
||||
|
||||
bool get isValidName {
|
||||
return nameRegExp.hasMatch(this);
|
||||
}
|
||||
|
||||
bool get isValidPassword {
|
||||
return passwordRegExp.hasMatch(this);
|
||||
}
|
||||
|
||||
bool get isNotEmpty {
|
||||
return this != trim();
|
||||
}
|
||||
|
||||
bool get isValidPhone {
|
||||
return phoneRegExp.hasMatch(this);
|
||||
}
|
||||
}
|
49
frontend/app/lib/widgets/bottom_navigation.dart
Normal file
49
frontend/app/lib/widgets/bottom_navigation.dart
Normal file
@ -0,0 +1,49 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class BottomNavigation extends StatelessWidget {
|
||||
BottomNavigation({
|
||||
super.key,
|
||||
required this.children,
|
||||
this.backgroundColor,
|
||||
this.iconColor,
|
||||
}) {
|
||||
backgroundColor ??= Colors.black;
|
||||
}
|
||||
|
||||
List<Widget> children;
|
||||
Color? backgroundColor;
|
||||
Color? iconColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 70,
|
||||
color: backgroundColor,
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
// horizontal: 10,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: children.isEmpty
|
||||
? MainAxisAlignment.center
|
||||
: MainAxisAlignment.spaceAround,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
...children,
|
||||
Builder(builder: (context) {
|
||||
return IconButton(
|
||||
onPressed: () => Scaffold.of(context).openDrawer(),
|
||||
icon: const Icon(
|
||||
Icons.menu,
|
||||
color: Colors.white,
|
||||
));
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
46
frontend/app/lib/widgets/bottom_navigation_item.dart
Normal file
46
frontend/app/lib/widgets/bottom_navigation_item.dart
Normal file
@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class BottomNavigationItem extends StatelessWidget {
|
||||
BottomNavigationItem({
|
||||
super.key,
|
||||
required this.onPressed,
|
||||
required this.icon,
|
||||
required this.color,
|
||||
this.textSize,
|
||||
this.iconSize,
|
||||
this.label,
|
||||
}) {
|
||||
textSize ??= 15;
|
||||
iconSize ??= 25;
|
||||
}
|
||||
|
||||
void Function() onPressed;
|
||||
IconData icon;
|
||||
Color color;
|
||||
double? textSize;
|
||||
double? iconSize;
|
||||
String? label;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisAlignment:
|
||||
label != null ? MainAxisAlignment.center : MainAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: onPressed,
|
||||
icon: Icon(
|
||||
icon,
|
||||
size: iconSize,
|
||||
color: color,
|
||||
)),
|
||||
if (label != null)
|
||||
Text(
|
||||
label!,
|
||||
style: TextStyle(fontSize: textSize, color: color),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
30
frontend/app/lib/widgets/drawer.dart
Normal file
30
frontend/app/lib/widgets/drawer.dart
Normal file
@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class SideDrawer extends StatelessWidget {
|
||||
SideDrawer({super.key, required this.children, this.backgroundColor}) {
|
||||
backgroundColor ??= Colors.black;
|
||||
}
|
||||
|
||||
final List<Widget> children;
|
||||
Color? backgroundColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Drawer(
|
||||
backgroundColor: backgroundColor,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(40.0),
|
||||
child: Center(
|
||||
child: Builder(builder: (context) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: children,
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
50
frontend/app/lib/widgets/side_drawer_item.dart
Normal file
50
frontend/app/lib/widgets/side_drawer_item.dart
Normal file
@ -0,0 +1,50 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class SideDrawerItem extends StatelessWidget {
|
||||
SideDrawerItem({
|
||||
super.key,
|
||||
required this.onPressed,
|
||||
required this.icon,
|
||||
required this.color,
|
||||
required this.label,
|
||||
this.textSize,
|
||||
this.iconSize,
|
||||
}) {
|
||||
textSize ??= 15;
|
||||
iconSize ??= 25;
|
||||
}
|
||||
|
||||
void Function() onPressed;
|
||||
IconData icon;
|
||||
Color color;
|
||||
String label;
|
||||
double? textSize;
|
||||
double? iconSize;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextButton(
|
||||
onPressed: onPressed,
|
||||
child: Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
color: color,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: textSize,
|
||||
color: color,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -195,6 +195,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: nested
|
||||
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
path:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -219,6 +227,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: provider
|
||||
sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.5"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
@ -42,6 +42,7 @@ dependencies:
|
||||
sqflite: ^2.3.0
|
||||
path: ^1.8.3
|
||||
fixnum: ^1.1.0
|
||||
provider: ^6.0.5
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^2.0.0
|
||||
|
Loading…
x
Reference in New Issue
Block a user