Initial commit

This commit is contained in:
2023-01-22 19:44:38 +01:00
parent 021bc02992
commit 1baa8028fa
164 changed files with 6587 additions and 2 deletions

111
lib/app.dart Normal file
View File

@ -0,0 +1,111 @@
import 'package:flutter/material.dart';
import 'package:marianum_mobile/data/incommingPackets/talkNotificationsPacket.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'data/accountModel.dart';
import 'screen/pages/files/files.dart';
import 'screen/pages/more/overhang.dart';
import 'screen/pages/talk/chatOverview.dart';
import 'screen/pages/timetable/timetable.dart';
import 'screen/settings/settings.dart';
class App extends StatefulWidget {
const App({Key? key}) : super(key: key);
@override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
int currentPage = 0;
@override
Widget build(BuildContext context) {
final PageController pageController = PageController();
return Scaffold(
appBar: AppBar(
title: const Text("Marianum Fulda"),
actions: <Widget>[
IconButton(
padding: const EdgeInsets.only(right: 15),
icon: const Icon(Icons.settings),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => const Settings()));
},
)
],
),
body: PageView(
controller: pageController,
children: const [
Timetable(),
Talk(),
Files(),
Overhang(),
],
onPageChanged: (page) {
setState(() {
currentPage = page;
});
},
),
bottomNavigationBar: BottomNavigationBar(
items: [
const BottomNavigationBarItem(icon: Icon(Icons.calendar_month), label: "Vertretung"),
BottomNavigationBarItem(icon: Stack(
children: [
const Icon(Icons.chat),
Consumer<TalkNotificationsPacket>(
builder: (context, data, child) {
return Visibility(
visible: data.amount != 0,
child: Positioned(
right: 0,
child: Container(
padding: const EdgeInsets.all(1),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(6),
),
constraints: const BoxConstraints(
minWidth: 12,
minHeight: 12,
),
child: Text(
"${data.amount}",
style: const TextStyle(
color: Colors.black,
fontSize: 10,
),
textAlign: TextAlign.center,
),
),
),
);
},
)
],
), label: "Talk"),
const BottomNavigationBarItem(icon: Icon(Icons.folder), label: "Dateien"),
const BottomNavigationBarItem(icon: Icon(Icons.list), label: "Mehr"),
],
selectedItemColor: Theme.of(context).primaryColor,
unselectedItemColor: Colors.grey,
showUnselectedLabels: true,
showSelectedLabels: true,
currentIndex: currentPage,
onTap: (item) {
setState(() {
currentPage = item;
pageController.jumpToPage(item);
});
},
),
);
}
}

View File

@ -0,0 +1,19 @@
import 'package:flutter/cupertino.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
class AccountModel extends ChangeNotifier {
bool _isLoggedIn = false;
bool get isLoggedIn => _isLoggedIn;
void logout() {
_isLoggedIn = false;
notifyListeners();
}
void login() {
_isLoggedIn = true;
notifyListeners();
}
}

View File

@ -0,0 +1,43 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/cupertino.dart';
import 'package:marianum_mobile/data/socketConnection.dart';
class IncomingPacket extends ChangeNotifier {
String packetId;
bool _isReceived = false;
bool get isReceived => _isReceived;
IncomingPacket(this.packetId) {
log("PACKETLISTENER ERSTELLT!");
SocketConnection.read.listen((event) {
if(event.startsWith("$packetId:")) {
_isReceived = true;
// THIS listener handles the incomming request
log("$packetId is handled!");
handle(jsonDecode(event.split("$packetId:")[1]));
}
notifyListeners();
});
}
void invoke({Object? data, bool indicateLoading = false, bool allowNotifyListeners = true}) {
data = data ?? {};
log("$packetId is invoked with data: $data");
SocketConnection.write.add("$packetId:${jsonEncode(data)}");
if(indicateLoading) {
_isReceived = false;
if(allowNotifyListeners) notifyListeners();
}
}
void handle(dynamic data) {
log("Warning: $packetId packet listener is registered, but no handle is defined!");
}
}

View File

