Merge branch 'develop' into develop-connectedDoubleLessons
This commit is contained in:
commit
20d7b16ede
1
.gitignore
vendored
1
.gitignore
vendored
@ -349,3 +349,4 @@ hs_err_pid*
|
||||
# End of https://www.toptal.com/developers/gitignore/api/flutter,intellij,androidstudio,xcode,dart
|
||||
|
||||
*.idea*
|
||||
**/.DS_store
|
||||
|
@ -24,6 +24,10 @@ linter:
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
file_names: false
|
||||
prefer_relative_imports: true
|
||||
unnecessary_lambdas: true
|
||||
prefer_single_quotes: true
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
|
@ -5,6 +5,6 @@ class ApiError {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "ApiError: $message";
|
||||
return 'ApiError: $message';
|
||||
}
|
||||
}
|
@ -6,11 +6,11 @@ import 'getHolidaysResponse.dart';
|
||||
|
||||
class GetHolidays {
|
||||
Future<GetHolidaysResponse> query() async {
|
||||
String response = (await http.get(Uri.parse("https://ferien-api.de/api/v1/holidays/HE"))).body;
|
||||
String response = (await http.get(Uri.parse('https://ferien-api.de/api/v1/holidays/HE'))).body;
|
||||
return GetHolidaysResponse(
|
||||
List<GetHolidaysResponseObject>.from(
|
||||
jsonDecode(response).map<GetHolidaysResponseObject>(
|
||||
(dynamic i) => GetHolidaysResponseObject.fromJson(i)
|
||||
GetHolidaysResponseObject.fromJson
|
||||
)
|
||||
)
|
||||
);
|
||||
|
@ -6,7 +6,7 @@ import 'getHolidaysResponse.dart';
|
||||
|
||||
class GetHolidaysCache extends RequestCache<GetHolidaysResponse> {
|
||||
GetHolidaysCache({onUpdate, renew}) : super(RequestCache.cacheDay, onUpdate, renew: renew) {
|
||||
start("MarianumMobile", "state-holidays");
|
||||
start('MarianumMobile', 'state-holidays');
|
||||
}
|
||||
|
||||
@override
|
||||
@ -15,6 +15,7 @@ class GetHolidaysCache extends RequestCache<GetHolidaysResponse> {
|
||||
return GetHolidaysResponse(
|
||||
List<GetHolidaysResponseObject>.from(
|
||||
parsedListJson.map<GetHolidaysResponseObject>(
|
||||
// ignore: unnecessary_lambdas
|
||||
(dynamic i) => GetHolidaysResponseObject.fromJson(i)
|
||||
)
|
||||
)
|
||||
|
@ -11,21 +11,21 @@ import 'autocompleteResponse.dart';
|
||||
class AutocompleteApi {
|
||||
Future<AutocompleteResponse> find(String query) async {
|
||||
Map<String, dynamic> getParameters = {
|
||||
"search": query,
|
||||
"itemType": " ",
|
||||
"itemId": " ",
|
||||
"shareTypes[]": ["0"],
|
||||
"limit": "10",
|
||||
'search': query,
|
||||
'itemType': ' ',
|
||||
'itemId': ' ',
|
||||
'shareTypes[]': ['0'],
|
||||
'limit': '10',
|
||||
};
|
||||
|
||||
Map<String, String> headers = {};
|
||||
headers.putIfAbsent("Accept", () => "application/json");
|
||||
headers.putIfAbsent("OCS-APIRequest", () => "true");
|
||||
headers.putIfAbsent('Accept', () => 'application/json');
|
||||
headers.putIfAbsent('OCS-APIRequest', () => 'true');
|
||||
|
||||
Uri endpoint = Uri.https("${AccountData().buildHttpAuthString()}@${EndpointData().nextcloud().domain}", "${EndpointData().nextcloud().path}/ocs/v2.php/core/autocomplete/get", getParameters);
|
||||
Uri endpoint = Uri.https('${AccountData().buildHttpAuthString()}@${EndpointData().nextcloud().domain}', '${EndpointData().nextcloud().path}/ocs/v2.php/core/autocomplete/get', getParameters);
|
||||
|
||||
Response response = await http.get(endpoint, headers: headers);
|
||||
if(response.statusCode != HttpStatus.ok) throw Exception("Api call failed with ${response.statusCode}: ${response.body}");
|
||||
if(response.statusCode != HttpStatus.ok) throw Exception('Api call failed with ${response.statusCode}: ${response.body}');
|
||||
String result = response.body;
|
||||
return AutocompleteResponse.fromJson(jsonDecode(result)['ocs']);
|
||||
}
|
||||
|
@ -10,14 +10,14 @@ import 'fileSharingApiParams.dart';
|
||||
class FileSharingApi {
|
||||
Future<void> share(FileSharingApiParams query) async {
|
||||
Map<String, String> headers = {};
|
||||
headers.putIfAbsent("Accept", () => "application/json");
|
||||
headers.putIfAbsent("OCS-APIRequest", () => "true");
|
||||
headers.putIfAbsent('Accept', () => 'application/json');
|
||||
headers.putIfAbsent('OCS-APIRequest', () => 'true');
|
||||
|
||||
Uri endpoint = Uri.https("${AccountData().buildHttpAuthString()}@${EndpointData().nextcloud().domain}", "${EndpointData().nextcloud().path}/ocs/v2.php/apps/files_sharing/api/v1/shares", query.toJson().map((key, value) => MapEntry(key, value.toString())));
|
||||
Uri endpoint = Uri.https('${AccountData().buildHttpAuthString()}@${EndpointData().nextcloud().domain}', '${EndpointData().nextcloud().path}/ocs/v2.php/apps/files_sharing/api/v1/shares', query.toJson().map((key, value) => MapEntry(key, value.toString())));
|
||||
Response response = await http.post(endpoint, headers: headers);
|
||||
|
||||
if(response.statusCode != HttpStatus.ok) {
|
||||
throw Exception("Api call failed with ${response.statusCode}: ${response.body}");
|
||||
throw Exception('Api call failed with ${response.statusCode}: ${response.body}');
|
||||
}
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ class GetChat extends TalkApi<GetChatResponse> {
|
||||
String chatToken;
|
||||
|
||||
GetChatParams params;
|
||||
GetChat(this.chatToken, this.params) : super("v1/chat/$chatToken", null, getParameters: params.toJson());
|
||||
GetChat(this.chatToken, this.params) : super('v1/chat/$chatToken', null, getParameters: params.toJson());
|
||||
|
||||
@override
|
||||
assemble(String raw) {
|
||||
|
@ -9,7 +9,7 @@ class GetChatCache extends RequestCache<GetChatResponse> {
|
||||
String chatToken;
|
||||
|
||||
GetChatCache({required onUpdate, required this.chatToken}) : super(RequestCache.cacheNothing, onUpdate) {
|
||||
start("MarianumMobile", "nc-chat-$chatToken");
|
||||
start('MarianumMobile', 'nc-chat-$chatToken');
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -61,21 +61,21 @@ class GetChatResponseObject {
|
||||
|
||||
static GetChatResponseObject getDateDummy(int timestamp) {
|
||||
DateTime elementDate = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
|
||||
return getTextDummy(Jiffy.parseFromDateTime(elementDate).format(pattern: "dd.MM.yyyy"));
|
||||
return getTextDummy(Jiffy.parseFromDateTime(elementDate).format(pattern: 'dd.MM.yyyy'));
|
||||
}
|
||||
|
||||
static GetChatResponseObject getTextDummy(String text) {
|
||||
return GetChatResponseObject(
|
||||
0,
|
||||
"",
|
||||
'',
|
||||
GetRoomResponseObjectMessageActorType.user,
|
||||
"",
|
||||
"",
|
||||
'',
|
||||
'',
|
||||
0,
|
||||
"",
|
||||
'',
|
||||
GetRoomResponseObjectMessageType.system,
|
||||
false,
|
||||
"",
|
||||
'',
|
||||
text,
|
||||
null,
|
||||
null,
|
||||
@ -113,10 +113,10 @@ class RichObjectString {
|
||||
}
|
||||
|
||||
enum RichObjectStringObjectType {
|
||||
@JsonValue("user") user,
|
||||
@JsonValue("group") group,
|
||||
@JsonValue("file") file,
|
||||
@JsonValue("guest") guest,
|
||||
@JsonValue("highlight") highlight,
|
||||
@JsonValue("talk-poll") talkPoll,
|
||||
@JsonValue('user') user,
|
||||
@JsonValue('group') group,
|
||||
@JsonValue('file') file,
|
||||
@JsonValue('guest') guest,
|
||||
@JsonValue('highlight') highlight,
|
||||
@JsonValue('talk-poll') talkPoll,
|
||||
}
|
@ -6,7 +6,7 @@ class RichObjectStringProcessor {
|
||||
if(data == null) return message;
|
||||
|
||||
data.forEach((key, value) {
|
||||
message = message.replaceAll(RegExp("{$key}"), value.name);
|
||||
message = message.replaceAll(RegExp('{$key}'), value.name);
|
||||
});
|
||||
|
||||
return message;
|
||||
|
@ -7,7 +7,7 @@ import 'createRoomParams.dart';
|
||||
class CreateRoom extends TalkApi {
|
||||
CreateRoomParams params;
|
||||
|
||||
CreateRoom(this.params) : super("v4/room", params);
|
||||
CreateRoom(this.params) : super('v4/room', params);
|
||||
|
||||
@override
|
||||
assemble(String raw) {
|
||||
|
@ -7,7 +7,7 @@ import '../talkApi.dart';
|
||||
class DeleteMessage extends TalkApi {
|
||||
String chatToken;
|
||||
int messageId;
|
||||
DeleteMessage(this.chatToken, this.messageId) : super("v1/chat/$chatToken/$messageId", null);
|
||||
DeleteMessage(this.chatToken, this.messageId) : super('v1/chat/$chatToken/$messageId', null);
|
||||
|
||||
@override
|
||||
assemble(String raw) {
|
||||
|
@ -8,7 +8,7 @@ import 'deleteReactMessageParams.dart';
|
||||
class DeleteReactMessage extends TalkApi {
|
||||
String chatToken;
|
||||
int messageId;
|
||||
DeleteReactMessage({required this.chatToken, required this.messageId, required DeleteReactMessageParams params}) : super("v1/reaction/$chatToken/$messageId", params);
|
||||
DeleteReactMessage({required this.chatToken, required this.messageId, required DeleteReactMessageParams params}) : super('v1/reaction/$chatToken/$messageId', params);
|
||||
|
||||
@override
|
||||
assemble(String raw) {
|
||||
|
@ -7,7 +7,7 @@ import 'getParticipantsResponse.dart';
|
||||
|
||||
class GetParticipants extends TalkApi<GetParticipantsResponse> {
|
||||
String token;
|
||||
GetParticipants(this.token) : super("v4/room/$token/participants", null);
|
||||
GetParticipants(this.token) : super('v4/room/$token/participants', null);
|
||||
|
||||
@override
|
||||
GetParticipantsResponse assemble(String raw) {
|
||||
|
@ -8,7 +8,7 @@ class GetParticipantsCache extends RequestCache<GetParticipantsResponse> {
|
||||
String chatToken;
|
||||
|
||||
GetParticipantsCache({required onUpdate, required this.chatToken}) : super(RequestCache.cacheNothing, onUpdate) {
|
||||
start("MarianumMobile", "nc-chat-participants-$chatToken");
|
||||
start('MarianumMobile', 'nc-chat-participants-$chatToken');
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -10,7 +10,7 @@ import 'getReactionsResponse.dart';
|
||||
class GetReactions extends TalkApi<GetReactionsResponse> {
|
||||
String chatToken;
|
||||
int messageId;
|
||||
GetReactions({required this.chatToken, required this.messageId}) : super("v1/reaction/$chatToken/$messageId", null);
|
||||
GetReactions({required this.chatToken, required this.messageId}) : super('v1/reaction/$chatToken/$messageId', null);
|
||||
|
||||
@override
|
||||
assemble(String raw) {
|
||||
|
@ -28,6 +28,6 @@ class GetReactionsResponseObject {
|
||||
}
|
||||
|
||||
enum GetReactionsResponseObjectActorType {
|
||||
@JsonValue("guests") guests,
|
||||
@JsonValue("users") users,
|
||||
@JsonValue('guests') guests,
|
||||
@JsonValue('users') users,
|
||||
}
|
@ -6,7 +6,7 @@ import '../talkApi.dart';
|
||||
class LeaveRoom extends TalkApi {
|
||||
String chatToken;
|
||||
|
||||
LeaveRoom(this.chatToken) : super("v4/room/$chatToken/participants/self", null);
|
||||
LeaveRoom(this.chatToken) : super('v4/room/$chatToken/participants/self', null);
|
||||
|
||||
@override
|
||||
assemble(String raw) {
|
||||
|
@ -8,7 +8,7 @@ import 'reactMessageParams.dart';
|
||||
class ReactMessage extends TalkApi {
|
||||
String chatToken;
|
||||
int messageId;
|
||||
ReactMessage({required this.chatToken, required this.messageId, required ReactMessageParams params}) : super("v1/reaction/$chatToken/$messageId", params);
|
||||
ReactMessage({required this.chatToken, required this.messageId, required ReactMessageParams params}) : super('v1/reaction/$chatToken/$messageId', params);
|
||||
|
||||
@override
|
||||
assemble(String raw) {
|
||||
|
@ -9,7 +9,7 @@ import 'getRoomResponse.dart';
|
||||
|
||||
class GetRoom extends TalkApi<GetRoomResponse> {
|
||||
GetRoomParams params;
|
||||
GetRoom(this.params) : super("v4/room", null, getParameters: params.toJson());
|
||||
GetRoom(this.params) : super('v4/room', null, getParameters: params.toJson());
|
||||
|
||||
|
||||
|
||||
|
@ -8,7 +8,7 @@ import 'getRoomResponse.dart';
|
||||
|
||||
class GetRoomCache extends RequestCache<GetRoomResponse> {
|
||||
GetRoomCache({onUpdate, renew}) : super(RequestCache.cacheMinute, onUpdate, renew: renew) {
|
||||
start("MarianumMobile", "nc-rooms");
|
||||
start('MarianumMobile', 'nc-rooms');
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -19,10 +19,10 @@ class GetRoomResponse extends ApiResponse {
|
||||
final buffer = StringBuffer();
|
||||
|
||||
if(favoritesToTop) {
|
||||
buffer.write(chat.isFavorite ? "b" : "a");
|
||||
buffer.write(chat.isFavorite ? 'b' : 'a');
|
||||
}
|
||||
if(unreadToTop) {
|
||||
buffer.write(chat.unreadMessages > 0 ? "b" : "a");
|
||||
buffer.write(chat.unreadMessages > 0 ? 'b' : 'a');
|
||||
}
|
||||
|
||||
buffer.write(chat.lastActivity);
|
||||
@ -152,16 +152,16 @@ enum GetRoomResponseObjectParticipantNotificationLevel {
|
||||
// }
|
||||
|
||||
enum GetRoomResponseObjectMessageActorType {
|
||||
@JsonValue("deleted_users") deletedUsers,
|
||||
@JsonValue("users") user,
|
||||
@JsonValue("guests") guest,
|
||||
@JsonValue("bots") bot,
|
||||
@JsonValue("bridged") bridge,
|
||||
@JsonValue('deleted_users') deletedUsers,
|
||||
@JsonValue('users') user,
|
||||
@JsonValue('guests') guest,
|
||||
@JsonValue('bots') bot,
|
||||
@JsonValue('bridged') bridge,
|
||||
}
|
||||
|
||||
enum GetRoomResponseObjectMessageType {
|
||||
@JsonValue("comment") comment,
|
||||
@JsonValue("comment_deleted") deletedComment,
|
||||
@JsonValue("system") system,
|
||||
@JsonValue("command") command,
|
||||
@JsonValue('comment') comment,
|
||||
@JsonValue('comment_deleted') deletedComment,
|
||||
@JsonValue('system') system,
|
||||
@JsonValue('command') command,
|
||||
}
|
@ -7,7 +7,7 @@ import 'sendMessageParams.dart';
|
||||
|
||||
class SendMessage extends TalkApi {
|
||||
String chatToken;
|
||||
SendMessage(this.chatToken, SendMessageParams params) : super("v1/chat/$chatToken", params);
|
||||
SendMessage(this.chatToken, SendMessageParams params) : super('v1/chat/$chatToken', params);
|
||||
|
||||
@override
|
||||
assemble(String raw) {
|
||||
|
@ -8,7 +8,7 @@ class SetFavorite extends TalkApi {
|
||||
String chatToken;
|
||||
bool favoriteState;
|
||||
|
||||
SetFavorite(this.chatToken, this.favoriteState) : super("v4/room/$chatToken/favorite", null);
|
||||
SetFavorite(this.chatToken, this.favoriteState) : super('v4/room/$chatToken/favorite', null);
|
||||
|
||||
@override
|
||||
assemble(String raw) {
|
||||
|
@ -10,7 +10,7 @@ class SetReadMarker extends TalkApi {
|
||||
bool readState;
|
||||
SetReadMarkerParams? setReadMarkerParams;
|
||||
|
||||
SetReadMarker(this.chatToken, this.readState, {this.setReadMarkerParams}) : super("v1/chat/$chatToken/read", null, getParameters: setReadMarkerParams?.toJson()) {
|
||||
SetReadMarker(this.chatToken, this.readState, {this.setReadMarkerParams}) : super('v1/chat/$chatToken/read', null, getParameters: setReadMarkerParams?.toJson()) {
|
||||
if(readState) assert(setReadMarkerParams?.lastReadMessage != null);
|
||||
}
|
||||
|
||||
|
@ -34,21 +34,21 @@ abstract class TalkApi<T extends ApiResponse?> extends ApiRequest {
|
||||
getParameters?.update(key, (value) => value.toString());
|
||||
});
|
||||
|
||||
Uri endpoint = Uri.https("${AccountData().buildHttpAuthString()}@${EndpointData().nextcloud().domain}", "${EndpointData().nextcloud().path}/ocs/v2.php/apps/spreed/api/$path", getParameters);
|
||||
Uri endpoint = Uri.https('${AccountData().buildHttpAuthString()}@${EndpointData().nextcloud().domain}', '${EndpointData().nextcloud().path}/ocs/v2.php/apps/spreed/api/$path', getParameters);
|
||||
|
||||
headers ??= {};
|
||||
headers?.putIfAbsent("Accept", () => "application/json");
|
||||
headers?.putIfAbsent("OCS-APIRequest", () => "true");
|
||||
headers?.putIfAbsent('Accept', () => 'application/json');
|
||||
headers?.putIfAbsent('OCS-APIRequest', () => 'true');
|
||||
|
||||
http.Response? data;
|
||||
|
||||
try {
|
||||
data = await request(endpoint, body, headers);
|
||||
if(data == null) throw Exception("No response Data");
|
||||
if(data == null) throw Exception('No response Data');
|
||||
if(data.statusCode >= 400 || data.statusCode < 200) throw Exception("Response status code '${data.statusCode}' might indicate an error");
|
||||
} catch(e) {
|
||||
log(e.toString());
|
||||
throw ApiError("Request $endpoint could not be dispatched: ${e.toString()}");
|
||||
throw ApiError('Request $endpoint could not be dispatched: ${e.toString()}');
|
||||
}
|
||||
//dynamic jsonData = jsonDecode(data.body);
|
||||
|
||||
@ -59,10 +59,10 @@ abstract class TalkApi<T extends ApiResponse?> extends ApiRequest {
|
||||
return assembled;
|
||||
} catch (e) {
|
||||
// TODO report error
|
||||
log("Error assembling Talk API ${T.toString()} message: ${e.toString()} response on ${endpoint.path} with request body: $body and request headers: ${headers.toString()}");
|
||||
log('Error assembling Talk API ${T.toString()} message: ${e.toString()} response on ${endpoint.path} with request body: $body and request headers: ${headers.toString()}');
|
||||
}
|
||||
|
||||
throw Exception("Error assembling Talk API response");
|
||||
throw Exception('Error assembling Talk API response');
|
||||
}
|
||||
|
||||
}
|
@ -7,6 +7,6 @@ class TalkError {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "Talk - $status - ($code): $message";
|
||||
return 'Talk - $status - ($code): $message';
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ class CacheableFile {
|
||||
CacheableFile.fromDavFile(WebDavFile file) {
|
||||
path = file.path.path;
|
||||
isDirectory = file.isDirectory;
|
||||
name = file.isDirectory ? file.name : file.path.path.split("/").last;
|
||||
name = file.isDirectory ? file.name : file.path.path.split('/').last;
|
||||
mimeType = file.mimeType;
|
||||
size = file.size;
|
||||
eTag = file.etag;
|
||||
|
@ -1,7 +1,6 @@
|
||||
|
||||
import 'package:nextcloud/nextcloud.dart';
|
||||
|
||||
import '../../../../../model/endpointData.dart';
|
||||
import '../../webdavApi.dart';
|
||||
import 'cacheableFile.dart';
|
||||
import 'listFilesParams.dart';
|
||||
@ -15,18 +14,19 @@ class ListFiles extends WebdavApi<ListFilesParams> {
|
||||
@override
|
||||
Future<ListFilesResponse> run() async {
|
||||
List<WebDavFile> davFiles = (await (await WebdavApi.webdav).propfind(PathUri.parse(params.path))).toWebDavFiles();
|
||||
Set<CacheableFile> files = davFiles.map((e) => CacheableFile.fromDavFile(e)).toSet();
|
||||
Set<CacheableFile> files = davFiles.map(CacheableFile.fromDavFile).toSet();
|
||||
|
||||
// webdav handles subdirectories wrong, this is a fix
|
||||
if(EndpointData().getEndpointMode() == EndpointMode.stage) {
|
||||
files = files.map((e) { // somehow
|
||||
e.path = e.path.split("mobile/cloud/remote.php/webdav")[1];
|
||||
return e;
|
||||
}).toSet();
|
||||
}
|
||||
// currently this fix is not needed anymore
|
||||
// if(EndpointData().getEndpointMode() == EndpointMode.stage) {
|
||||
// files = files.map((e) { // somehow
|
||||
// e.path = e.path.split("mobile/cloud/remote.php/webdav")[1];
|
||||
// return e;
|
||||
// }).toSet();
|
||||
// }
|
||||
|
||||
// somehow the current working folder is also listed, it is filtered here.
|
||||
files.removeWhere((element) => element.path == "/${params.path}/" || element.path == "/");
|
||||
files.removeWhere((element) => element.path == '/${params.path}/' || element.path == '/');
|
||||
|
||||
return ListFilesResponse(files);
|
||||
}
|
||||
|
@ -10,9 +10,9 @@ class ListFilesCache extends RequestCache<ListFilesResponse> {
|
||||
String path;
|
||||
|
||||
ListFilesCache({required onUpdate, required this.path}) : super(RequestCache.cacheNothing, onUpdate) {
|
||||
var bytes = utf8.encode("MarianumMobile-$path");
|
||||
var bytes = utf8.encode('MarianumMobile-$path');
|
||||
String cacheName = md5.convert(bytes).toString();
|
||||
start("MarianumMobile", "wd-folder-$cacheName");
|
||||
start('MarianumMobile', 'wd-folder-$cacheName');
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -35,7 +35,7 @@ class ListFilesResponse extends ApiResponse {
|
||||
|
||||
switch(sortOption) {
|
||||
case SortOption.date:
|
||||
buffer.write(Jiffy.parseFromMillisecondsSinceEpoch(file.modifiedAt?.millisecondsSinceEpoch ?? 0).format(pattern: "yyyyMMddhhmmss"));
|
||||
buffer.write(Jiffy.parseFromMillisecondsSinceEpoch(file.modifiedAt?.millisecondsSinceEpoch ?? 0).format(pattern: 'yyyyMMddhhmmss'));
|
||||
break;
|
||||
|
||||
case SortOption.name:
|
||||
|
@ -18,10 +18,10 @@ abstract class WebdavApi<T> extends ApiRequest {
|
||||
static Future<String> webdavConnectString = buildWebdavConnectString();
|
||||
|
||||
static Future<WebDavClient> establishWebdavConnection() async {
|
||||
return NextcloudClient(Uri.parse("https://${EndpointData().nextcloud().full()}"), password: AccountData().getPassword(), loginName: AccountData().getUsername()).webdav;
|
||||
return NextcloudClient(Uri.parse('https://${EndpointData().nextcloud().full()}'), password: AccountData().getPassword(), loginName: AccountData().getUsername()).webdav;
|
||||
}
|
||||
|
||||
static Future<String> buildWebdavConnectString() async {
|
||||
return "https://${AccountData().buildHttpAuthString()}@${EndpointData().nextcloud().full()}/remote.php/dav/files/${AccountData().getUsername()}/";
|
||||
return 'https://${AccountData().buildHttpAuthString()}@${EndpointData().nextcloud().full()}/remote.php/dav/files/${AccountData().getUsername()}/';
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ import '../../mhslApi.dart';
|
||||
import 'getBreakersResponse.dart';
|
||||
|
||||
class GetBreakers extends MhslApi<GetBreakersResponse> {
|
||||
GetBreakers() : super("breaker/");
|
||||
GetBreakers() : super('breaker/');
|
||||
|
||||
@override
|
||||
GetBreakersResponse assemble(String raw) {
|
||||
|
@ -6,7 +6,7 @@ import 'getBreakersResponse.dart';
|
||||
|
||||
class GetBreakersCache extends RequestCache<GetBreakersResponse> {
|
||||
GetBreakersCache({onUpdate, renew}) : super(RequestCache.cacheMinute, onUpdate, renew: renew) {
|
||||
start("MarianumMobile", "breakers");
|
||||
start('MarianumMobile', 'breakers');
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -28,9 +28,9 @@ class GetBreakersReponseObject {
|
||||
}
|
||||
|
||||
enum BreakerArea {
|
||||
@JsonValue("GLOBAL") global,
|
||||
@JsonValue("TIMETABLE") timetable,
|
||||
@JsonValue("TALK") talk,
|
||||
@JsonValue("FILES") files,
|
||||
@JsonValue("MORE") more,
|
||||
@JsonValue('GLOBAL') global,
|
||||
@JsonValue('TIMETABLE') timetable,
|
||||
@JsonValue('TALK') talk,
|
||||
@JsonValue('FILES') files,
|
||||
@JsonValue('MORE') more,
|
||||
}
|
@ -13,6 +13,7 @@ class CustomTimetableEvent {
|
||||
DateTime startDate;
|
||||
@JsonKey(toJson: MhslApi.dateTimeToJson, fromJson: MhslApi.dateTimeFromJson)
|
||||
DateTime endDate;
|
||||
String? color;
|
||||
String rrule;
|
||||
@JsonKey(toJson: MhslApi.dateTimeToJson, fromJson: MhslApi.dateTimeFromJson)
|
||||
DateTime createdAt;
|
||||
@ -20,7 +21,7 @@ class CustomTimetableEvent {
|
||||
DateTime updatedAt;
|
||||
|
||||
CustomTimetableEvent({required this.id, required this.title, required this.description, required this.startDate,
|
||||
required this.endDate, required this.rrule, required this.createdAt, required this.updatedAt});
|
||||
required this.endDate, required this.color, required this.rrule, required this.createdAt, required this.updatedAt});
|
||||
|
||||
factory CustomTimetableEvent.fromJson(Map<String, dynamic> json) => _$CustomTimetableEventFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$CustomTimetableEventToJson(this);
|
||||
|
@ -14,6 +14,7 @@ CustomTimetableEvent _$CustomTimetableEventFromJson(
|
||||
description: json['description'] as String,
|
||||
startDate: MhslApi.dateTimeFromJson(json['startDate'] as String),
|
||||
endDate: MhslApi.dateTimeFromJson(json['endDate'] as String),
|
||||
color: json['color'] as String?,
|
||||
rrule: json['rrule'] as String,
|
||||
createdAt: MhslApi.dateTimeFromJson(json['createdAt'] as String),
|
||||
updatedAt: MhslApi.dateTimeFromJson(json['updatedAt'] as String),
|
||||
@ -27,6 +28,7 @@ Map<String, dynamic> _$CustomTimetableEventToJson(
|
||||
'description': instance.description,
|
||||
'startDate': MhslApi.dateTimeToJson(instance.startDate),
|
||||
'endDate': MhslApi.dateTimeToJson(instance.endDate),
|
||||
'color': instance.color,
|
||||
'rrule': instance.rrule,
|
||||
'createdAt': MhslApi.dateTimeToJson(instance.createdAt),
|
||||
'updatedAt': MhslApi.dateTimeToJson(instance.updatedAt),
|
||||
|
@ -9,11 +9,11 @@ import 'getCustomTimetableEventResponse.dart';
|
||||
|
||||
class GetCustomTimetableEvent extends MhslApi<GetCustomTimetableEventResponse> {
|
||||
GetCustomTimetableEventParams params;
|
||||
GetCustomTimetableEvent(this.params) : super("server/timetable/customEvents?user=${params.user}");
|
||||
GetCustomTimetableEvent(this.params) : super('server/timetable/customEvents?user=${params.user}');
|
||||
|
||||
@override
|
||||
GetCustomTimetableEventResponse assemble(String raw) {
|
||||
return GetCustomTimetableEventResponse.fromJson({"events": jsonDecode(raw)});
|
||||
return GetCustomTimetableEventResponse.fromJson({'events': jsonDecode(raw)});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -9,7 +9,7 @@ class GetCustomTimetableEventCache extends RequestCache<GetCustomTimetableEventR
|
||||
GetCustomTimetableEventParams params;
|
||||
|
||||
GetCustomTimetableEventCache(this.params, {onUpdate, renew}) : super(RequestCache.cacheMinute, onUpdate, renew: renew) {
|
||||
start("MarianumMobile", "customTimetableEvents");
|
||||
start('MarianumMobile', 'customTimetableEvents');
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -6,7 +6,7 @@ import '../../mhslApi.dart';
|
||||
import 'getMessagesResponse.dart';
|
||||
|
||||
class GetMessages extends MhslApi<GetMessagesResponse> {
|
||||
GetMessages() : super("message/messages.json");
|
||||
GetMessages() : super('message/messages.json');
|
||||
|
||||
|
||||
@override
|
||||
|
@ -6,7 +6,7 @@ import 'getMessagesResponse.dart';
|
||||
|
||||
class GetMessagesCache extends RequestCache<GetMessagesResponse> {
|
||||
GetMessagesCache({onUpdate, renew}) : super(RequestCache.cacheMinute, onUpdate, renew: renew) {
|
||||
start("MarianumMobile", "message");
|
||||
start('MarianumMobile', 'message');
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -15,21 +15,21 @@ abstract class MhslApi<T> extends ApiRequest {
|
||||
T assemble(String raw);
|
||||
|
||||
Future<T> run() async {
|
||||
Uri endpoint = Uri.parse("https://mhsl.eu/marianum/marianummobile/$subpath");
|
||||
Uri endpoint = Uri.parse('https://mhsl.eu/marianum/marianummobile/$subpath');
|
||||
|
||||
http.Response? data = await request(endpoint);
|
||||
if(data == null) {
|
||||
throw ApiError("Request could not be dispatched!");
|
||||
throw ApiError('Request could not be dispatched!');
|
||||
}
|
||||
|
||||
if(data.statusCode > 299) {
|
||||
throw ApiError("Non 200 Status code from mhsl services: $subpath: ${data.statusCode}");
|
||||
throw ApiError('Non 200 Status code from mhsl services: $subpath: ${data.statusCode}');
|
||||
}
|
||||
|
||||
return assemble(utf8.decode(data.bodyBytes));
|
||||
}
|
||||
|
||||
static String dateTimeToJson(DateTime time) => Jiffy.parseFromDateTime(time).format(pattern: "yyyy-MM-dd HH:mm:ss");
|
||||
static String dateTimeToJson(DateTime time) => Jiffy.parseFromDateTime(time).format(pattern: 'yyyy-MM-dd HH:mm:ss');
|
||||
static DateTime dateTimeFromJson(String time) => DateTime.parse(time);
|
||||
|
||||
}
|
@ -9,7 +9,7 @@ import 'notifyRegisterParams.dart';
|
||||
|
||||
class NotifyRegister extends MhslApi<void> {
|
||||
NotifyRegisterParams params;
|
||||
NotifyRegister(this.params) : super("notify/register/");
|
||||
NotifyRegister(this.params) : super('notify/register/');
|
||||
|
||||
|
||||
@override
|
||||
|
@ -6,14 +6,12 @@ part 'addFeedbackParams.g.dart';
|
||||
class AddFeedbackParams {
|
||||
String user;
|
||||
String feedback;
|
||||
String? screenshot;
|
||||
int appVersion;
|
||||
|
||||
|
||||
AddFeedbackParams({
|
||||
required this.user,
|
||||
required this.feedback,
|
||||
this.screenshot,
|
||||
required this.appVersion,
|
||||
});
|
||||
|
||||
|
@ -10,7 +10,6 @@ AddFeedbackParams _$AddFeedbackParamsFromJson(Map<String, dynamic> json) =>
|
||||
AddFeedbackParams(
|
||||
user: json['user'] as String,
|
||||
feedback: json['feedback'] as String,
|
||||
screenshot: json['screenshot'] as String?,
|
||||
appVersion: json['appVersion'] as int,
|
||||
);
|
||||
|
||||
@ -18,6 +17,5 @@ Map<String, dynamic> _$AddFeedbackParamsToJson(AddFeedbackParams instance) =>
|
||||
<String, dynamic>{
|
||||
'user': instance.user,
|
||||
'feedback': instance.feedback,
|
||||
'screenshot': instance.screenshot,
|
||||
'appVersion': instance.appVersion,
|
||||
};
|
||||
|
@ -12,7 +12,7 @@ import 'updateUserIndexParams.dart';
|
||||
|
||||
class UpdateUserIndex extends MhslApi<void> {
|
||||
UpdateUserIndexParams params;
|
||||
UpdateUserIndex(this.params) : super("server/userIndex/update");
|
||||
UpdateUserIndex(this.params) : super('server/userIndex/update');
|
||||
|
||||
@override
|
||||
void assemble(String raw) {}
|
||||
@ -20,7 +20,7 @@ class UpdateUserIndex extends MhslApi<void> {
|
||||
@override
|
||||
Future<http.Response> request(Uri uri) {
|
||||
String data = jsonEncode(params.toJson());
|
||||
log("Updating userindex:\n $data");
|
||||
log('Updating userindex:\n $data');
|
||||
return http.post(uri, body: data);
|
||||
}
|
||||
|
||||
|
@ -35,8 +35,8 @@ abstract class RequestCache<T extends ApiResponse?> {
|
||||
onUpdate(newValue);
|
||||
|
||||
Localstore.instance.collection(file).doc(document).set({
|
||||
"json": jsonEncode(newValue),
|
||||
"lastupdate": DateTime.now().millisecondsSinceEpoch
|
||||
'json': jsonEncode(newValue),
|
||||
'lastupdate': DateTime.now().millisecondsSinceEpoch
|
||||
});
|
||||
} on WebuntisError catch(e) {
|
||||
onError(e);
|
||||
|
@ -9,7 +9,7 @@ import 'authenticateResponse.dart';
|
||||
class Authenticate extends WebuntisApi {
|
||||
AuthenticateParams param;
|
||||
|
||||
Authenticate(this.param) : super("authenticate", param, authenticatedResponse: false);
|
||||
Authenticate(this.param) : super('authenticate', param, authenticatedResponse: false);
|
||||
|
||||
@override
|
||||
Future<AuthenticateResponse> run() async {
|
||||
|
@ -4,7 +4,7 @@ import '../../webuntisApi.dart';
|
||||
import 'getHolidaysResponse.dart';
|
||||
|
||||
class GetHolidays extends WebuntisApi {
|
||||
GetHolidays() : super("getHolidays", null);
|
||||
GetHolidays() : super('getHolidays', null);
|
||||
|
||||
@override
|
||||
Future<GetHolidaysResponse> run() async {
|
||||
|
@ -6,7 +6,7 @@ import 'getHolidaysResponse.dart';
|
||||
|
||||
class GetHolidaysCache extends RequestCache<GetHolidaysResponse> {
|
||||
GetHolidaysCache({onUpdate}) : super(RequestCache.cacheDay, onUpdate) {
|
||||
start("MarianumMobile", "wu-holidays");
|
||||
start('MarianumMobile', 'wu-holidays');
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -5,7 +5,7 @@ import '../../webuntisApi.dart';
|
||||
import 'getRoomsResponse.dart';
|
||||
|
||||
class GetRooms extends WebuntisApi {
|
||||
GetRooms() : super("getRooms", null);
|
||||
GetRooms() : super('getRooms', null);
|
||||
|
||||
@override
|
||||
Future<GetRoomsResponse> run() async {
|
||||
@ -14,10 +14,10 @@ class GetRooms extends WebuntisApi {
|
||||
return finalize(GetRoomsResponse.fromJson(jsonDecode(rawAnswer)));
|
||||
} catch(e, trace) {
|
||||
log(trace.toString());
|
||||
log("Failed to parse getRoom data with server response: $rawAnswer");
|
||||
log('Failed to parse getRoom data with server response: $rawAnswer');
|
||||
}
|
||||
|
||||
throw Exception("Failed to parse getRoom server response: $rawAnswer");
|
||||
throw Exception('Failed to parse getRoom server response: $rawAnswer');
|
||||
}
|
||||
|
||||
}
|
@ -6,7 +6,7 @@ import 'getRoomsResponse.dart';
|
||||
|
||||
class GetRoomsCache extends RequestCache<GetRoomsResponse> {
|
||||
GetRoomsCache({onUpdate}) : super(RequestCache.cacheHour, onUpdate) {
|
||||
start("MarianumMobile", "wu-rooms");
|
||||
start('MarianumMobile', 'wu-rooms');
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -4,7 +4,7 @@ import '../../webuntisApi.dart';
|
||||
import 'getSubjectsResponse.dart';
|
||||
|
||||
class GetSubjects extends WebuntisApi {
|
||||
GetSubjects() : super("getSubjects", null);
|
||||
GetSubjects() : super('getSubjects', null);
|
||||
|
||||
@override
|
||||
Future<GetSubjectsResponse> run() async {
|
||||
|
@ -6,7 +6,7 @@ import 'getSubjectsResponse.dart';
|
||||
|
||||
class GetSubjectsCache extends RequestCache<GetSubjectsResponse> {
|
||||
GetSubjectsCache({onUpdate}) : super(RequestCache.cacheHour, onUpdate) {
|
||||
start("MarianumMobile", "wu-subjects");
|
||||
start('MarianumMobile', 'wu-subjects');
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -7,7 +7,7 @@ import 'getTimetableResponse.dart';
|
||||
class GetTimetable extends WebuntisApi {
|
||||
GetTimetableParams params;
|
||||
|
||||
GetTimetable(this.params) : super("getTimetable", params);
|
||||
GetTimetable(this.params) : super('getTimetable', params);
|
||||
|
||||
@override
|
||||
Future<GetTimetableResponse> run() async {
|
||||
|
@ -11,7 +11,7 @@ class GetTimetableCache extends RequestCache<GetTimetableResponse> {
|
||||
int enddate;
|
||||
|
||||
GetTimetableCache({required onUpdate, onError, required this.startdate, required this.enddate}) : super(RequestCache.cacheMinute, onUpdate, onError: onError) {
|
||||
start("MarianumMobile", "wu-timetable-$startdate-$enddate");
|
||||
start('MarianumMobile', 'wu-timetable-$startdate-$enddate');
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -67,10 +67,10 @@ class GetTimetableParamsOptions {
|
||||
}
|
||||
|
||||
enum GetTimetableParamsOptionsFields {
|
||||
@JsonValue("id") id,
|
||||
@JsonValue("name") name,
|
||||
@JsonValue("longname") longname,
|
||||
@JsonValue("externalkey") externalkey;
|
||||
@JsonValue('id') id,
|
||||
@JsonValue('name') name,
|
||||
@JsonValue('longname') longname,
|
||||
@JsonValue('externalkey') externalkey;
|
||||
|
||||
static List<GetTimetableParamsOptionsFields> all = [id, name, longname, externalkey];
|
||||
}
|
||||
@ -88,7 +88,7 @@ class GetTimetableParamsOptionsElement {
|
||||
}
|
||||
|
||||
enum GetTimetableParamsOptionsElementKeyType {
|
||||
@JsonValue("id") id,
|
||||
@JsonValue("name") name,
|
||||
@JsonValue("externalkey") externalkey
|
||||
@JsonValue('id') id,
|
||||
@JsonValue('name') name,
|
||||
@JsonValue('externalkey') externalkey
|
||||
}
|
@ -9,7 +9,7 @@ import 'queries/authenticate/authenticate.dart';
|
||||
import 'webuntisError.dart';
|
||||
|
||||
abstract class WebuntisApi extends ApiRequest {
|
||||
Uri endpoint = Uri.parse("https://${EndpointData().webuntis().full()}/WebUntis/jsonrpc.do?school=marianum-fulda");
|
||||
Uri endpoint = Uri.parse('https://${EndpointData().webuntis().full()}/WebUntis/jsonrpc.do?school=marianum-fulda');
|
||||
String method;
|
||||
ApiParams? genericParam;
|
||||
http.Response? response;
|
||||
@ -22,11 +22,11 @@ abstract class WebuntisApi extends ApiRequest {
|
||||
Future<String> query(WebuntisApi untis) async {
|
||||
String query = '{"id":"ID","method":"$method","params":${untis._body()},"jsonrpc":"2.0"}';
|
||||
|
||||
String sessionId = "0";
|
||||
String sessionId = '0';
|
||||
if(authenticatedResponse) {
|
||||
sessionId = (await Authenticate.getSession()).sessionId;
|
||||
}
|
||||
http.Response data = await post(query, {"Cookie": "JSESSIONID=$sessionId"});
|
||||
http.Response data = await post(query, {'Cookie': 'JSESSIONID=$sessionId'});
|
||||
response = data;
|
||||
|
||||
dynamic jsonData = jsonDecode(data.body);
|
||||
@ -49,7 +49,7 @@ abstract class WebuntisApi extends ApiRequest {
|
||||
Future<ApiResponse> run();
|
||||
|
||||
String _body() {
|
||||
return genericParam == null ? "{}" : jsonEncode(genericParam);
|
||||
return genericParam == null ? '{}' : jsonEncode(genericParam);
|
||||
}
|
||||
|
||||
Future<http.Response> post(String data, Map<String, String>? headers) async {
|
||||
@ -57,7 +57,7 @@ abstract class WebuntisApi extends ApiRequest {
|
||||
.post(endpoint, body: data, headers: headers)
|
||||
.timeout(
|
||||
const Duration(seconds: 10),
|
||||
onTimeout: () => throw WebuntisError("Timeout", 1)
|
||||
onTimeout: () => throw WebuntisError('Timeout', 1)
|
||||
);
|
||||
}
|
||||
}
|
@ -6,6 +6,6 @@ class WebuntisError implements Exception {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "WebUntis ($code): $message";
|
||||
return 'WebUntis ($code): $message';
|
||||
}
|
||||
}
|
134
lib/app.dart
134
lib/app.dart
@ -5,7 +5,7 @@ import 'dart:developer';
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:persistent_bottom_nav_bar_v2/persistent-tab-view.dart';
|
||||
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:badges/badges.dart' as badges;
|
||||
|
||||
@ -39,14 +39,14 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
log("AppLifecycle: ${state.toString()}");
|
||||
log('AppLifecycle: ${state.toString()}');
|
||||
|
||||
if(state == AppLifecycleState.resumed) {
|
||||
EasyThrottle.throttle(
|
||||
"appLifecycleState",
|
||||
'appLifecycleState',
|
||||
const Duration(seconds: 10),
|
||||
() {
|
||||
log("Refreshing due to LifecycleChange");
|
||||
log('Refreshing due to LifecycleChange');
|
||||
NotificationTasks.updateProviders(context);
|
||||
Provider.of<TimetableProps>(context, listen: false).run();
|
||||
}
|
||||
@ -93,67 +93,75 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PersistentTabView(
|
||||
context,
|
||||
controller: App.bottomNavigator,
|
||||
navBarStyle: NavBarStyle.style6,
|
||||
hideNavigationBarWhenKeyboardShows: true,
|
||||
navBarHeight: MediaQuery.of(context).viewInsets.bottom > 0 ? 0.0 : kBottomNavigationBarHeight,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
decoration: const NavBarDecoration(
|
||||
border: Border(top: BorderSide(width: 1, color: Colors.grey)),
|
||||
),
|
||||
screenTransitionAnimation: const ScreenTransitionAnimation(animateTabTransition: true, curve: Curves.ease, duration: Duration(milliseconds: 200)),
|
||||
screens: const [
|
||||
Breaker(breaker: BreakerArea.timetable, child: Timetable()),
|
||||
Breaker(breaker: BreakerArea.talk, child: ChatList()),
|
||||
Breaker(breaker: BreakerArea.files, child: Files([])),
|
||||
Breaker(breaker: BreakerArea.more, child: Overhang()),
|
||||
],
|
||||
items: [
|
||||
PersistentBottomNavBarItem(
|
||||
activeColorPrimary: Theme.of(context).primaryColor,
|
||||
inactiveColorPrimary: Theme.of(context).colorScheme.secondary,
|
||||
icon: const Icon(Icons.calendar_month),
|
||||
title: "Vertretung"
|
||||
),
|
||||
PersistentBottomNavBarItem(
|
||||
activeColorPrimary: Theme.of(context).primaryColor,
|
||||
inactiveColorPrimary: Theme.of(context).colorScheme.secondary,
|
||||
icon: Consumer<ChatListProps>(
|
||||
builder: (context, value, child) {
|
||||
if(value.primaryLoading()) return const Icon(Icons.chat);
|
||||
int messages = value.getRoomsResponse.data.map((e) => e.unreadMessages).reduce((a, b) => a+b);
|
||||
return badges.Badge(
|
||||
showBadge: messages > 0,
|
||||
position: badges.BadgePosition.topEnd(top: -3, end: -3),
|
||||
stackFit: StackFit.loose,
|
||||
badgeStyle: badges.BadgeStyle(
|
||||
padding: const EdgeInsets.all(3),
|
||||
badgeColor: Theme.of(context).primaryColor,
|
||||
elevation: 1,
|
||||
),
|
||||
badgeContent: Text("$messages", style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold)),
|
||||
child: const Icon(Icons.chat),
|
||||
);
|
||||
},
|
||||
),
|
||||
title: "Talk",
|
||||
),
|
||||
PersistentBottomNavBarItem(
|
||||
activeColorPrimary: Theme.of(context).primaryColor,
|
||||
inactiveColorPrimary: Theme.of(context).colorScheme.secondary,
|
||||
icon: const Icon(Icons.folder),
|
||||
title: "Dateien"
|
||||
),
|
||||
PersistentBottomNavBarItem(
|
||||
activeColorPrimary: Theme.of(context).primaryColor,
|
||||
inactiveColorPrimary: Theme.of(context).colorScheme.secondary,
|
||||
icon: const Icon(Icons.apps),
|
||||
title: "Mehr"
|
||||
),
|
||||
],
|
||||
);
|
||||
gestureNavigationEnabled: true,
|
||||
navBarOverlap: const NavBarOverlap.none(),
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
|
||||
screenTransitionAnimation: const ScreenTransitionAnimation(curve: Curves.easeOutQuad, duration: Duration(milliseconds: 200)),
|
||||
tabs: [
|
||||
PersistentTabConfig(
|
||||
screen: const Breaker(breaker: BreakerArea.timetable, child: Timetable()),
|
||||
item: ItemConfig(
|
||||
activeForegroundColor: Theme.of(context).primaryColor,
|
||||
inactiveForegroundColor: Theme.of(context).colorScheme.secondary,
|
||||
icon: const Icon(Icons.calendar_month),
|
||||
title: 'Vertretung'
|
||||
),
|
||||
),
|
||||
PersistentTabConfig(
|
||||
screen: const Breaker(breaker: BreakerArea.talk, child: ChatList()),
|
||||
item: ItemConfig(
|
||||
activeForegroundColor: Theme.of(context).primaryColor,
|
||||
inactiveForegroundColor: Theme.of(context).colorScheme.secondary,
|
||||
icon: Consumer<ChatListProps>(
|
||||
builder: (context, value, child) {
|
||||
if(value.primaryLoading()) return const Icon(Icons.chat);
|
||||
int messages = value.getRoomsResponse.data.map((e) => e.unreadMessages).reduce((a, b) => a+b);
|
||||
return badges.Badge(
|
||||
showBadge: messages > 0,
|
||||
position: badges.BadgePosition.topEnd(top: -3, end: -3),
|
||||
stackFit: StackFit.loose,
|
||||
badgeStyle: badges.BadgeStyle(
|
||||
padding: const EdgeInsets.all(3),
|
||||
badgeColor: Theme.of(context).primaryColor,
|
||||
elevation: 1,
|
||||
),
|
||||
badgeContent: Text('$messages', style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold)),
|
||||
child: const Icon(Icons.chat),
|
||||
);
|
||||
},
|
||||
),
|
||||
title: 'Talk',
|
||||
),
|
||||
),
|
||||
PersistentTabConfig(
|
||||
screen: const Breaker(breaker: BreakerArea.files, child: Files([])),
|
||||
item: ItemConfig(
|
||||
activeForegroundColor: Theme.of(context).primaryColor,
|
||||
inactiveForegroundColor: Theme.of(context).colorScheme.secondary,
|
||||
icon: const Icon(Icons.folder),
|
||||
title: 'Dateien'
|
||||
),
|
||||
),
|
||||
PersistentTabConfig(
|
||||
screen: const Breaker(breaker: BreakerArea.more, child: Overhang()),
|
||||
item: ItemConfig(
|
||||
activeForegroundColor: Theme.of(context).primaryColor,
|
||||
inactiveForegroundColor: Theme.of(context).colorScheme.secondary,
|
||||
icon: const Icon(Icons.apps),
|
||||
title: 'Mehr'
|
||||
),
|
||||
),
|
||||
],
|
||||
navBarBuilder: (config) => Style6BottomNavBar(
|
||||
navBarConfig: config,
|
||||
navBarDecoration: NavBarDecoration(
|
||||
border: const Border(top: BorderSide(width: 1, color: Colors.grey)),
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
5
lib/extensions/renderNotNull.dart
Normal file
5
lib/extensions/renderNotNull.dart
Normal file
@ -0,0 +1,5 @@
|
||||
extension RenderNotNullExt<T> on T? {
|
||||
R? wrapNullable<R>(R Function(T data) defaultValueCallback) {
|
||||
return this != null ? defaultValueCallback(this as T) : null;
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:feedback/feedback.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
@ -30,7 +29,6 @@ import 'storage/base/settingsProvider.dart';
|
||||
import 'theming/darkAppTheme.dart';
|
||||
import 'theming/lightAppTheme.dart';
|
||||
import 'view/login/login.dart';
|
||||
import 'view/pages/more/feedback/feedbackForm.dart';
|
||||
import 'widget/placeholderView.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
@ -40,7 +38,7 @@ Future<void> main() async {
|
||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||
log("Firebase token: ${await FirebaseMessaging.instance.getToken() ?? "Error: no Firebase token!"}");
|
||||
} catch (e) {
|
||||
log("Error initializing Firebase app!");
|
||||
log('Error initializing Firebase app!');
|
||||
}
|
||||
|
||||
ByteData data = await PlatformAssetBundle().load('assets/ca/lets-encrypt-r3.pem');
|
||||
@ -71,12 +69,7 @@ Future<void> main() async {
|
||||
ChangeNotifierProvider(create: (context) => MessageProps()),
|
||||
ChangeNotifierProvider(create: (context) => HolidaysProps()),
|
||||
],
|
||||
child: BetterFeedback(
|
||||
themeMode: ThemeMode.dark,
|
||||
feedbackBuilder: (context, callback, scrollController) => FeedbackForm(callback: callback, scrollController: scrollController),
|
||||
localeOverride: const Locale('de'),
|
||||
child: const Main(),
|
||||
),
|
||||
child: const Main(),
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -93,7 +86,7 @@ class _MainState extends State<Main> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
Jiffy.setLocale("de");
|
||||
Jiffy.setLocale('de');
|
||||
|
||||
AccountData().waitForPopulation().then((value) {
|
||||
Provider.of<AccountModel>(context, listen: false)
|
||||
@ -113,7 +106,12 @@ class _MainState extends State<Main> {
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Consumer<SettingsProvider>(
|
||||
builder: (context, settings, child) {
|
||||
var devToolsSettings = settings.val().devToolsSettings;
|
||||
return MaterialApp(
|
||||
showPerformanceOverlay: devToolsSettings.showPerformanceOverlay,
|
||||
checkerboardOffscreenLayers: devToolsSettings.checkerboardOffscreenLayers,
|
||||
checkerboardRasterCacheImages: devToolsSettings.checkerboardRasterCacheImages,
|
||||
|
||||
debugShowCheckedModeBanner: false,
|
||||
localizationsDelegates: const [
|
||||
...GlobalMaterialLocalizations.delegates,
|
||||
@ -132,16 +130,16 @@ class _MainState extends State<Main> {
|
||||
darkTheme: DarkAppTheme.theme,
|
||||
home: LoaderOverlay(
|
||||
child: Breaker(
|
||||
breaker: BreakerArea.global,
|
||||
child: Consumer<AccountModel>(
|
||||
builder: (context, accountModel, child) {
|
||||
switch(accountModel.state) {
|
||||
case AccountModelState.loggedIn: return const App();
|
||||
case AccountModelState.loggedOut: return const Login();
|
||||
case AccountModelState.undefined: return const PlaceholderView(icon: Icons.timer, text: "Daten werden geladen");
|
||||
}
|
||||
},
|
||||
)
|
||||
breaker: BreakerArea.global,
|
||||
child: Consumer<AccountModel>(
|
||||
builder: (context, accountModel, child) {
|
||||
switch(accountModel.state) {
|
||||
case AccountModelState.loggedIn: return const App();
|
||||
case AccountModelState.loggedOut: return const Login();
|
||||
case AccountModelState.undefined: return const PlaceholderView(icon: Icons.timer, text: 'Daten werden geladen');
|
||||
}
|
||||
},
|
||||
)
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -10,8 +10,8 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'accountModel.dart';
|
||||
|
||||
class AccountData {
|
||||
static const _usernameField = "username";
|
||||
static const _passwordField = "password";
|
||||
static const _usernameField = 'username';
|
||||
static const _passwordField = 'password';
|
||||
|
||||
static final AccountData _instance = AccountData._construct();
|
||||
final Future<SharedPreferences> _storage = SharedPreferences.getInstance();
|
||||
@ -29,21 +29,21 @@ class AccountData {
|
||||
String? _password;
|
||||
|
||||
String getUsername() {
|
||||
if(_username == null) throw Exception("Username not initialized");
|
||||
if(_username == null) throw Exception('Username not initialized');
|
||||
return _username!;
|
||||
}
|
||||
|
||||
String getPassword() {
|
||||
if(_password == null) throw Exception("Password not initialized");
|
||||
if(_password == null) throw Exception('Password not initialized');
|
||||
return _password!;
|
||||
}
|
||||
|
||||
String getUserSecret() {
|
||||
return sha512.convert(utf8.encode("${AccountData().getUsername()}:${AccountData().getPassword()}")).toString();
|
||||
return sha512.convert(utf8.encode('${AccountData().getUsername()}:${AccountData().getPassword()}')).toString();
|
||||
}
|
||||
|
||||
Future<String> getDeviceId() async {
|
||||
return sha512.convert(utf8.encode("${getUserSecret()}@${await FirebaseMessaging.instance.getToken()}")).toString();
|
||||
return sha512.convert(utf8.encode('${getUserSecret()}@${await FirebaseMessaging.instance.getToken()}')).toString();
|
||||
}
|
||||
|
||||
Future<void> setData(String username, String password) async {
|
||||
@ -84,7 +84,7 @@ class AccountData {
|
||||
}
|
||||
|
||||
String buildHttpAuthString() {
|
||||
if(!isPopulated()) throw Exception("AccountData (e.g. username or password) is not initialized!");
|
||||
return "$_username:$_password";
|
||||
if(!isPopulated()) throw Exception('AccountData (e.g. username or password) is not initialized!');
|
||||
return '$_username:$_password';
|
||||
}
|
||||
}
|
@ -26,7 +26,7 @@ class BreakerProps extends DataHolder {
|
||||
for(var key in breakers.regional.keys) {
|
||||
GetBreakersReponseObject value = breakers.regional[key]!;
|
||||
|
||||
if(int.parse(key.split("b")[1]) >= selfVersion) {
|
||||
if(int.parse(key.split('b')[1]) >= selfVersion) {
|
||||
if(value.areas.contains(type)) return value.message;
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import '../../api/marianumcloud/talk/chat/getChatResponse.dart';
|
||||
import '../dataHolder.dart';
|
||||
|
||||
class ChatProps extends DataHolder {
|
||||
String _queryToken = "";
|
||||
String _queryToken = '';
|
||||
DateTime _lastTokenSet = DateTime.now();
|
||||
|
||||
GetChatResponse? _getChatResponse;
|
||||
|
@ -21,7 +21,7 @@ class Endpoint {
|
||||
String domain;
|
||||
String path;
|
||||
|
||||
Endpoint({required this.domain, this.path = ""});
|
||||
Endpoint({required this.domain, this.path = ''});
|
||||
|
||||
String full() {
|
||||
return domain + path;
|
||||
@ -40,17 +40,17 @@ class EndpointData {
|
||||
EndpointMode getEndpointMode() {
|
||||
late String existingName;
|
||||
existingName = AccountData().getUsername();
|
||||
return existingName.startsWith("google") ? EndpointMode.stage : EndpointMode.live;
|
||||
return existingName.startsWith('google') ? EndpointMode.stage : EndpointMode.live;
|
||||
}
|
||||
|
||||
Endpoint webuntis() {
|
||||
return EndpointOptions(
|
||||
live: Endpoint(
|
||||
domain: "peleus.webuntis.com",
|
||||
domain: 'peleus.webuntis.com',
|
||||
),
|
||||
staged: Endpoint(
|
||||
domain: "mhsl.eu",
|
||||
path: "/marianum/marianummobile/webuntis/public/index.php/api"
|
||||
domain: 'mhsl.eu',
|
||||
path: '/marianum/marianummobile/webuntis/public/index.php/api'
|
||||
),
|
||||
).get(getEndpointMode());
|
||||
}
|
||||
@ -58,11 +58,11 @@ class EndpointData {
|
||||
Endpoint nextcloud() {
|
||||
return EndpointOptions(
|
||||
live: Endpoint(
|
||||
domain: "cloud.marianum-fulda.de",
|
||||
domain: 'cloud.marianum-fulda.de',
|
||||
),
|
||||
staged: Endpoint(
|
||||
domain: "mhsl.eu",
|
||||
path: "/marianum/marianummobile/cloud",
|
||||
domain: 'mhsl.eu',
|
||||
path: '/marianum/marianummobile/cloud',
|
||||
)
|
||||
).get(getEndpointMode());
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ extension ExtendedList on List {
|
||||
|
||||
class FilesProps extends DataHolder {
|
||||
List<String> folderPath = List<String>.empty(growable: true);
|
||||
String currentFolderName = "Home";
|
||||
String currentFolderName = 'Home';
|
||||
|
||||
ListFilesResponse? _listFilesResponse;
|
||||
ListFilesResponse get listFilesResponse => _listFilesResponse!;
|
||||
@ -32,7 +32,7 @@ class FilesProps extends DataHolder {
|
||||
_listFilesResponse = null;
|
||||
notifyListeners();
|
||||
ListFilesCache(
|
||||
path: folderPath.isEmpty ? "/" : folderPath.join("/"),
|
||||
path: folderPath.isEmpty ? '/' : folderPath.join('/'),
|
||||
onUpdate: (ListFilesResponse data) => {
|
||||
_listFilesResponse = data,
|
||||
notifyListeners(),
|
||||
@ -48,7 +48,7 @@ class FilesProps extends DataHolder {
|
||||
|
||||
void popFolder() {
|
||||
folderPath.removeLast();
|
||||
if(folderPath.isEmpty) currentFolderName = "Home";
|
||||
if(folderPath.isEmpty) currentFolderName = 'Home';
|
||||
run();
|
||||
}
|
||||
}
|
@ -49,8 +49,8 @@ class TimetableProps extends DataHolder {
|
||||
@override
|
||||
void run({renew}) {
|
||||
GetTimetableCache(
|
||||
startdate: int.parse(DateFormat("yyyyMMdd").format(startDate)),
|
||||
enddate: int.parse(DateFormat("yyyyMMdd").format(endDate)),
|
||||
startdate: int.parse(DateFormat('yyyyMMdd').format(startDate)),
|
||||
enddate: int.parse(DateFormat('yyyyMMdd').format(endDate)),
|
||||
onUpdate: (GetTimetableResponse data) => {
|
||||
_getTimetableResponse = data,
|
||||
notifyListeners(),
|
||||
|
@ -49,11 +49,11 @@ class NotificationController {
|
||||
|
||||
DebugTile(context).run(() {
|
||||
showDialog(context: context, builder: (context) => AlertDialog(
|
||||
title: const Text("Notification report"),
|
||||
title: const Text('Notification report'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text("Dieser Bericht wird angezeigt, da du den Entwicklermodus aktiviert hast und die App über eine Benachrichtigung geöffnet wurde."),
|
||||
const Text('Dieser Bericht wird angezeigt, da du den Entwicklermodus aktiviert hast und die App über eine Benachrichtigung geöffnet wurde.'),
|
||||
Text(JsonViewer.format(message.data)),
|
||||
],
|
||||
),
|
||||
|
@ -10,13 +10,13 @@ import '../widget/confirmDialog.dart';
|
||||
class NotifyUpdater {
|
||||
static ConfirmDialog enableAfterDisclaimer(SettingsProvider settings) {
|
||||
return ConfirmDialog(
|
||||
title: "Warnung",
|
||||
title: 'Warnung',
|
||||
icon: Icons.warning_amber,
|
||||
content: ""
|
||||
"Die Push-Benachrichtigungen werden durch mhsl.eu versendet.\n\n"
|
||||
"Durch das aktivieren dieser Funktion wird dein Nutzername, dein Password und eine Geräte-ID von mhsl dauerhaft gespeichert und verarbeitet.\n\n"
|
||||
"Für mehr Informationen drücke lange auf die Einstellungsoption!",
|
||||
confirmButton: "Aktivieren",
|
||||
content: ''
|
||||
'Die Push-Benachrichtigungen werden durch mhsl.eu versendet.\n\n'
|
||||
'Durch das aktivieren dieser Funktion wird dein Nutzername, dein Password und eine Geräte-ID von mhsl dauerhaft gespeichert und verarbeitet.\n\n'
|
||||
'Für mehr Informationen drücke lange auf die Einstellungsoption!',
|
||||
confirmButton: 'Aktivieren',
|
||||
onConfirm: () {
|
||||
FirebaseMessaging.instance.requestPermission(
|
||||
provisional: false
|
||||
@ -29,7 +29,7 @@ class NotifyUpdater {
|
||||
static void registerToServer() async {
|
||||
String? fcmToken = await FirebaseMessaging.instance.getToken();
|
||||
|
||||
if(fcmToken == null) throw Exception("Failed to register push notification because there is no FBC token!");
|
||||
if(fcmToken == null) throw Exception('Failed to register push notification because there is no FBC token!');
|
||||
|
||||
NotifyRegister(
|
||||
NotifyRegisterParams(
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import '../devTools/devToolsSettings.dart';
|
||||
import '../file/fileSettings.dart';
|
||||
import '../fileView/fileViewSettings.dart';
|
||||
import '../gradeAverages/gradeAveragesSettings.dart';
|
||||
@ -27,6 +28,7 @@ class Settings {
|
||||
HolidaysSettings holidaysSettings;
|
||||
FileViewSettings fileViewSettings;
|
||||
NotificationSettings notificationSettings;
|
||||
DevToolsSettings devToolsSettings;
|
||||
|
||||
Settings({
|
||||
required this.appTheme,
|
||||
@ -38,6 +40,7 @@ class Settings {
|
||||
required this.holidaysSettings,
|
||||
required this.fileViewSettings,
|
||||
required this.notificationSettings,
|
||||
required this.devToolsSettings,
|
||||
});
|
||||
|
||||
static String _themeToJson(ThemeMode m) => m.name;
|
||||
|
@ -23,6 +23,8 @@ Settings _$SettingsFromJson(Map<String, dynamic> json) => Settings(
|
||||
json['fileViewSettings'] as Map<String, dynamic>),
|
||||
notificationSettings: NotificationSettings.fromJson(
|
||||
json['notificationSettings'] as Map<String, dynamic>),
|
||||
devToolsSettings: DevToolsSettings.fromJson(
|
||||
json['devToolsSettings'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{
|
||||
@ -35,4 +37,5 @@ Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{
|
||||
'holidaysSettings': instance.holidaysSettings.toJson(),
|
||||
'fileViewSettings': instance.fileViewSettings.toJson(),
|
||||
'notificationSettings': instance.notificationSettings.toJson(),
|
||||
'devToolsSettings': instance.devToolsSettings.toJson(),
|
||||
};
|
||||
|
@ -8,7 +8,7 @@ import '../../view/settings/defaultSettings.dart';
|
||||
import 'settings.dart';
|
||||
|
||||
class SettingsProvider extends ChangeNotifier {
|
||||
static const String _fieldName = "settings";
|
||||
static const String _fieldName = 'settings';
|
||||
|
||||
late SharedPreferences _storage;
|
||||
late Settings _settings = DefaultSettings.get();
|
||||
@ -45,13 +45,13 @@ class SettingsProvider extends ChangeNotifier {
|
||||
_settings = Settings.fromJson(jsonDecode(_storage.getString(_fieldName)!));
|
||||
} catch(exception) {
|
||||
try {
|
||||
log("Settings were changed, trying to recover from old Settings: ${exception.toString()}");
|
||||
log('Settings were changed, trying to recover from old Settings: ${exception.toString()}');
|
||||
_settings = Settings.fromJson(_mergeSettings(jsonDecode(_storage.getString(_fieldName)!), DefaultSettings.get().toJson()));
|
||||
log("Settings recovered successfully: ${_settings.toJson().toString()}");
|
||||
log('Settings recovered successfully: ${_settings.toJson().toString()}');
|
||||
} catch(exception) {
|
||||
log("Settings are defective and not recoverable, using defaults: ${exception.toString()}");
|
||||
log('Settings are defective and not recoverable, using defaults: ${exception.toString()}');
|
||||
_settings = DefaultSettings.get();
|
||||
log("Settings were reset to defaults!");
|
||||
log('Settings were reset to defaults!');
|
||||
}
|
||||
}
|
||||
|
||||
|
15
lib/storage/devTools/devToolsSettings.dart
Normal file
15
lib/storage/devTools/devToolsSettings.dart
Normal file
@ -0,0 +1,15 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'devToolsSettings.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class DevToolsSettings {
|
||||
bool showPerformanceOverlay;
|
||||
bool checkerboardOffscreenLayers;
|
||||
bool checkerboardRasterCacheImages;
|
||||
|
||||
DevToolsSettings({required this.showPerformanceOverlay, required this.checkerboardOffscreenLayers, required this.checkerboardRasterCacheImages});
|
||||
|
||||
factory DevToolsSettings.fromJson(Map<String, dynamic> json) => _$DevToolsSettingsFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$DevToolsSettingsToJson(this);
|
||||
}
|
22
lib/storage/devTools/devToolsSettings.g.dart
Normal file
22
lib/storage/devTools/devToolsSettings.g.dart
Normal file
@ -0,0 +1,22 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'devToolsSettings.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
DevToolsSettings _$DevToolsSettingsFromJson(Map<String, dynamic> json) =>
|
||||
DevToolsSettings(
|
||||
showPerformanceOverlay: json['showPerformanceOverlay'] as bool,
|
||||
checkerboardOffscreenLayers: json['checkerboardOffscreenLayers'] as bool,
|
||||
checkerboardRasterCacheImages:
|
||||
json['checkerboardRasterCacheImages'] as bool,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$DevToolsSettingsToJson(DevToolsSettings instance) =>
|
||||
<String, dynamic>{
|
||||
'showPerformanceOverlay': instance.showPerformanceOverlay,
|
||||
'checkerboardOffscreenLayers': instance.checkerboardOffscreenLayers,
|
||||
'checkerboardRasterCacheImages': instance.checkerboardRasterCacheImages,
|
||||
};
|
@ -4,13 +4,13 @@ class AppTheme {
|
||||
static ThemeModeDisplay getDisplayOptions(ThemeMode theme) {
|
||||
switch(theme) {
|
||||
case ThemeMode.system:
|
||||
return ThemeModeDisplay(icon: Icons.auto_fix_high_outlined, displayName: "Systemvorgabe");
|
||||
return ThemeModeDisplay(icon: Icons.auto_fix_high_outlined, displayName: 'Systemvorgabe');
|
||||
|
||||
case ThemeMode.light:
|
||||
return ThemeModeDisplay(icon: Icons.wb_sunny_outlined, displayName: "Hell");
|
||||
return ThemeModeDisplay(icon: Icons.wb_sunny_outlined, displayName: 'Hell');
|
||||
|
||||
case ThemeMode.dark:
|
||||
return ThemeModeDisplay(icon: Icons.dark_mode_outlined, displayName: "Dunkel");
|
||||
return ThemeModeDisplay(icon: Icons.dark_mode_outlined, displayName: 'Dunkel');
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ class _LoginState extends State<Login> {
|
||||
bool displayDisclaimerText = true;
|
||||
|
||||
String? _checkInput(value){
|
||||
return (value ?? "").length == 0 ? "Eingabe erforderlich" : null;
|
||||
return (value ?? '').length == 0 ? 'Eingabe erforderlich' : null;
|
||||
}
|
||||
|
||||
Future<String?> _login(LoginData data) async {
|
||||
@ -42,7 +42,7 @@ class _LoginState extends State<Login> {
|
||||
} catch(e) {
|
||||
await AccountData().removeData();
|
||||
log(e.toString());
|
||||
return "Benutzername oder Password falsch! (${e.toString()})";
|
||||
return 'Benutzername oder Password falsch! (${e.toString()})';
|
||||
}
|
||||
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
@ -51,14 +51,14 @@ class _LoginState extends State<Login> {
|
||||
|
||||
Future<String> _resetPassword(String name) {
|
||||
return Future.delayed(Duration.zero).then((_) {
|
||||
return "Diese Funktion steht nicht zur Verfügung!";
|
||||
return 'Diese Funktion steht nicht zur Verfügung!';
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlutterLogin(
|
||||
logo: Image.asset("assets/logo/icon.png").image,
|
||||
logo: Image.asset('assets/logo/icon.png').image,
|
||||
|
||||
userValidator: _checkInput,
|
||||
passwordValidator: _checkInput,
|
||||
@ -84,9 +84,9 @@ class _LoginState extends State<Login> {
|
||||
),
|
||||
|
||||
messages: LoginMessages(
|
||||
loginButton: "Anmelden",
|
||||
userHint: "Nutzername",
|
||||
passwordHint: "Passwort",
|
||||
loginButton: 'Anmelden',
|
||||
userHint: 'Nutzername',
|
||||
passwordHint: 'Passwort',
|
||||
),
|
||||
|
||||
disableCustomPageTransformer: true,
|
||||
@ -97,15 +97,15 @@ class _LoginState extends State<Login> {
|
||||
child: Visibility(
|
||||
visible: displayDisclaimerText,
|
||||
child: const Text(
|
||||
"Dies ist ein Inoffizieller Nextcloud & Webuntis Client und wird nicht vom Marianum selbst betrieben.\nKeinerlei Gewähr für Vollständigkeit, Richtigkeit und Aktualität!",
|
||||
'Dies ist ein Inoffizieller Nextcloud & Webuntis Client und wird nicht vom Marianum selbst betrieben.\nKeinerlei Gewähr für Vollständigkeit, Richtigkeit und Aktualität!',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
footer: "Marianum Fulda - Die persönliche Schule",
|
||||
title: "Marianum Fulda",
|
||||
footer: 'Marianum Fulda - Die persönliche Schule',
|
||||
title: 'Marianum Fulda',
|
||||
|
||||
userType: LoginUserType.name,
|
||||
);
|
||||
|
@ -6,11 +6,13 @@ import 'package:flowder/flowder.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:jiffy/jiffy.dart';
|
||||
import '../../../widget/infoDialog.dart';
|
||||
import 'package:nextcloud/nextcloud.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import '../../../api/marianumcloud/webdav/queries/listFiles/cacheableFile.dart';
|
||||
import '../../../api/marianumcloud/webdav/webdavApi.dart';
|
||||
import '../../../model/endpointData.dart';
|
||||
import '../../../widget/centeredLeading.dart';
|
||||
import '../../../widget/confirmDialog.dart';
|
||||
import '../../../widget/fileViewer.dart';
|
||||
@ -27,7 +29,7 @@ class FileElement extends StatefulWidget {
|
||||
Directory paths = await getTemporaryDirectory();
|
||||
|
||||
var encodedPath = Uri.encodeComponent(remotePath);
|
||||
encodedPath = encodedPath.replaceAll("%2F", "/");
|
||||
encodedPath = encodedPath.replaceAll('%2F', '/');
|
||||
|
||||
String local = paths.path + Platform.pathSeparator + name;
|
||||
|
||||
@ -42,7 +44,7 @@ class FileElement extends StatefulWidget {
|
||||
onDone: () {
|
||||
//Future<OpenResult> result = OpenFile.open(local); // TODO legacy - refactor: remove onDone parameter
|
||||
Navigator.of(context).push(MaterialPageRoute(builder: (context) => FileViewer(path: local)));
|
||||
onDone(OpenResult(message: "File viewer opened", type: ResultType.done));
|
||||
onDone(OpenResult(message: 'File viewer opened', type: ResultType.done));
|
||||
// result.then((value) => {
|
||||
// onDone(value)
|
||||
// });
|
||||
@ -69,21 +71,21 @@ class _FileElementState extends State<FileElement> {
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.only(right: 10),
|
||||
child: const Text("Download:"),
|
||||
child: const Text('Download:'),
|
||||
),
|
||||
Expanded(
|
||||
child: LinearProgressIndicator(value: percent/100),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(left: 10),
|
||||
child: Text("${percent.round()}%"),
|
||||
child: Text('${percent.round()}%'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return widget.file.isDirectory
|
||||
? Text("geändert ${Jiffy.parseFromDateTime(widget.file.modifiedAt ?? DateTime.now()).fromNow()}")
|
||||
: Text("${filesize(widget.file.size)}, ${Jiffy.parseFromDateTime(widget.file.modifiedAt ?? DateTime.now()).fromNow()}");
|
||||
? Text('geändert ${Jiffy.parseFromDateTime(widget.file.modifiedAt ?? DateTime.now()).fromNow()}')
|
||||
: Text('${filesize(widget.file.size)}, ${Jiffy.parseFromDateTime(widget.file.modifiedAt ?? DateTime.now()).fromNow()}');
|
||||
}
|
||||
|
||||
@override
|
||||
@ -103,14 +105,18 @@ class _FileElementState extends State<FileElement> {
|
||||
},
|
||||
));
|
||||
} else {
|
||||
if(EndpointData().getEndpointMode() == EndpointMode.stage) {
|
||||
InfoDialog.show(context, 'Virtuelle Dateien im Staging Prozess können nicht heruntergeladen werden!');
|
||||
return;
|
||||
}
|
||||
if(widget.file.currentlyDownloading) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => ConfirmDialog(
|
||||
title: "Download abbrechen?",
|
||||
content: "Möchtest du den Download abbrechen?",
|
||||
cancelButton: "Nein",
|
||||
confirmButton: "Ja, Abbrechen",
|
||||
title: 'Download abbrechen?',
|
||||
content: 'Möchtest du den Download abbrechen?',
|
||||
cancelButton: 'Nein',
|
||||
confirmButton: 'Ja, Abbrechen',
|
||||
onConfirm: () {
|
||||
downloadCore?.then((value) {
|
||||
if(!value.isCancelled) value.cancel();
|
||||
@ -137,7 +143,7 @@ class _FileElementState extends State<FileElement> {
|
||||
if(result.type != ResultType.done) {
|
||||
showDialog(context: context, builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text("Download"),
|
||||
title: const Text('Download'),
|
||||
content: Text(result.message),
|
||||
);
|
||||
});
|
||||
@ -157,12 +163,12 @@ class _FileElementState extends State<FileElement> {
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.delete_outline),
|
||||
title: const Text("Löschen"),
|
||||
title: const Text('Löschen'),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
showDialog(context: context, builder: (context) => ConfirmDialog(
|
||||
title: "Element löschen?",
|
||||
content: "Das Element wird unwiederruflich gelöscht.",
|
||||
title: 'Element löschen?',
|
||||
content: 'Das Element wird unwiederruflich gelöscht.',
|
||||
onConfirm: () {
|
||||
WebdavApi.webdav
|
||||
.then((value) => value.delete(PathUri.parse(widget.file.path)))
|
||||
@ -175,7 +181,7 @@ class _FileElementState extends State<FileElement> {
|
||||
visible: !kReleaseMode,
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.share_outlined),
|
||||
title: const Text("Teilen"),
|
||||
title: const Text('Teilen'),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
UnimplementedDialog.show(context);
|
||||
|
@ -43,8 +43,8 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
|
||||
setState(() {
|
||||
state = FileUploadState.checkConflict;
|
||||
});
|
||||
List<WebDavResponse> result = (await webdavClient.propfind(PathUri.parse(widget.remotePath.join("/")))).responses;
|
||||
if(result.any((element) => element.href!.endsWith("/$targetFileName"))) {
|
||||
List<WebDavResponse> result = (await webdavClient.propfind(PathUri.parse(widget.remotePath.join('/')))).responses;
|
||||
if(result.any((element) => element.href!.endsWith('/$targetFileName'))) {
|
||||
setState(() {
|
||||
state = FileUploadState.conflict;
|
||||
});
|
||||
@ -57,7 +57,7 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
|
||||
}
|
||||
|
||||
Future<HttpClientResponse> uploadTask = webdavClient.putFile(File(widget.localPath), FileStat.statSync(widget.localPath), PathUri.parse(fullRemotePath)); // TODO use onProgress from putFile
|
||||
uploadTask.then((value) => Future<HttpClientResponse?>.value(value)).catchError((e) {
|
||||
uploadTask.then(Future<HttpClientResponse?>.value).catchError((e) {
|
||||
setState(() {
|
||||
state = FileUploadState.error;
|
||||
});
|
||||
@ -67,7 +67,7 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
|
||||
|
||||
cancelableOperation = CancelableOperation<HttpClientResponse>.fromFuture(
|
||||
uploadTask,
|
||||
onCancel: () => log("Upload cancelled"),
|
||||
onCancel: () => log('Upload cancelled'),
|
||||
);
|
||||
|
||||
cancelableOperation!.then((value) {
|
||||
@ -88,7 +88,7 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
targetFileName = widget.fileName;
|
||||
remoteFolderName = widget.remotePath.isNotEmpty ? widget.remotePath.last : "/";
|
||||
remoteFolderName = widget.remotePath.isNotEmpty ? widget.remotePath.last : '/';
|
||||
fileNameController.text = widget.fileName;
|
||||
}
|
||||
|
||||
@ -96,7 +96,7 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
|
||||
Widget build(BuildContext context) {
|
||||
if(state == FileUploadState.naming) {
|
||||
return AlertDialog(
|
||||
title: const Text("Datei hochladen"),
|
||||
title: const Text('Datei hochladen'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@ -107,7 +107,7 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
|
||||
},
|
||||
autocorrect: false,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Dateiname",
|
||||
labelText: 'Dateiname',
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -115,10 +115,10 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
|
||||
actions: [
|
||||
TextButton(onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
}, child: const Text("Abbrechen")),
|
||||
}, child: const Text('Abbrechen')),
|
||||
TextButton(onPressed: () async {
|
||||
upload();
|
||||
}, child: const Text("Hochladen")),
|
||||
}, child: const Text('Hochladen')),
|
||||
],
|
||||
|
||||
);
|
||||
@ -127,7 +127,7 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
|
||||
if(state == FileUploadState.conflict) {
|
||||
return AlertDialog(
|
||||
icon: const Icon(Icons.error_outline),
|
||||
title: const Text("Datei konflikt"),
|
||||
title: const Text('Datei konflikt'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@ -139,10 +139,10 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
|
||||
setState(() {
|
||||
state = FileUploadState.naming;
|
||||
});
|
||||
}, child: const Text("Datei umbenennen")),
|
||||
}, child: const Text('Datei umbenennen')),
|
||||
TextButton(onPressed: () {
|
||||
upload(override: true);
|
||||
}, child: const Text("Datei überschreiben")),
|
||||
}, child: const Text('Datei überschreiben')),
|
||||
],
|
||||
|
||||
);
|
||||
@ -151,15 +151,15 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
|
||||
if(state == FileUploadState.upload || state == FileUploadState.checkConflict) {
|
||||
return AlertDialog(
|
||||
icon: const Icon(Icons.upload),
|
||||
title: const Text("Hochladen"),
|
||||
title: const Text('Hochladen'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Visibility(
|
||||
visible: state == FileUploadState.upload,
|
||||
replacement: const Text("Prüfe auf dateikonflikte..."),
|
||||
child: const Text("Upload läuft!\nDies kann je nach Dateigröße einige Zeit dauern...", textAlign: TextAlign.center),
|
||||
replacement: const Text('Prüfe auf dateikonflikte...'),
|
||||
child: const Text('Upload läuft!\nDies kann je nach Dateigröße einige Zeit dauern...', textAlign: TextAlign.center),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
const CircularProgressIndicator()
|
||||
@ -183,7 +183,7 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
|
||||
}
|
||||
return AlertDialog(
|
||||
icon: const Icon(Icons.done),
|
||||
title: const Text("Upload fertig"),
|
||||
title: const Text('Upload fertig'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@ -193,7 +193,7 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
|
||||
actions: [
|
||||
TextButton(onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
}, child: const Text("Fertig")),
|
||||
}, child: const Text('Fertig')),
|
||||
],
|
||||
|
||||
);
|
||||
@ -202,23 +202,23 @@ class _FileUploadDialogState extends State<FileUploadDialog> {
|
||||
if(state == FileUploadState.error) {
|
||||
return AlertDialog(
|
||||
icon: const Icon(Icons.error_outline),
|
||||
title: const Text("Fehler"),
|
||||
title: const Text('Fehler'),
|
||||
content: const Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text("Es ist ein Fehler aufgetreten!", textAlign: TextAlign.center),
|
||||
Text('Es ist ein Fehler aufgetreten!', textAlign: TextAlign.center),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
}, child: const Text("Schlißen")),
|
||||
}, child: const Text('Schlißen')),
|
||||
],
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
throw UnimplementedError("Invalid state");
|
||||
throw UnimplementedError('Invalid state');
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -43,17 +43,17 @@ enum SortOption {
|
||||
class SortOptions {
|
||||
static Map<SortOption, BetterSortOption> options = {
|
||||
SortOption.name: BetterSortOption(
|
||||
displayName: "Name",
|
||||
displayName: 'Name',
|
||||
icon: Icons.sort_by_alpha_outlined,
|
||||
compare: (CacheableFile a, CacheableFile b) => a.name.compareTo(b.name)
|
||||
),
|
||||
SortOption.date: BetterSortOption(
|
||||
displayName: "Datum",
|
||||
displayName: 'Datum',
|
||||
icon: Icons.history_outlined,
|
||||
compare: (CacheableFile a, CacheableFile b) => a.modifiedAt!.compareTo(b.modifiedAt!)
|
||||
),
|
||||
SortOption.size: BetterSortOption(
|
||||
displayName: "Größe",
|
||||
displayName: 'Größe',
|
||||
icon: Icons.sd_card_outlined,
|
||||
compare: (CacheableFile a, CacheableFile b) {
|
||||
if(a.isDirectory || b.isDirectory) return a.isDirectory ? 1 : 0;
|
||||
@ -88,7 +88,7 @@ class _FilesState extends State<Files> {
|
||||
|
||||
void _query() {
|
||||
ListFilesCache(
|
||||
path: widget.path.isEmpty ? "/" : widget.path.join("/"),
|
||||
path: widget.path.isEmpty ? '/' : widget.path.join('/'),
|
||||
onUpdate: (ListFilesResponse d) {
|
||||
if(!context.mounted) return; // prevent setState when widget is possibly already disposed
|
||||
d.files.removeWhere((element) => element.name.isEmpty || element.name == widget.path.lastOrNull());
|
||||
@ -109,7 +109,7 @@ class _FilesState extends State<Files> {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.path.isNotEmpty ? widget.path.last : "Dateien"),
|
||||
title: Text(widget.path.isNotEmpty ? widget.path.last : 'Dateien'),
|
||||
actions: [
|
||||
// IconButton(
|
||||
// icon: const Icon(Icons.search),
|
||||
@ -127,7 +127,7 @@ class _FilesState extends State<Files> {
|
||||
children: [
|
||||
Icon(e ? Icons.text_rotate_up : Icons.text_rotation_down, color: Theme.of(context).colorScheme.onSurface),
|
||||
const SizedBox(width: 15),
|
||||
Text(e ? "Aufsteigend" : "Absteigend")
|
||||
Text(e ? 'Aufsteigend' : 'Absteigend')
|
||||
],
|
||||
)
|
||||
)).toList();
|
||||
@ -164,7 +164,7 @@ class _FilesState extends State<Files> {
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
heroTag: "uploadFile",
|
||||
heroTag: 'uploadFile',
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
onPressed: () {
|
||||
showDialog(context: context, builder: (context) {
|
||||
@ -172,29 +172,29 @@ class _FilesState extends State<Files> {
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.create_new_folder_outlined),
|
||||
title: const Text("Ordner erstellen"),
|
||||
title: const Text('Ordner erstellen'),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
showDialog(context: context, builder: (context) {
|
||||
var inputController = TextEditingController();
|
||||
return AlertDialog(
|
||||
title: const Text("Neuer Ordner"),
|
||||
title: const Text('Neuer Ordner'),
|
||||
content: TextField(
|
||||
controller: inputController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Name",
|
||||
labelText: 'Name',
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
}, child: const Text("Abbrechen")),
|
||||
}, child: const Text('Abbrechen')),
|
||||
TextButton(onPressed: () {
|
||||
WebdavApi.webdav.then((webdav) {
|
||||
webdav.mkcol(PathUri.parse("${widget.path.join("/")}/${inputController.text}")).then((value) => _query());
|
||||
});
|
||||
Navigator.of(context).pop();
|
||||
}, child: const Text("Ordner erstellen")),
|
||||
}, child: const Text('Ordner erstellen')),
|
||||
],
|
||||
);
|
||||
});
|
||||
@ -202,12 +202,10 @@ class _FilesState extends State<Files> {
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.upload_file),
|
||||
title: const Text("Aus Dateien hochladen"),
|
||||
title: const Text('Aus Dateien hochladen'),
|
||||
onTap: () {
|
||||
context.loaderOverlay.show();
|
||||
FilePick.documentPick().then((value) {
|
||||
mediaUpload(value);
|
||||
});
|
||||
FilePick.documentPick().then(mediaUpload);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
@ -215,7 +213,7 @@ class _FilesState extends State<Files> {
|
||||
visible: !Platform.isIOS,
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.add_a_photo_outlined),
|
||||
title: const Text("Aus Gallerie hochladen"),
|
||||
title: const Text('Aus Gallerie hochladen'),
|
||||
onTap: () {
|
||||
context.loaderOverlay.show();
|
||||
FilePick.galleryPick().then((value) {
|
||||
@ -231,13 +229,14 @@ class _FilesState extends State<Files> {
|
||||
},
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
body: data == null ? const LoadingSpinner() : data!.files.isEmpty ? const PlaceholderView(icon: Icons.folder_off_rounded, text: "Der Ordner ist leer") : LoaderOverlay(
|
||||
body: data == null ? const LoadingSpinner() : data!.files.isEmpty ? const PlaceholderView(icon: Icons.folder_off_rounded, text: 'Der Ordner ist leer') : LoaderOverlay(
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () {
|
||||
_query();
|
||||
return Future.delayed(const Duration(seconds: 3));
|
||||
},
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: files.length,
|
||||
itemBuilder: (context, index) {
|
||||
CacheableFile file = files.toList()[index];
|
||||
@ -257,6 +256,6 @@ class _FilesState extends State<Files> {
|
||||
}
|
||||
|
||||
var fileName = path.split(Platform.pathSeparator).last;
|
||||
showDialog(context: context, builder: (context) => FileUploadDialog(localPath: path, remotePath: widget.path, fileName: fileName, onUploadFinished: () => _query()), barrierDismissible: false);
|
||||
showDialog(context: context, builder: (context) => FileUploadDialog(localPath: path, remotePath: widget.path, fileName: fileName, onUploadFinished: _query), barrierDismissible: false);
|
||||
}
|
||||
}
|
||||
|
78
lib/view/pages/more/feedback/feedbackDialog.dart
Normal file
78
lib/view/pages/more/feedback/feedbackDialog.dart
Normal file
@ -0,0 +1,78 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:package_info/package_info.dart';
|
||||
|
||||
import '../../../../api/mhsl/server/feedback/addFeedback.dart';
|
||||
import '../../../../api/mhsl/server/feedback/addFeedbackParams.dart';
|
||||
import '../../../../model/accountData.dart';
|
||||
import '../../../../widget/infoDialog.dart';
|
||||
|
||||
class FeedbackDialog extends StatefulWidget {
|
||||
const FeedbackDialog({super.key});
|
||||
|
||||
@override
|
||||
State<FeedbackDialog> createState() => _FeedbackDialogState();
|
||||
}
|
||||
|
||||
class _FeedbackDialogState extends State<FeedbackDialog> {
|
||||
final TextEditingController _feedbackInput = TextEditingController();
|
||||
String? _error;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
|
||||
title: const Text('Feedback'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('Feedback, Anregungen, Ideen, Fehler und Verbesserungen'),
|
||||
const SizedBox(height: 10),
|
||||
const Text('Bitte gib keine geheimen Daten wie z.B. Passwörter weiter.', style: TextStyle(fontSize: 10)),
|
||||
const SizedBox(height: 10),
|
||||
TextField(
|
||||
controller: _feedbackInput,
|
||||
autofocus: true,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
label: Text('Feedback und Verbesserungen')
|
||||
),
|
||||
// style: TextStyle(),
|
||||
// expands: true,
|
||||
minLines: 3,
|
||||
maxLines: 5,
|
||||
),
|
||||
Visibility(
|
||||
visible: _error != null,
|
||||
child: Text('Senden fehlgeschlagen: $_error', style: const TextStyle(color: Colors.red))
|
||||
)
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text('Abbrechen')),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
AddFeedback(
|
||||
AddFeedbackParams(
|
||||
user: AccountData().getUserSecret(),
|
||||
feedback: _feedbackInput.text,
|
||||
appVersion: int.parse((await PackageInfo.fromPlatform()).buildNumber)
|
||||
)
|
||||
)
|
||||
.run()
|
||||
.then((value) {
|
||||
Navigator.of(context).pop();
|
||||
InfoDialog.show(context, 'Danke für dein Feedback!');
|
||||
})
|
||||
.catchError((error, trace) {
|
||||
setState(() {
|
||||
_error = error.toString();
|
||||
});
|
||||
});
|
||||
},
|
||||
child: const Text('Senden'),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../theming/darkAppTheme.dart';
|
||||
import '../../../../widget/loadingSpinner.dart';
|
||||
|
||||
class FeedbackForm extends StatefulWidget {
|
||||
final Future<void> Function(String, {Map<String, dynamic>? extras}) callback;
|
||||
final ScrollController? scrollController;
|
||||
const FeedbackForm({required this.scrollController, required this.callback, super.key});
|
||||
|
||||
@override
|
||||
State<FeedbackForm> createState() => _FeedbackFormState();
|
||||
}
|
||||
|
||||
class _FeedbackFormState extends State<FeedbackForm> {
|
||||
final TextEditingController _feedbackInput = TextEditingController();
|
||||
bool _textFieldEmpty = false;
|
||||
bool _isSending = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_feedbackInput.addListener(() {
|
||||
setState(() {
|
||||
_textFieldEmpty = _feedbackInput.text.isEmpty;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Theme(
|
||||
data: DarkAppTheme.theme,
|
||||
child: Visibility(
|
||||
visible: !_isSending,
|
||||
replacement: const LoadingSpinner(infoText: "Daten werden ermittelt"),
|
||||
child: SingleChildScrollView(
|
||||
controller: widget.scrollController,
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text("Bitte gib keine geheimen Daten wie z.B. Passwörter weiter!", style: TextStyle(fontSize: 10)),
|
||||
const SizedBox(height: 10),
|
||||
TextField(
|
||||
controller: _feedbackInput,
|
||||
autofocus: true,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
label: const Text("Dein Feedback"),
|
||||
errorText: _textFieldEmpty ? "Bitte gib eine Beschreibung an" : null
|
||||
),
|
||||
minLines: 1,
|
||||
maxLines: 2,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
if(_isSending) return;
|
||||
if(_feedbackInput.text.isEmpty) {
|
||||
setState(() {
|
||||
_textFieldEmpty = true;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isSending = true;
|
||||
});
|
||||
|
||||
widget.callback(_feedbackInput.text);
|
||||
},
|
||||
child: const Text("Senden"),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
const Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
"Feedback, mal süß wie Kuchen, mal sauer wie Gurken, doch immer ein Schlüssel fürs Wachsen und Lernen.",
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Icon(Icons.emoji_objects_outlined)
|
||||
],
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:feedback/feedback.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:loader_overlay/loader_overlay.dart';
|
||||
import 'package:package_info/package_info.dart';
|
||||
|
||||
import '../../../../api/mhsl/server/feedback/addFeedback.dart';
|
||||
import '../../../../api/mhsl/server/feedback/addFeedbackParams.dart';
|
||||
import '../../../../model/accountData.dart';
|
||||
import '../../../../widget/infoDialog.dart';
|
||||
|
||||
class FeedbackSender {
|
||||
static send(BuildContext context, UserFeedback feedback) async {
|
||||
BetterFeedback.of(context).hide();
|
||||
context.loaderOverlay.show();
|
||||
AddFeedbackParams params = AddFeedbackParams(
|
||||
user: AccountData().getUserSecret(),
|
||||
feedback: feedback.text,
|
||||
screenshot: await compute((message) => base64Encode(message), feedback.screenshot),
|
||||
appVersion: int.parse((await PackageInfo.fromPlatform()).buildNumber)
|
||||
);
|
||||
AddFeedback(params).run().then((value) {
|
||||
InfoDialog.show(context, "Danke für dein Feedback!");
|
||||
context.loaderOverlay.hide();
|
||||
}).catchError((error, trace) {
|
||||
InfoDialog.show(context, error.toString());
|
||||
context.loaderOverlay.hide();
|
||||
});
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ class _GradeAverageState extends State<GradeAverage> {
|
||||
|
||||
String getGradeDisplay(int grade) {
|
||||
if(gradeSystem) {
|
||||
return "Note $grade";
|
||||
return 'Note $grade';
|
||||
} else {
|
||||
return "$grade Punkt${grade > 1 ? "e" : ""}";
|
||||
}
|
||||
@ -48,22 +48,22 @@ class _GradeAverageState extends State<GradeAverage> {
|
||||
if(!settings.val().gradeAveragesSettings.askedForPreferredGradeSystem) {
|
||||
settings.val(write: true).gradeAveragesSettings.askedForPreferredGradeSystem = true;
|
||||
showDialog(context: context, builder: (context) => AlertDialog(
|
||||
title: const Text("Notensystem"),
|
||||
content: const Text("Wähle dein bevorzugtes Schulnotensystem"),
|
||||
title: const Text('Notensystem'),
|
||||
content: const Text('Wähle dein bevorzugtes Schulnotensystem'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
switchSystem(true);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text("Realschule"),
|
||||
child: const Text('Realschule'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
switchSystem(false);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text("Oberstufe"),
|
||||
child: const Text('Oberstufe'),
|
||||
),
|
||||
],
|
||||
));
|
||||
@ -81,7 +81,7 @@ class _GradeAverageState extends State<GradeAverage> {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Notendurschnittsrechner"),
|
||||
title: const Text('Notendurschnittsrechner'),
|
||||
actions: [
|
||||
Visibility(
|
||||
visible: grades.isNotEmpty,
|
||||
@ -89,9 +89,9 @@ class _GradeAverageState extends State<GradeAverage> {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => ConfirmDialog(
|
||||
title: "Zurücksetzen?",
|
||||
content: "Alle Einträge werden entfernt.",
|
||||
confirmButton: "Zurücksetzen",
|
||||
title: 'Zurücksetzen?',
|
||||
content: 'Alle Einträge werden entfernt.',
|
||||
confirmButton: 'Zurücksetzen',
|
||||
onConfirm: () {
|
||||
grades.clear();
|
||||
setState(() {});
|
||||
@ -109,7 +109,7 @@ class _GradeAverageState extends State<GradeAverage> {
|
||||
children: [
|
||||
Icon(e ? Icons.calculate_outlined : Icons.school_outlined, color: Theme.of(context).colorScheme.onSurface),
|
||||
const SizedBox(width: 15),
|
||||
Text(e ? "Notensystem" : "Punktesystem"),
|
||||
Text(e ? 'Notensystem' : 'Punktesystem'),
|
||||
],
|
||||
),
|
||||
)).toList(),
|
||||
@ -120,9 +120,9 @@ class _GradeAverageState extends State<GradeAverage> {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => ConfirmDialog(
|
||||
title: "Notensystem wechseln",
|
||||
content: "Beim wechsel des Notensystems werden alle Einträge zurückgesetzt.",
|
||||
confirmButton: "Fortfahren",
|
||||
title: 'Notensystem wechseln',
|
||||
content: 'Beim wechsel des Notensystems werden alle Einträge zurückgesetzt.',
|
||||
confirmButton: 'Fortfahren',
|
||||
onConfirm: () => switchSystem(e),
|
||||
),
|
||||
);
|
||||
@ -142,7 +142,7 @@ class _GradeAverageState extends State<GradeAverage> {
|
||||
const SizedBox(height: 10),
|
||||
const Divider(),
|
||||
const SizedBox(height: 10),
|
||||
Text(gradeSystem ? "Wähle unten die Anzahl deiner jewiligen Noten aus" : "Wähle unten die Anzahl deiner jeweiligen Punkte aus"),
|
||||
Text(gradeSystem ? 'Wähle unten die Anzahl deiner jewiligen Noten aus' : 'Wähle unten die Anzahl deiner jeweiligen Punkte aus'),
|
||||
const SizedBox(height: 10),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
@ -169,7 +169,7 @@ class _GradeAverageState extends State<GradeAverage> {
|
||||
icon: const Icon(Icons.remove),
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
Text("${grades.where(isThis).length}", style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
|
||||
Text('${grades.where(isThis).length}', style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold)),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
|
@ -42,23 +42,23 @@ class _HolidaysState extends State<Holidays> {
|
||||
}
|
||||
|
||||
String parseString(String enDate) {
|
||||
return Jiffy.parse(enDate).format(pattern: "dd.MM.yyyy");
|
||||
return Jiffy.parse(enDate).format(pattern: 'dd.MM.yyyy');
|
||||
}
|
||||
|
||||
void showDisclaimer() {
|
||||
showDialog(context: context, builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text("Richtigkeit und Bereitstellung der Daten"),
|
||||
title: const Text('Richtigkeit und Bereitstellung der Daten'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(""
|
||||
"Sämtliche Datumsangaben sind ohne Gewähr.\n"
|
||||
"Ich übernehme weder Verantwortung für die Richtigkeit der Daten noch hafte ich für wirtschaftliche Schäden die aus der Verwendung dieser Daten entstehen können.\n\n"
|
||||
"Die Daten stammen von https://ferien-api.de/"),
|
||||
const Text(''
|
||||
'Sämtliche Datumsangaben sind ohne Gewähr.\n'
|
||||
'Ich übernehme weder Verantwortung für die Richtigkeit der Daten noch hafte ich für wirtschaftliche Schäden die aus der Verwendung dieser Daten entstehen können.\n\n'
|
||||
'Die Daten stammen von https://ferien-api.de/'),
|
||||
const SizedBox(height: 30),
|
||||
ListTile(
|
||||
title: const Text("Diese Meldung nicht mehr anzeigen"),
|
||||
title: const Text('Diese Meldung nicht mehr anzeigen'),
|
||||
trailing: Checkbox(
|
||||
value: settings.val().holidaysSettings.dismissedDisclaimer,
|
||||
onChanged: (value) => settings.val(write: true).holidaysSettings.dismissedDisclaimer = value!,
|
||||
@ -67,8 +67,8 @@ class _HolidaysState extends State<Holidays> {
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(child: const Text("ferien-api.de besuchen"), onPressed: () => ConfirmDialog.openBrowser(context, "https://ferien-api.de/")),
|
||||
TextButton(child: const Text("Okay"), onPressed: () => Navigator.of(context).pop()),
|
||||
TextButton(child: const Text('ferien-api.de besuchen'), onPressed: () => ConfirmDialog.openBrowser(context, 'https://ferien-api.de/')),
|
||||
TextButton(child: const Text('Okay'), onPressed: () => Navigator.of(context).pop()),
|
||||
],
|
||||
);
|
||||
});
|
||||
@ -78,11 +78,11 @@ class _HolidaysState extends State<Holidays> {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Schulferien in Hessen"),
|
||||
title: const Text('Schulferien in Hessen'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.warning_amber_outlined),
|
||||
onPressed: () => showDisclaimer(),
|
||||
onPressed: showDisclaimer,
|
||||
),
|
||||
PopupMenuButton<bool>(
|
||||
initialValue: settings.val().holidaysSettings.showPastEvents,
|
||||
@ -95,7 +95,7 @@ class _HolidaysState extends State<Holidays> {
|
||||
children: [
|
||||
Icon(e ? Icons.history_outlined : Icons.history_toggle_off_outlined, color: Theme.of(context).colorScheme.onSurface),
|
||||
const SizedBox(width: 15),
|
||||
Text(e ? "Alle anzeigen" : "Nur zukünftige anzeigen")
|
||||
Text(e ? 'Alle anzeigen' : 'Nur zukünftige anzeigen')
|
||||
],
|
||||
)
|
||||
)).toList();
|
||||
@ -115,19 +115,19 @@ class _HolidaysState extends State<Holidays> {
|
||||
List<GetHolidaysResponseObject> holidays = value.getHolidaysResponse.data;
|
||||
if(!showPastEvents) holidays = holidays.where((element) => DateTime.parse(element.end).isAfter(DateTime.now())).toList();
|
||||
|
||||
if(holidays.isEmpty) return const PlaceholderView(icon: Icons.search_off, text: "Es wurden keine Ferieneinträge gefunden!");
|
||||
if(holidays.isEmpty) return const PlaceholderView(icon: Icons.search_off, text: 'Es wurden keine Ferieneinträge gefunden!');
|
||||
|
||||
return ListView.builder(
|
||||
itemCount: holidays.length,
|
||||
itemBuilder: (context, index) {
|
||||
GetHolidaysResponseObject holiday = holidays[index];
|
||||
String holidayType = holiday.name.split(" ").first.capitalize();
|
||||
String holidayType = holiday.name.split(' ').first.capitalize();
|
||||
return ListTile(
|
||||
leading: const CenteredLeading(Icon(Icons.calendar_month)),
|
||||
title: Text("$holidayType ab ${parseString(holiday.start)}"),
|
||||
subtitle: Text("bis ${parseString(holiday.end)}"),
|
||||
title: Text('$holidayType ab ${parseString(holiday.start)}'),
|
||||
subtitle: Text('bis ${parseString(holiday.end)}'),
|
||||
onTap: () => showDialog(context: context, builder: (context) => SimpleDialog(
|
||||
title: Text("$holidayType ${holiday.year} in Hessen"),
|
||||
title: Text('$holidayType ${holiday.year} in Hessen'),
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const CenteredLeading(Icon(Icons.signpost_outlined)),
|
||||
@ -136,11 +136,11 @@ class _HolidaysState extends State<Holidays> {
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.arrow_forward),
|
||||
title: Text("vom ${parseString(holiday.start)}"),
|
||||
title: Text('vom ${parseString(holiday.start)}'),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.arrow_back),
|
||||
title: Text("bis zum ${parseString(holiday.end)}"),
|
||||
title: Text('bis zum ${parseString(holiday.end)}'),
|
||||
),
|
||||
Visibility(
|
||||
visible: !DateTime.parse(holiday.start).difference(DateTime.now()).isNegative,
|
||||
|
@ -27,7 +27,7 @@ class _MessageState extends State<Message> {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Marianum Message"),
|
||||
title: const Text('Marianum Message'),
|
||||
),
|
||||
body: Consumer<MessageProps>(builder: (context, value, child) {
|
||||
if(value.primaryLoading()) return const LoadingSpinner();
|
||||
@ -43,7 +43,7 @@ class _MessageState extends State<Message> {
|
||||
children: [Icon(Icons.newspaper)],
|
||||
),
|
||||
title: Text(message.name, overflow: TextOverflow.ellipsis),
|
||||
subtitle: Text("vom ${message.date}"),
|
||||
subtitle: Text('vom ${message.date}'),
|
||||
trailing: const Icon(Icons.arrow_right),
|
||||
onTap: () {
|
||||
Navigator.push(context, MaterialPageRoute(builder: (context) => MessageView(basePath: value.getMessagesResponse.base, message: message)));
|
||||
|
@ -29,12 +29,12 @@ class _MessageViewState extends State<MessageView> {
|
||||
Navigator.of(context).pop();
|
||||
showDialog(context: context, builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text("Fehler beim öffnen"),
|
||||
title: const Text('Fehler beim öffnen'),
|
||||
content: Text("Dokument '${widget.message.name}' konnte nicht geladen werden:\n${e.description}"),
|
||||
actions: [
|
||||
TextButton(onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
}, child: const Text("Ok"))
|
||||
}, child: const Text('Ok'))
|
||||
],
|
||||
);
|
||||
});
|
||||
@ -43,9 +43,9 @@ class _MessageViewState extends State<MessageView> {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => ConfirmDialog(
|
||||
title: "Link öffnen",
|
||||
content: "Möchtest du den folgenden Link öffnen?\n${e.uri}",
|
||||
confirmButton: "Öffnen",
|
||||
title: 'Link öffnen',
|
||||
content: 'Möchtest du den folgenden Link öffnen?\n${e.uri}',
|
||||
confirmButton: 'Öffnen',
|
||||
onConfirm: () => launchUrl(Uri.parse(e.uri), mode: LaunchMode.externalApplication),
|
||||
),
|
||||
);
|
||||
|
@ -8,10 +8,10 @@ class Roomplan extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Raumplan"),
|
||||
title: const Text('Raumplan'),
|
||||
),
|
||||
body: PhotoView(
|
||||
imageProvider: Image.asset("assets/img/raumplan.jpg").image,
|
||||
imageProvider: Image.asset('assets/img/raumplan.jpg').image,
|
||||
minScale: 0.5,
|
||||
maxScale: 2.0,
|
||||
backgroundDecoration: BoxDecoration(color: Theme.of(context).colorScheme.background),
|
||||
|
@ -16,18 +16,18 @@ class _QrShareViewState extends State<QrShareView> {
|
||||
length: 2,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Teile die App"),
|
||||
title: const Text('Teile die App'),
|
||||
bottom: const TabBar(
|
||||
tabs: [
|
||||
Tab(icon: Icon(Icons.android_outlined), text: "Android"),
|
||||
Tab(icon: Icon(Icons.apple_outlined), text: "iOS & iPadOS"),
|
||||
Tab(icon: Icon(Icons.android_outlined), text: 'Android'),
|
||||
Tab(icon: Icon(Icons.apple_outlined), text: 'iOS & iPadOS'),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: const TabBarView(
|
||||
children: [
|
||||
AppSharePlatformView("Für Android", "https://play.google.com/store/apps/details?id=eu.mhsl.marianum.mobile.client"),
|
||||
AppSharePlatformView("Für iOS & iPad", "https://apps.apple.com/us/app/marianum-fulda/id6458789560"),
|
||||
AppSharePlatformView('Für Android', 'https://play.google.com/store/apps/details?id=eu.mhsl.marianum.mobile.client'),
|
||||
AppSharePlatformView('Für iOS & iPad', 'https://apps.apple.com/us/app/marianum-fulda/id6458789560'),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:loader_overlay/loader_overlay.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
import '../../../../widget/sharePositionOrigin.dart';
|
||||
@ -10,34 +9,32 @@ class SelectShareTypeDialog extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LoaderOverlay(
|
||||
child: SimpleDialog(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.qr_code_2_outlined),
|
||||
title: const Text("Per QR-Code"),
|
||||
trailing: const Icon(Icons.arrow_right),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(MaterialPageRoute(builder: (context) => const QrShareView()));
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.link_outlined),
|
||||
title: const Text("Per Link teilen"),
|
||||
trailing: const Icon(Icons.arrow_right),
|
||||
onTap: () {
|
||||
Share.share(
|
||||
sharePositionOrigin: SharePositionOrigin.get(context),
|
||||
subject: "App Teilen",
|
||||
"Hol dir die für das Marianum maßgeschneiderte App:"
|
||||
"\n\nAndroid: https://play.google.com/store/apps/details?id=eu.mhsl.marianum.mobile.client "
|
||||
"\niOS: https://apps.apple.com/us/app/marianum-fulda/id6458789560 "
|
||||
"\n\nViel Spaß!"
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
return SimpleDialog(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.qr_code_2_outlined),
|
||||
title: const Text('Per QR-Code'),
|
||||
trailing: const Icon(Icons.arrow_right),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(MaterialPageRoute(builder: (context) => const QrShareView()));
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.link_outlined),
|
||||
title: const Text('Per Link teilen'),
|
||||
trailing: const Icon(Icons.arrow_right),
|
||||
onTap: () {
|
||||
Share.share(
|
||||
sharePositionOrigin: SharePositionOrigin.get(context),
|
||||
subject: 'App Teilen',
|
||||
'Hol dir die für das Marianum maßgeschneiderte App:'
|
||||
'\n\nAndroid: https://play.google.com/store/apps/details?id=eu.mhsl.marianum.mobile.client '
|
||||
'\niOS: https://apps.apple.com/us/app/marianum-fulda/id6458789560 '
|
||||
'\n\nViel Spaß!'
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,16 @@
|
||||
|
||||
import 'package:feedback/feedback.dart';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:in_app_review/in_app_review.dart';
|
||||
import '../../extensions/renderNotNull.dart';
|
||||
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
||||
|
||||
import '../../widget/ListItem.dart';
|
||||
import '../../widget/centeredLeading.dart';
|
||||
import '../../widget/infoDialog.dart';
|
||||
import '../settings/settings.dart';
|
||||
import 'more/feedback/feedbackSender.dart';
|
||||
import 'more/feedback/feedbackDialog.dart';
|
||||
import 'more/gradeAverages/gradeAverage.dart';
|
||||
import 'more/holidays/holidays.dart';
|
||||
import 'more/message/message.dart';
|
||||
@ -21,21 +25,22 @@ class Overhang extends StatelessWidget {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Mehr"),
|
||||
title: const Text('Mehr'),
|
||||
actions: [
|
||||
IconButton(onPressed: () => Navigator.of(context).push(MaterialPageRoute(builder: (context) => const Settings())), icon: const Icon(Icons.settings))
|
||||
IconButton(onPressed: () => pushScreen(context, screen: const Settings(), withNavBar: false), icon: const Icon(Icons.settings))
|
||||
],
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
const ListItemNavigator(icon: Icons.newspaper, text: "Marianum Message", target: Message()),
|
||||
const ListItemNavigator(icon: Icons.room, text: "Raumplan", target: Roomplan()),
|
||||
const ListItemNavigator(icon: Icons.calculate, text: "Notendurschnittsrechner", target: GradeAverage()),
|
||||
const ListItemNavigator(icon: Icons.calendar_month, text: "Schulferien", target: Holidays()),
|
||||
const ListItemNavigator(icon: Icons.newspaper, text: 'Marianum Message', target: Message()),
|
||||
const ListItemNavigator(icon: Icons.room, text: 'Raumplan', target: Roomplan()),
|
||||
const ListItemNavigator(icon: Icons.calculate, text: 'Notendurschnittsrechner', target: GradeAverage()),
|
||||
const ListItemNavigator(icon: Icons.calendar_month, text: 'Schulferien', target: Holidays()),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.share_outlined),
|
||||
title: const Text("Teile die App"),
|
||||
title: const Text('Teile die App'),
|
||||
subtitle: const Text('Mit Freunden und deiner Klasse teilen'),
|
||||
trailing: const Icon(Icons.arrow_right),
|
||||
onTap: () => showDialog(context: context, builder: (context) => const SelectShareTypeDialog())
|
||||
),
|
||||
@ -44,30 +49,32 @@ class Overhang extends StatelessWidget {
|
||||
builder: (context, snapshot) {
|
||||
if(!snapshot.hasData) return const SizedBox.shrink();
|
||||
|
||||
return Visibility(
|
||||
visible: snapshot.requireData,
|
||||
child: ListTile(
|
||||
leading: const CenteredLeading(Icon(Icons.star_rate_outlined)),
|
||||
title: const Text("App Bewerten"),
|
||||
trailing: const Icon(Icons.arrow_right),
|
||||
onTap: () {
|
||||
InAppReview.instance.openStoreListing(appStoreId: "6458789560").then(
|
||||
(value) => InfoDialog.show(context, "Vielen Dank!"),
|
||||
String? getPlatformStoreName() {
|
||||
if(Platform.isAndroid) return 'Play store';
|
||||
if(Platform.isIOS) return 'App store';
|
||||
return null;
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
leading: const CenteredLeading(Icon(Icons.star_rate_outlined)),
|
||||
title: const Text('App bewerten'),
|
||||
subtitle: getPlatformStoreName().wrapNullable((data) => Text('Im $data')),
|
||||
trailing: const Icon(Icons.arrow_right),
|
||||
onTap: () {
|
||||
InAppReview.instance.openStoreListing(appStoreId: '6458789560').then(
|
||||
(value) => InfoDialog.show(context, 'Vielen Dank!'),
|
||||
onError: (error) => InfoDialog.show(context, error.toString())
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const CenteredLeading(Icon(Icons.feedback_outlined)),
|
||||
title: const Text("Du hast eine Idee?"),
|
||||
subtitle: const Text("Fehler und Verbessungsvorschläge"),
|
||||
title: const Text('Du hast eine Idee?'),
|
||||
subtitle: const Text('Fehler und Verbessungsvorschläge'),
|
||||
trailing: const Icon(Icons.arrow_right),
|
||||
onTap: () {
|
||||
BetterFeedback.of(context).show((UserFeedback feedback) => FeedbackSender.send(context, feedback));
|
||||
},
|
||||
onTap: () => showDialog(context: context, barrierDismissible: false, builder: (context) => const FeedbackDialog()),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -66,7 +66,7 @@ class _ChatInfoState extends State<ChatInfo> {
|
||||
if(participants != null) ...[
|
||||
ListTile(
|
||||
leading: const Icon(Icons.supervised_user_circle),
|
||||
title: Text("${participants!.data.length} Teilnehmer"),
|
||||
title: Text('${participants!.data.length} Teilnehmer'),
|
||||
trailing: const Icon(Icons.arrow_right),
|
||||
onTap: () => TalkNavigator.pushSplitView(context, ParticipantsListView(participants!)),
|
||||
),
|
||||
|
@ -16,7 +16,7 @@ class _ParticipantsListViewState extends State<ParticipantsListView> {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Teilnehmende"),
|
||||
title: const Text('Teilnehmende'),
|
||||
),
|
||||
body: ListView(
|
||||
children: widget.participantsResponse.data.map((participant) {
|
||||
|
@ -40,9 +40,9 @@ class _ChatListState extends State<ChatList> {
|
||||
|
||||
ConfirmDialog(
|
||||
icon: Icons.notifications_active_outlined,
|
||||
title: "Benachrichtigungen aktivieren",
|
||||
content: "Auf wunsch kannst du Push-Benachrichtigungen aktivieren. Deine Einstellungen kannst du jederzeit ändern.",
|
||||
confirmButton: "Weiter",
|
||||
title: 'Benachrichtigungen aktivieren',
|
||||
content: 'Auf wunsch kannst du Push-Benachrichtigungen aktivieren. Deine Einstellungen kannst du jederzeit ändern.',
|
||||
confirmButton: 'Weiter',
|
||||
onConfirm: () {
|
||||
FirebaseMessaging.instance.requestPermission(
|
||||
provisional: false
|
||||
@ -53,7 +53,7 @@ class _ChatListState extends State<ChatList> {
|
||||
break;
|
||||
case AuthorizationStatus.denied:
|
||||
showDialog(context: context, builder: (context) => const AlertDialog(
|
||||
content: Text("Du kannst die Benachrichtigungen später jederzeit in den App-Einstellungen aktivieren."),
|
||||
content: Text('Du kannst die Benachrichtigungen später jederzeit in den App-Einstellungen aktivieren.'),
|
||||
));
|
||||
break;
|
||||
default:
|
||||
@ -79,7 +79,7 @@ class _ChatListState extends State<ChatList> {
|
||||
breakpoint: 1000,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Talk"),
|
||||
title: const Text('Talk'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.search),
|
||||
@ -91,16 +91,16 @@ class _ChatListState extends State<ChatList> {
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
heroTag: "createChat",
|
||||
heroTag: 'createChat',
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
onPressed: () async {
|
||||
showSearch(context: context, delegate: JoinChat()).then((username) {
|
||||
if(username == null) return;
|
||||
|
||||
ConfirmDialog(
|
||||
title: "Chat starten",
|
||||
title: 'Chat starten',
|
||||
content: "Möchtest du einen Chat mit Nutzer '$username' starten?",
|
||||
confirmButton: "Chat starten",
|
||||
confirmButton: 'Chat starten',
|
||||
onConfirm: () {
|
||||
CreateRoom(CreateRoomParams(
|
||||
roomType: 1,
|
||||
@ -136,7 +136,10 @@ class _ChatListState extends State<ChatList> {
|
||||
_query(renew: true);
|
||||
return Future.delayed(const Duration(seconds: 3));
|
||||
},
|
||||
child: ListView(children: chats),
|
||||
child: ListView(
|
||||
padding: EdgeInsets.zero,
|
||||
children: chats
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -1,7 +1,6 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:loader_overlay/loader_overlay.dart';
|
||||
import 'package:marianum_mobile/extensions/dateTime.dart';
|
||||
import '../../../extensions/dateTime.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../../api/marianumcloud/talk/chat/getChatResponse.dart';
|
||||
@ -52,8 +51,8 @@ class _ChatViewState extends State<ChatView> {
|
||||
data.getChatResponse.sortByTimestamp().forEach((element) {
|
||||
DateTime elementDate = DateTime.fromMillisecondsSinceEpoch(element.timestamp * 1000);
|
||||
|
||||
if(element.systemMessage.contains("reaction")) return;
|
||||
int commonRead = int.parse(data.getChatResponse.headers?['x-chat-last-common-read'] ?? "0");
|
||||
if(element.systemMessage.contains('reaction')) return;
|
||||
int commonRead = int.parse(data.getChatResponse.headers?['x-chat-last-common-read'] ?? '0');
|
||||
|
||||
if(!elementDate.isSameDay(lastDate)) {
|
||||
lastDate = elementDate;
|
||||
@ -82,8 +81,8 @@ class _ChatViewState extends State<ChatView> {
|
||||
context: context,
|
||||
isSender: false,
|
||||
bubbleData: GetChatResponseObject.getTextDummy(
|
||||
"Zurzeit können in dieser App nur die letzten 200 vergangenen Nachrichten angezeigt werden. "
|
||||
"Um ältere Nachrichten abzurufen verwende die Webversion unter https://cloud.marianum-fulda.de"
|
||||
'Zurzeit können in dieser App nur die letzten 200 vergangenen Nachrichten angezeigt werden. '
|
||||
'Um ältere Nachrichten abzurufen verwende die Webversion unter https://cloud.marianum-fulda.de'
|
||||
),
|
||||
chatData: widget.room,
|
||||
refetch: _query,
|
||||
@ -92,6 +91,7 @@ class _ChatViewState extends State<ChatView> {
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xffefeae2),
|
||||
appBar: ClickableAppBar(
|
||||
onTap: () {
|
||||
TalkNavigator.pushSplitView(context, ChatInfo(widget.room));
|
||||
@ -111,30 +111,31 @@ class _ChatViewState extends State<ChatView> {
|
||||
body: Container(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: const AssetImage("assets/background/chat.png"),
|
||||
image: const AssetImage('assets/background/chat.png'),
|
||||
scale: 1.5,
|
||||
opacity: 1,
|
||||
repeat: ImageRepeat.repeat,
|
||||
invertColors: AppTheme.isDarkMode(context)
|
||||
)
|
||||
),
|
||||
child: LoaderOverlay(
|
||||
child: data.primaryLoading() ? const LoadingSpinner() : Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView(
|
||||
reverse: true,
|
||||
controller: _listController,
|
||||
children: messages.reversed.toList(),
|
||||
),
|
||||
child: data.primaryLoading() ? const LoadingSpinner() : Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView(
|
||||
reverse: true,
|
||||
controller: _listController,
|
||||
children: messages.reversed.toList(),
|
||||
),
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: SafeArea(child: ChatTextfield(widget.room.token)),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
),
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: TalkNavigator.isSecondaryVisible(context)
|
||||
? ChatTextfield(widget.room.token)
|
||||
: SafeArea(child: ChatTextfield(widget.room.token)
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -5,7 +5,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:jiffy/jiffy.dart';
|
||||
import 'package:marianum_mobile/extensions/text.dart';
|
||||
import '../../../../extensions/text.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../../../api/marianumcloud/talk/chat/getChatResponse.dart';
|
||||
@ -114,7 +114,7 @@ class _ChatBubbleState extends State<ChatBubble> {
|
||||
);
|
||||
|
||||
Text timeText = Text(
|
||||
Jiffy.parseFromMillisecondsSinceEpoch(widget.bubbleData.timestamp * 1000).format(pattern: "HH:mm"),
|
||||
Jiffy.parseFromMillisecondsSinceEpoch(widget.bubbleData.timestamp * 1000).format(pattern: 'HH:mm'),
|
||||
textAlign: TextAlign.end,
|
||||
style: TextStyle(color: widget.timeIconColor, fontSize: widget.timeIconSize),
|
||||
);
|
||||
@ -184,7 +184,7 @@ class _ChatBubbleState extends State<ChatBubble> {
|
||||
),
|
||||
onLongPress: () {
|
||||
showDialog(context: context, builder: (context) {
|
||||
List<String> commonReactions = ["👍", "👎", "😆", "❤️", "👀", "🤔"];
|
||||
List<String> commonReactions = ['👍', '👎', '😆', '❤️', '👀', '🤔'];
|
||||
bool canReact = widget.bubbleData.messageType == GetRoomResponseObjectMessageType.comment;
|
||||
return SimpleDialog(
|
||||
children: [
|
||||
@ -222,7 +222,7 @@ class _ChatBubbleState extends State<ChatBubble> {
|
||||
visible: canReact,
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.add_reaction_outlined),
|
||||
title: const Text("Reaktionen"),
|
||||
title: const Text('Reaktionen'),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(MaterialPageRoute(builder: (context) => MessageReactions(
|
||||
token: widget.chatData.token,
|
||||
@ -235,7 +235,7 @@ class _ChatBubbleState extends State<ChatBubble> {
|
||||
visible: !message.containsFile,
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.copy),
|
||||
title: const Text("Nachricht kopieren"),
|
||||
title: const Text('Nachricht kopieren'),
|
||||
onTap: () => {
|
||||
Clipboard.setData(ClipboardData(text: widget.bubbleData.message)),
|
||||
Navigator.of(context).pop(),
|
||||
@ -256,7 +256,7 @@ class _ChatBubbleState extends State<ChatBubble> {
|
||||
visible: widget.isSender && DateTime.fromMillisecondsSinceEpoch(widget.bubbleData.timestamp * 1000).add(const Duration(hours: 6)).isAfter(DateTime.now()),
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.delete_outline),
|
||||
title: const Text("Nachricht löschen"),
|
||||
title: const Text('Nachricht löschen'),
|
||||
onTap: () {
|
||||
DeleteMessage(widget.chatData.token, widget.bubbleData.id).run().then((value) {
|
||||
Provider.of<ChatProps>(context, listen: false).run();
|
||||
@ -276,12 +276,12 @@ class _ChatBubbleState extends State<ChatBubble> {
|
||||
if(downloadProgress > 0) {
|
||||
showDialog(context: context, builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text("Download abbrechen?"),
|
||||
content: const Text("Möchtest du den Download abbrechen?"),
|
||||
title: const Text('Download abbrechen?'),
|
||||
content: const Text('Möchtest du den Download abbrechen?'),
|
||||
actions: [
|
||||
TextButton(onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
}, child: const Text("Nein")),
|
||||
}, child: const Text('Nein')),
|
||||
TextButton(onPressed: () {
|
||||
downloadCore?.then((value) {
|
||||
if(!value.isCancelled) value.cancel();
|
||||
@ -291,7 +291,7 @@ class _ChatBubbleState extends State<ChatBubble> {
|
||||
downloadProgress = 0;
|
||||
downloadCore = null;
|
||||
});
|
||||
}, child: const Text("Ja, Abbrechen"))
|
||||
}, child: const Text('Ja, Abbrechen'))
|
||||
],
|
||||
);
|
||||
});
|
||||
@ -336,7 +336,7 @@ class _ChatBubbleState extends State<ChatBubble> {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(right: 2.5, left: 2.5),
|
||||
child: ActionChip(
|
||||
label: Text("${e.key} ${e.value}"),
|
||||
label: Text('${e.key} ${e.value}'),
|
||||
visualDensity: const VisualDensity(vertical: VisualDensity.minimumDensity, horizontal: VisualDensity.minimumDensity),
|
||||
padding: EdgeInsets.zero,
|
||||
backgroundColor: hasSelfReacted ? Theme.of(context).primaryColor : null,
|
||||
|
@ -14,14 +14,14 @@ class ChatMessage {
|
||||
Map<String, RichObjectString>? originalData;
|
||||
|
||||
RichObjectString? file;
|
||||
String content = "";
|
||||
String content = '';
|
||||
|
||||
bool get containsFile => file != null;
|
||||
|
||||
ChatMessage({required this.originalMessage, this.originalData}) {
|
||||
if(originalData?.containsKey("file") ?? false) {
|
||||
if(originalData?.containsKey('file') ?? false) {
|
||||
file = originalData?['file'];
|
||||
content = file?.name ?? "Datei";
|
||||
content = file?.name ?? 'Datei';
|
||||
} else {
|
||||
content = RichObjectStringProcessor.parseToString(originalMessage, originalData);
|
||||
}
|
||||
@ -56,7 +56,7 @@ class ChatMessage {
|
||||
fadeInDuration: Duration.zero,
|
||||
fadeOutDuration: Duration.zero,
|
||||
errorListener: (value) {},
|
||||
imageUrl: "https://${AccountData().buildHttpAuthString()}@${EndpointData().nextcloud().full()}/index.php/core/preview?fileId=${file!.id}&x=100&y=-1&a=1",
|
||||
imageUrl: 'https://${AccountData().buildHttpAuthString()}@${EndpointData().nextcloud().full()}/index.php/core/preview?fileId=${file!.id}&x=100&y=-1&a=1',
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -41,9 +41,9 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
||||
}
|
||||
|
||||
String filename = "${path.split("/").last.split(".").first}-${const Uuid().v4()}.${path.split(".").last}";
|
||||
String shareFolder = "MarianumMobile";
|
||||
String shareFolder = 'MarianumMobile';
|
||||
WebdavApi.webdav.then((webdav) {
|
||||
webdav.mkcol(PathUri.parse("/$shareFolder"));
|
||||
webdav.mkcol(PathUri.parse('/$shareFolder'));
|
||||
});
|
||||
|
||||
showDialog(context: context, builder: (context) => FileUploadDialog(
|
||||
@ -55,7 +55,7 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
||||
FileSharingApi().share(FileSharingApiParams(
|
||||
shareType: 10,
|
||||
shareWith: widget.sendToToken,
|
||||
path: "$shareFolder/$filename",
|
||||
path: '$shareFolder/$filename',
|
||||
)).then((value) => _query());
|
||||
},
|
||||
), barrierDismissible: false);
|
||||
@ -77,7 +77,7 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_textBoxController.text = settings.val().talkSettings.drafts[widget.sendToToken] ?? "";
|
||||
_textBoxController.text = settings.val().talkSettings.drafts[widget.sendToToken] ?? '';
|
||||
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
@ -95,12 +95,10 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.file_open),
|
||||
title: const Text("Aus Dateien auswählen"),
|
||||
title: const Text('Aus Dateien auswählen'),
|
||||
onTap: () {
|
||||
context.loaderOverlay.show();
|
||||
FilePick.documentPick().then((value) {
|
||||
mediaUpload(value);
|
||||
});
|
||||
FilePick.documentPick().then(mediaUpload);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
@ -108,7 +106,7 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
||||
visible: !Platform.isIOS,
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.image),
|
||||
title: const Text("Aus Gallerie auswählen"),
|
||||
title: const Text('Aus Gallerie auswählen'),
|
||||
onTap: () {
|
||||
context.loaderOverlay.show();
|
||||
FilePick.galleryPick().then((value) {
|
||||
@ -147,12 +145,12 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
||||
maxLines: 7,
|
||||
minLines: 1,
|
||||
decoration: const InputDecoration(
|
||||
hintText: "Nachricht schreiben...",
|
||||
hintText: 'Nachricht schreiben...',
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onChanged: (String text) {
|
||||
if(text.trim().toLowerCase() == "marbot marbot marbot") {
|
||||
var newText = "Roboter sind cool und so, aber Marbots sind besser!";
|
||||
if(text.trim().toLowerCase() == 'marbot marbot marbot') {
|
||||
var newText = 'Roboter sind cool und so, aber Marbots sind besser!';
|
||||
_textBoxController.text = newText;
|
||||
text = newText;
|
||||
}
|
||||
@ -175,8 +173,8 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
_textBoxController.text = "";
|
||||
setDraft("");
|
||||
_textBoxController.text = '';
|
||||
setDraft('');
|
||||
});
|
||||
},
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user