@ -0,0 +1,19 @@
import 'dart:developer';
import 'package:marianum_mobile/data/incomingPacket.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AuthenticatePacket extends IncomingPacket {
AuthenticatePacket() : super("authenticate");
@override
void handle(data) {
SharedPreferences.getInstance().then((value) => {
invoke(data: {
'username': value.getString("username"),
'password': value.getString("password")
})
});
}
}

View File

@ -0,0 +1,29 @@
import 'package:marianum_mobile/data/incomingPacket.dart';
class FileEntry {
String name;
bool isFolder;
String path;
FileEntry(this.name, this.isFolder, this.path);
}
class FileListPacket extends IncomingPacket {
FileListPacket() : super("fileList");
List<FileEntry> _entries = List<FileEntry>.empty(growable: true);
List<FileEntry> get entries => _entries;
String _lastPath = "/";
String get lastPath => _lastPath;
@override
void handle(data) {
_entries = List<FileEntry>.empty(growable: true);
_lastPath = data['backLink'];
data['files'].forEach((value) => {
_entries.add(FileEntry(value['name'], value['is_folder'], value['path']))
});
}
}

View File

@ -0,0 +1,28 @@
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:marianum_mobile/data/socketConnection.dart';
import '../incomingPacket.dart';
class ServerInfoPacket extends IncomingPacket {
ServerInfoPacket() : super('serverInfo');
String _serverName = "Unbekannt";
String _serverOwner = "Unbekannt";
String _serverVersion = "?";
String _legal = "Keine";
String get serverName => _serverName;
String get serverOwner => _serverOwner;
String get serverVersion => _serverVersion;
String get legal => _legal;
@override
void handle(data) {
_serverName = data['name'];
_serverOwner = data['owner'];
_serverVersion = data['version'];
_legal = data['legal'];
}
}

View File

@ -0,0 +1,26 @@
import 'package:marianum_mobile/data/incomingPacket.dart';
class TalkMessage {
bool self;
String sender;
String content;
String date;
TalkMessage(this.self, this.sender, this.content, this.date);
}
class TalkChatPacket extends IncomingPacket {
TalkChatPacket() : super("talkChat");
List<TalkMessage> _messages = List<TalkMessage>.empty(growable: true);
List<TalkMessage> get messages => _messages;
@override
void handle(data) {
_messages = List<TalkMessage>.empty(growable: true);
print(data);
data.forEach((message) => {
_messages.add(TalkMessage(true, message['actorId'], message['message'], message['timestamp']))
});
}
}

View File

@ -0,0 +1,46 @@
import 'dart:convert';
import 'package:intl/intl.dart';
import 'package:jiffy/jiffy.dart';
import 'package:marianum_mobile/data/incomingPacket.dart';
class TalkContact {
String name;
String lastMessage;
String lastTime;
String profilePicture;
bool unreadMessages;
bool isGroup;
String userToken;
TalkContact(this.name, this.lastMessage, this.lastTime, this.profilePicture, this.unreadMessages, this.isGroup, this.userToken);
}
class TalkContactsPaket extends IncomingPacket {
TalkContactsPaket() : super('talkContacts');
List<TalkContact> _contacts = List<TalkContact>.empty(growable: true);
List<TalkContact> get contacts => _contacts;
@override
void handle(data) {
_contacts = List<TalkContact>.empty(growable: true);
//data["ocs"]["data"].sort((a, b) => a['lastActivity'].compareTo(b['lastActivity']));
data.forEach((value) async {
await Jiffy.locale("de");
_contacts.add(
TalkContact(
value['displayName'],
value['lastMessage']['message'],
Jiffy(DateTime.fromMillisecondsSinceEpoch(value['lastActivity'] * 1000)).fromNow(),
value['name'] != null ? "https://cloud.marianum-fulda.de/avatar/${value['name']}/128" : "",
value['unreadMessages'] > 0,
value['type'] != 1,
value['token']
)
);
});
}
}

View File

@ -0,0 +1,13 @@
import 'package:marianum_mobile/data/incomingPacket.dart';
class TalkNotificationsPacket extends IncomingPacket {
TalkNotificationsPacket() : super("talkNotifications");
int _amount = 0;
int get amount => _amount;
@override
void handle(data) {
_amount = data['amount'];
}
}

View File

@ -0,0 +1,14 @@
import 'dart:convert';
import 'package:marianum_mobile/data/socketConnection.dart';
class OutgoingPacket {
final String command;
final dynamic data;
OutgoingPacket({required this.command, required this.data});
void send() {
SocketConnection.write.add("$command:${jsonEncode(data)}");
}
}

View File

@ -0,0 +1,8 @@
import 'package:marianum_mobile/data/outgoingPacket.dart';
class TalkContactsAskPacket extends OutgoingPacket {
TalkContactsAskPacket() : super(
command: "talkContacts",
data: {},
);
}

View File

@ -0,0 +1,10 @@
import 'package:web_socket_channel/web_socket_channel.dart';
class SocketConnection {
static final _connection = WebSocketChannel.connect(
Uri.parse('ws://localhost:1234'),
);
static var read = _connection.stream.asBroadcastStream();
static var write = _connection.sink;
}

109
lib/main.dart Normal file
View File

@ -0,0 +1,109 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:marianum_mobile/data/accountModel.dart';
import 'package:marianum_mobile/data/incommingPackets/authenticatePacket.dart';
import 'package:marianum_mobile/data/incommingPackets/fileListPacket.dart';
import 'package:marianum_mobile/data/incommingPackets/talkChatPacket.dart';
import 'package:marianum_mobile/data/incommingPackets/talkNotificationsPacket.dart';
import 'package:marianum_mobile/screen/login/login.dart';
import 'package:marianum_mobile/screen/pages/timetable/timetable.dart';
import 'package:marianum_mobile/widget/loadingSpinner.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'app.dart';
import 'data/incommingPackets/serverInfoPacket.dart';
import 'data/incommingPackets/talkContactsPacket.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
ByteData data = await PlatformAssetBundle().load('assets/ca/lets-encrypt-r3.pem');
SecurityContext.defaultContext.setTrustedCertificatesBytes(data.buffer.asUint8List());
AuthenticatePacket();
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => AccountModel()),
ChangeNotifierProvider(create: (context) => ServerInfoPacket()),
ChangeNotifierProvider(create: (context) => TalkContactsPaket()),
ChangeNotifierProvider(create: (context) => TalkNotificationsPacket()),
ChangeNotifierProvider(create: (context) => FileListPacket()),
ChangeNotifierProvider(create: (context) => TalkChatPacket()),
],
child: const Main(),
)
);
}
class Main extends StatefulWidget {
const Main({Key? key}) : super(key: key);
@override
State<Main> createState() => _MainState();
}
class _MainState extends State<Main> {
static const Color red = Color.fromARGB(255, 153, 51, 51);
final Future<SharedPreferences> _storage = SharedPreferences.getInstance();
@override
void initState() {
super.initState();
_storage.then((SharedPreferences preferences) => preferences.getBool("loggedIn") ?? false).then((value) => {
if(value) {
Provider.of<AccountModel>(context, listen: false).login()
} else {
Provider.of<AccountModel>(context, listen: false).logout()
}
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Marianum Fulda',
theme: ThemeData(
brightness: Brightness.light,
primaryColor: red,
appBarTheme: const AppBarTheme(
backgroundColor: red,
),
progressIndicatorTheme: const ProgressIndicatorThemeData(
color: red,
),
),
home: FutureBuilder<SharedPreferences>(
future: _storage,
builder: (BuildContext context, AsyncSnapshot<SharedPreferences> snapshot) {
if(snapshot.hasData) {
return Consumer<AccountModel>(
builder: (context, value, child) {
return value.isLoggedIn ? const App() : const Login();
},
);
} else {
return const LoadingSpinner();
}
},
)
);
}
}

View File

@ -0,0 +1,93 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_login/flutter_login.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../data/accountModel.dart';
class Login extends StatefulWidget {
const Login({Key? key}) : super(key: key);
@override
State<Login> createState() => _LoginState();
}
class _LoginState extends State<Login> {
Duration get loginTime => const Duration(milliseconds: 2250);
final Future<SharedPreferences> _storage = SharedPreferences.getInstance();
String? checkInput(value){
return (value ?? "").length < 5 ? "Eingabe zu kurz" : null;
}
@override
Widget build(BuildContext context) {
return FlutterLogin(
logo: Image.file(File("assets/logo/icon.png")).image,
userValidator: checkInput,
passwordValidator: checkInput,
onLogin: _authUser,
onSignup: null,
onRecoverPassword: _recoverPassword,
theme: LoginTheme(
primaryColor: Theme.of(context).primaryColor,
cardTheme: const CardTheme(
elevation: 10,
shape: InputBorder.none,
),
),
messages: LoginMessages(
loginButton: "Anmelden",
userHint: "Nutzername",
passwordHint: "Passwort",
),
disableCustomPageTransformer: true,
headerWidget: const Padding(
padding: EdgeInsets.only(bottom: 10),
child: Center(
child: Text(
"Dies ist ein Inoffizieller Nextclient & Webuntis Client und wird nicht vom Marianum selbst betrieben.\nBitte bedenke, dass deine persönlichen Anmelde & Infodaten durch dritte, nicht vom Marianum betriebene, Systeme geleitet werden!",
textAlign: TextAlign.center,
),
),
),
footer: "Marianum Fulda - Die persönliche Schule!",
title: "Marianum",
hideForgotPasswordButton: true,
userType: LoginUserType.name,
);
}
Future<String?> _authUser(LoginData data) async {
final SharedPreferences preferences = await _storage;
preferences.setBool("loggedIn", true);
preferences.setString("username", data.name);
preferences.setString("password", data.password);
debugPrint('Name: ${data.name}, Password: ${data.password}');
return Future.delayed(loginTime).then((_) {
Provider.of<AccountModel>(context, listen: false).login();
return null;
});
}
Future<String> _recoverPassword(String name) {
return Future.delayed(loginTime).then((_) {
return "Diese Funktion steht nicht zur Verfügung!";
});
}
}

View File

@ -0,0 +1,271 @@
import 'package:flutter/material.dart';
import 'package:marianum_mobile/data/incommingPackets/fileListPacket.dart';
import 'package:marianum_mobile/widget/loadingPacket.dart';
import 'package:provider/provider.dart';
import 'package:webdav_client/webdav_client.dart';
import '../../../data/accountModel.dart';
import '../../../widget/loadingSpinner.dart';
class Files extends StatefulWidget {
const Files({Key? key}) : super(key: key);
@override
State<Files> createState() => _FilesState();
}
class _FilesState extends State<Files> {
// List<String> path = List<String>.empty(growable: true);
// List<File> files = List<File>.empty(growable: true);
//
// var client = newClient(
// "https://cloud.marianum-fulda.de/remote.php/dav/files/***REMOVED***/",
// user: "***REMOVED***",
// password: "***REMOVED***",
// );
@override
void initState() {
Provider.of<FileListPacket>(context, listen: false).invoke();
super.initState();
// client.setHeaders(
// {
// "Authorization": "Bearer",
// "User-Agent": "Marianum Fulda/Alpha0.1 (Development build) ; https://mhsl.eu/id.html",
// }
// );
//
// path.add("/");
// Future.delayed(Duration.zero).then((context) => updatePath());
}
// void homeFolder() {
// path.clear();
// path.add("/");
// updatePath();
// }
//
// void popFolder() {
// if(path.length == 1) return;
// path.removeLast();
// updatePath();
// }
//
// void enterFolder(String sub) {
// path.add(sub);
// updatePath();
// }
//
// void updatePath() {
//
// final files = client.readDir(path.join("/"));
//
// showDialog(
// context: context,
// barrierDismissible: false,
// builder: (BuildContext context) {
// return const LoadingSpinner();
// }
// );
//
// files.then((value) =>
// setState(() {
// Navigator.pop(context);
// this.files.clear();
// this.files = value;
// })
// );
// }
@override
Widget build(BuildContext context) {
// List<Widget> items = List<Widget>.empty(growable: true);
//
// var counter = 0;
// for (final file in files) {
// bool isDir = file.isDir ?? false;
// String name = file.name ?? "?";
//
// items.add(ListTile(
// title: Text(file.name ?? "?"),
// leading: Icon(isDir ? Icons.folder_outlined : Icons.file_copy),
// trailing: Icon(isDir ? Icons.arrow_right : null),
// onTap: () {
// enterFolder(file.name ?? "");
// },
// onLongPress: () {
// setState(() {
// items[counter] = ListTile(
// title: Text(file.name ?? "?"),
// trailing: const Icon(Icons.delete),
// );
// });
// },
// ));
//
// }
//
// items.insert(0, AppBar(
// leading: path.length == 1 ? null : IconButton(
// icon: const Icon(Icons.keyboard_arrow_left),
// onPressed: () {
// popFolder();
// },
// ),
// actions: [
// IconButton(
// icon: const Icon(Icons.home),
// onPressed: () {
// homeFolder();
// },
// ),
//
// IconButton(
// icon: const Icon(Icons.refresh),
// onPressed: () {
// updatePath();
// },
// )
// ],
//
// title: Text(path.length == 1 ? "Dateien" : path.last),
// ));
//
// return ListView(
// children: items,
// );
return Consumer<FileListPacket>(
builder: (context, data, child) {
List<ListTile> entries = List<ListTile>.empty(growable: true);
data.entries.forEach((element) {
entries.add(ListTile(
title: Text(element.name),
leading: Icon(element.isFolder ? Icons.folder : Icons.file_copy_outlined),
onTap: () {
if(element.isFolder) {
Provider.of<FileListPacket>(context, listen: false).invoke(
data: {
"path": element.path
},
indicateLoading: true,
);
} else {
// TODO: Open an File
}
},
onLongPress: () {
showModalBottomSheet<void>(
context: context,
builder: (context) {
return ListView(
children: [
ListTile(
leading: Icon(Icons.delete),
title: Text("'${element.name.replaceRange(20, element.name.length, " ...")}' Löschen"),
),
const ListTile(
leading: Icon(Icons.share),
title: Text("Teilen"),
)
],
);
},
);
},
));
});
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back_outlined),
onPressed: () {
Provider.of<FileListPacket>(context, listen: false).invoke(
data: {
"path": data.lastPath
},
indicateLoading: true,
);
}
),
title: Text(data.lastPath),
actions: [
IconButton(
onPressed: () {
Provider.of<FileListPacket>(context, listen: false).invoke(indicateLoading: true);
},
icon: const Icon(Icons.home)
),
PopupMenuButton(
icon: Icon(Icons.add),
itemBuilder: (context) {
return [
const PopupMenuItem<int>(
value: 0,
child: ListTile(
leading: Icon(Icons.folder),
title: Text("Ordner erstellen"),
),
),
const PopupMenuItem<int>(
value: 1,
child: ListTile(
leading: Icon(Icons.upload),
title: Text("Datei Hochladen"),
),
)
];
},
)
],
),
floatingActionButton: FloatingActionButton.small(
onPressed: () {
showModalBottomSheet(
isScrollControlled: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context) {
return Container(
height: MediaQuery.of(context).size.height * 0.3,
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(25.0),
topRight: Radius.circular(25.0),
)
),
child: ListView(
children: const [
ListTile(
leading: Icon(Icons.create_new_folder_sharp),
title: Text("Neuer Ordner"),
),
ListTile(
leading: Icon(Icons.upload),
title: Text("Hochladen"),
)
],
),
);
},
);
},
backgroundColor: Theme.of(context).primaryColor,
child: const Icon(Icons.add),
),
body: LoadingPacket(packet: data, child: ListView(children: entries)),
);
},
);
}
}

View File

@ -0,0 +1,15 @@
import 'package:flutter/cupertino.dart';
class Message extends StatefulWidget {
const Message({Key? key}) : super(key: key);
@override
State<Message> createState() => _MessageState();
}
class _MessageState extends State<Message> {
@override
Widget build(BuildContext context) {
return const Text("Message");
}
}

View File

@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
import 'package:marianum_mobile/screen/pages/more/roomplan/roomplan.dart';
import '../../../widget/ListItem.dart';
import 'message/message.dart';
class Overhang extends StatelessWidget {
const Overhang({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView(
children: const [
ListItemNavigator(icon: Icons.newspaper, text: "Marianum Message", target: Message()),
ListItemNavigator(icon: Icons.room, text: "Raumplan", target: Roomplan()),
],
);
}
}

View File

@ -0,0 +1,10 @@
import 'package:flutter/cupertino.dart';
class Roomplan extends StatelessWidget {
const Roomplan({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Text("asd");
}
}

View File

@ -0,0 +1,166 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:marianum_mobile/data/outgoingPackets/talkContactsAskPacket.dart';
import 'package:marianum_mobile/widget/loadingPacket.dart';
import 'package:provider/provider.dart';
import '../../../data/accountModel.dart';
import '../../../data/incommingPackets/talkContactsPacket.dart';
import '../../../widget/loadingSpinner.dart';
import 'chatView.dart';
class Talk extends StatefulWidget {
const Talk({Key? key}) : super(key: key);
@override
State<Talk> createState() => _TalkState();
}
class _TalkState extends State<Talk> {
// List<ChatData> chats = List<ChatData>.empty(growable: true);
//
// Future<List<ChatData>> getChats() async {
// var url = Uri.https("***REMOVED***:***REMOVED***@mhsl.eu", "marianum/app/middleware/chat.php");
// var response = await http.get(
// url,
// headers: (
// {
// "Accept": "application/json",
// "OCS-APIRequest": "true",
// }
// ),
// );
//
// return compute(parseChats, response.body);
// }
@override
void initState() {
Provider.of<TalkContactsPaket>(context, listen: false).invoke();
//TalkContactsAskPacket().send();
super.initState();
// Future.delayed(Duration.zero).then((context) => updateChats());
// Provider.of<AccountModel>(context, listen: false).channel.sink.add("chat");
}
void updateChats() {
// var chats = getChats();
//
// showDialog(
// context: context,
// barrierDismissible: false,
// builder: (BuildContext context) {
// return const LoadingSpinner();
// }
// );
//
// chats.then((value) =>
// setState(() {
// Navigator.pop(context);
// this.chats.clear();
// this.chats = value;
// })
// );
}
@override
Widget build(BuildContext context) {
// List<ListTile> chats = List<ListTile>.empty(growable: true);
//
// for (var element in this.chats) {
// chats.add(
// ListTile(
// leading: element.type == 1 ? CircleAvatar(
// backgroundColor: Colors.grey,
// foregroundImage: Image.network(element.avatar).image,
// ) : const Icon(Icons.group),
// title: Text(element.name),
// subtitle: Text(
// "${element.lastMessageAuthor}: ${element.lastMessage.replaceAll("\n", "")}",
// overflow: TextOverflow.ellipsis,
// ),
// onTap: () {
// Navigator.push(context, MaterialPageRoute(builder: (builder) => const ChatView()));
// },
// trailing: element.unreadMessages > 0 ? const Icon(Icons.mark_chat_unread) : Text(element.lastActivity),
// )
// );
// }
//
// return ListView(
// children: chats,
// );
return Consumer<TalkContactsPaket>(
builder: (context, data, child) {
List<ListTile> chats = List<ListTile>.empty(growable: true);
for (var element in data.contacts) {
chats.add(ListTile(
title: Text(element.name),
subtitle: Text("${element.lastTime}: ${element.lastMessage}".replaceAll("\n", " "), overflow: TextOverflow.ellipsis),
trailing: element.unreadMessages ? const Icon(Icons.new_releases_outlined) : null,
leading: CircleAvatar(
foregroundImage: element.isGroup ? null : Image.network(element.profilePicture).image,
backgroundColor: Theme.of(context).primaryColor,
foregroundColor: Colors.white,
child: element.isGroup ? const Icon(Icons.group) : const Icon(Icons.person),
),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return ChatView(
userToken: element.userToken,
);
}));
},
));
}
return LoadingPacket(packet: data, child: ListView(children: chats));
},
);
}
}
//
// List<ChatData> parseChats(String json) {
// final parsed = jsonDecode(json).cast<Map<String, dynamic>>();
// return parsed.map<ChatData>((a) => ChatData.fromJson(a)).toList();
// }
//
// class ChatData {
// final String name;
// final String lastMessage;
// final String lastMessageAuthor;
// final String avatar;
// final int type;
// final String lastActivity;
// final int unreadMessages;
//
// const ChatData({
// required this.name,
// required this.lastMessage,
// required this.lastMessageAuthor,
// required this.avatar,
// required this.type,
// required this.lastActivity,
// required this.unreadMessages,
// });
//
// factory ChatData.fromJson(Map<String, dynamic> json) {
// return ChatData(
// name: json['name'] as String,
// lastMessage: json['last_message'] as String,
// lastMessageAuthor: json['last_message_author'] as String,
// avatar: json['avatar'] as String,
// type: json['type'] as int,
// lastActivity: json['lastActivity'] as String,
// unreadMessages: json['unreadMessages'] as int,
// );
// }
// }

View File

@ -0,0 +1,101 @@
import 'package:bubble/bubble.dart';
import 'package:flutter/material.dart';
import 'package:marianum_mobile/data/incommingPackets/talkChatPacket.dart';
import 'package:marianum_mobile/widget/loadingPacket.dart';
import 'package:provider/provider.dart';
class ChatView extends StatefulWidget {
final String userToken;
const ChatView({Key? key, required this.userToken}) : super(key: key);
@override
State<ChatView> createState() => _ChatViewState();
}
class _ChatViewState extends State<ChatView> {
static const styleSystem = BubbleStyle(
color: Color.fromRGBO(212, 234, 244, 1.0),
borderWidth: 1,
elevation: 2,
margin: BubbleEdges.only(top: 15),
alignment: Alignment.center,
);
static const styleOther = BubbleStyle(
nip: BubbleNip.leftBottom,
color: Colors.white,
borderWidth: 1,
elevation: 2,
margin: BubbleEdges.only(top: 15, left: 10),
alignment: Alignment.topLeft,
);
static const styleSelf = BubbleStyle(
nip: BubbleNip.rightBottom,
color: Color.fromRGBO(225, 255, 199, 1.0),
borderWidth: 1,
elevation: 2,
margin: BubbleEdges.only(top: 15, right: 10),
alignment: Alignment.topRight,
);
@override
void initState() {
super.initState();
Provider.of<TalkChatPacket>(context, listen: false).invoke(
data: {
"token": widget.userToken
},
indicateLoading: true,
allowNotifyListeners: false,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey,
appBar: AppBar(
title: const Text("Chat mit jemandem"),
),
body: Consumer<TalkChatPacket>(
builder: (context, data, child) {
List<Bubble> messages = List<Bubble>.empty(growable: true);
data.messages.forEach((element) {
messages.add(Bubble(
style: styleSelf,
child: Text(element.content),
));
});
return LoadingPacket(packet: data, child: ListView(
children: [],
));
},
),
// ListView(
// children: [
// Bubble(
// style: styleSystem,
// child: const Text("Chat gestartet"),
// ),
// Bubble(
// style: styleOther,
// child: const Text("Hi, das ist ein Testtext"),
// ),
// Bubble(
// style: styleSelf,
// child: Text(widget.userToken),
// )
// ],
// ),
);
}
}

View File

@ -0,0 +1,27 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class Timetable extends StatefulWidget {
const Timetable({Key? key}) : super(key: key);
@override
State<Timetable> createState() => _TimetableState();
}
class _TimetableState extends State<Timetable> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
List<ListTile> chats = List<ListTile>.empty(growable: true);
return const Center(
child: Text("Not supported"),
);
}
}

View File

@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
class About extends StatelessWidget {
const About({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Über diese App"),
),
body: const Card(
elevation: 1,
borderOnForeground: true,
child: Text("Marianum Fulda"),
),
);
}
}

View File

@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../data/accountModel.dart';
import '../../data/incommingPackets/serverInfoPacket.dart';
import '../../widget/ListItem.dart';
class Settings extends StatefulWidget {
const Settings({Key? key}) : super(key: key);
@override
State<Settings> createState() => _SettingsState();
}
class _SettingsState extends State<Settings> {
@override
void initState() {
super.initState();
ServerInfoPacket().invoke();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Einstellungen"),
),
body: ListView(
children: [
const ListItemNavigator(icon: Icons.info, text: "Über diese App", target: AboutDialog(
applicationIcon: Icon(Icons.send_time_extension_outlined),
applicationLegalese: "Released under MIT-License",
applicationName: "Marianum Fulda",
applicationVersion: "ALPHA 0.1",
)),
ListTile(
leading: const Icon(Icons.logout),
title: const Text("Account abmelden"),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (builder) => AlertDialog(
title: const Text("Abmelden?"),
content: const Text("Möchtest du dich wirklich abmelden?"),
actions: [
TextButton(
child: const Text("Abmelden"),
onPressed: () {
SharedPreferences.getInstance().then((value) => {
value.clear(),
}).then((value) => {
Provider.of<AccountModel>(context, listen: false).logout(),
Navigator.popUntil(context, (route) => !Navigator.canPop(context)),
});
}
),
TextButton(
child: const Text("Abbrechen"),
onPressed: () {
Navigator.pop(context);
},
),
],
)));
},
),
Consumer<ServerInfoPacket>(
builder: (context, serverInfo, child) {
return ListTile(
leading: const Icon(Icons.home_work_outlined),
title: Text("Server: ${serverInfo.serverName}"),
subtitle: Text(
"Betreiber: ${serverInfo.serverOwner}\n"
"Serverversion: ${serverInfo.serverVersion}\n"
"Rechtliche hinweise: ${serverInfo.legal}\n"
),
);
},
)
],
),
);
}
}

28
lib/widget/ListItem.dart Normal file
View File

@ -0,0 +1,28 @@
import 'package:flutter/material.dart';
class ListItemNavigator extends StatelessWidget {
const ListItemNavigator({Key? key, required this.icon, required this.text, required this.target, this.onLongPress, this.arrow = true}) : super(key: key);
final IconData icon;
final String text;
final bool arrow;
final Widget target;
final GestureLongPressCallback? onLongPress;
@override
Widget build(BuildContext context) {
onTabAction() => Navigator.push(context, MaterialPageRoute(builder: (context) => target));
onLongPressAction() => onLongPress;
return ListTile(
leading: Icon(icon),
title: Text(text),
trailing: arrow ? const Icon(Icons.arrow_right) : null,
onTap: onTabAction,
onLongPress: onLongPressAction,
);
}
}

View File

@ -0,0 +1,30 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:marianum_mobile/data/incomingPacket.dart';
import 'package:provider/provider.dart';
class LoadingPacket extends StatefulWidget {
final Widget child;
final IncomingPacket packet;
const LoadingPacket({Key? key, required this.child, required this.packet}) : super(key: key);
@override
State<LoadingPacket> createState() => _LoadingPacketState();
}
class _LoadingPacketState extends State<LoadingPacket> {
@override
Widget build(BuildContext context) {
return widget.packet.isReceived ? widget.child : Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const CircularProgressIndicator(),
Padding(padding: const EdgeInsets.all(20), child: Text("Request: '${widget.packet.packetId}'"))
]
)
);
}
}

View File

@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
class LoadingSpinner extends StatefulWidget {
const LoadingSpinner({Key? key}) : super(key: key);
@override
State<LoadingSpinner> createState() => _LoadingSpinnerState();
}
class _LoadingSpinnerState extends State<LoadingSpinner> {
@override
Widget build(BuildContext context) {
return Dialog(
child: Padding(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(
color: Theme.of(context).primaryColor,
),
const Padding(padding: EdgeInsets.only(left: 15), child: Text("Daten abrufen...")),
],
),
),
);
}
}