This commit is contained in:
Márton Kiss
2023-05-26 21:25:00 +02:00
parent 9e3b805fdd
commit 1558794e93
528 changed files with 38239 additions and 37732 deletions

View File

@@ -1,165 +1,165 @@
import 'dart:convert';
import 'dart:developer';
import 'package:filcnaplo/models/config.dart';
import 'package:filcnaplo/models/news.dart';
import 'package:filcnaplo/models/release.dart';
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/models/supporter.dart';
import 'package:filcnaplo_kreta_api/models/school.dart';
import 'package:http/http.dart' as http;
import 'package:connectivity_plus/connectivity_plus.dart';
class FilcAPI {
// Public API
static const schoolList = "https://filcnaplo.hu/v2/school_list.json";
static const news = "https://filcnaplo.hu/v2/news.json";
static const supporters = "https://api.filcnaplo.hu/sponsors";
// Private API
static const config = "https://api.filcnaplo.hu/config";
static const reportApi = "https://api.filcnaplo.hu/report";
static const premiumApi = "https://api.filcnaplo.hu/premium/activate";
// static const premiumScopesApi = "https://api.filcnaplo.hu/premium/scopes";
// Updates
static const repo = "filc/naplo";
static const releases = "https://api.github.com/repos/$repo/releases";
static Future<bool> checkConnectivity() async => (await Connectivity().checkConnectivity()) != ConnectivityResult.none;
static Future<List<School>?> getSchools() async {
try {
http.Response res = await http.get(Uri.parse(schoolList));
if (res.statusCode == 200) {
List<School> schools = (jsonDecode(res.body) as List).cast<Map>().map((json) => School.fromJson(json)).toList();
schools.add(School(
city: "Tiszabura",
instituteCode: "supporttest-reni-tiszabura-teszt01",
name: "FILC Éles Reni tiszabura-teszt",
));
return schools;
} else {
throw "HTTP ${res.statusCode}: ${res.body}";
}
} on Exception catch (error, stacktrace) {
log("ERROR: FilcAPI.getSchools: $error $stacktrace");
}
return null;
}
static Future<Config?> getConfig(SettingsProvider settings) async {
final userAgent = SettingsProvider.defaultSettings().config.userAgent;
Map<String, String> headers = {
"x-filc-id": settings.xFilcId,
"user-agent": userAgent,
};
log("[CONFIG] x-filc-id: \"${settings.xFilcId}\"");
log("[CONFIG] user-agent: \"$userAgent\"");
try {
http.Response res = await http.get(Uri.parse(config), headers: headers);
if (res.statusCode == 200) {
return Config.fromJson(jsonDecode(res.body));
} else if (res.statusCode == 429) {
res = await http.get(Uri.parse(config));
if (res.statusCode == 200) return Config.fromJson(jsonDecode(res.body));
}
throw "HTTP ${res.statusCode}: ${res.body}";
} on Exception catch (error, stacktrace) {
log("ERROR: FilcAPI.getConfig: $error $stacktrace");
}
return null;
}
static Future<List<News>?> getNews() async {
try {
http.Response res = await http.get(Uri.parse(news));
if (res.statusCode == 200) {
return (jsonDecode(res.body) as List).cast<Map>().map((e) => News.fromJson(e)).toList();
} else {
throw "HTTP ${res.statusCode}: ${res.body}";
}
} on Exception catch (error, stacktrace) {
log("ERROR: FilcAPI.getNews: $error $stacktrace");
}
return null;
}
static Future<Supporters?> getSupporters() async {
try {
http.Response res = await http.get(Uri.parse(supporters));
if (res.statusCode == 200) {
return Supporters.fromJson(jsonDecode(res.body));
} else {
throw "HTTP ${res.statusCode}: ${res.body}";
}
} on Exception catch (error, stacktrace) {
log("ERROR: FilcAPI.getSupporters: $error $stacktrace");
}
return null;
}
static Future<List<Release>?> getReleases() async {
try {
http.Response res = await http.get(Uri.parse(releases));
if (res.statusCode == 200) {
return (jsonDecode(res.body) as List).cast<Map>().map((e) => Release.fromJson(e)).toList();
} else {
throw "HTTP ${res.statusCode}: ${res.body}";
}
} on Exception catch (error, stacktrace) {
log("ERROR: FilcAPI.getReleases: $error $stacktrace");
}
return null;
}
static Future<http.StreamedResponse?> downloadRelease(ReleaseDownload release) {
try {
var client = http.Client();
var request = http.Request('GET', Uri.parse(release.url));
return client.send(request);
} on Exception catch (error, stacktrace) {
log("ERROR: FilcAPI.downloadRelease: $error $stacktrace");
return Future.value(null);
}
}
static Future<void> sendReport(ErrorReport report) async {
try {
http.Response res = await http.post(Uri.parse(reportApi), body: {
"os": report.os,
"version": report.version,
"error": report.error,
"stack_trace": report.stack,
});
if (res.statusCode != 200) {
throw "HTTP ${res.statusCode}: ${res.body}";
}
} on Exception catch (error, stacktrace) {
log("ERROR: FilcAPI.sendReport: $error $stacktrace");
}
}
}
class ErrorReport {
String stack;
String os;
String version;
String error;
ErrorReport({
required this.stack,
required this.os,
required this.version,
required this.error,
});
}
import 'dart:convert';
import 'dart:developer';
import 'package:filcnaplo/models/config.dart';
import 'package:filcnaplo/models/news.dart';
import 'package:filcnaplo/models/release.dart';
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/models/supporter.dart';
import 'package:filcnaplo_kreta_api/models/school.dart';
import 'package:http/http.dart' as http;
import 'package:connectivity_plus/connectivity_plus.dart';
class FilcAPI {
// Public API
static const schoolList = "https://filcnaplo.hu/v2/school_list.json";
static const news = "https://filcnaplo.hu/v2/news.json";
static const supporters = "https://api.filcnaplo.hu/sponsors";
// Private API
static const config = "https://api.filcnaplo.hu/config";
static const reportApi = "https://api.filcnaplo.hu/report";
static const premiumApi = "https://api.filcnaplo.hu/premium/activate";
// static const premiumScopesApi = "https://api.filcnaplo.hu/premium/scopes";
// Updates
static const repo = "filc/naplo";
static const releases = "https://api.github.com/repos/$repo/releases";
static Future<bool> checkConnectivity() async => (await Connectivity().checkConnectivity()) != ConnectivityResult.none;
static Future<List<School>?> getSchools() async {
try {
http.Response res = await http.get(Uri.parse(schoolList));
if (res.statusCode == 200) {
List<School> schools = (jsonDecode(res.body) as List).cast<Map>().map((json) => School.fromJson(json)).toList();
schools.add(School(
city: "Tiszabura",
instituteCode: "supporttest-reni-tiszabura-teszt01",
name: "FILC Éles Reni tiszabura-teszt",
));
return schools;
} else {
throw "HTTP ${res.statusCode}: ${res.body}";
}
} on Exception catch (error, stacktrace) {
log("ERROR: FilcAPI.getSchools: $error $stacktrace");
}
return null;
}
static Future<Config?> getConfig(SettingsProvider settings) async {
final userAgent = SettingsProvider.defaultSettings().config.userAgent;
Map<String, String> headers = {
"x-filc-id": settings.xFilcId,
"user-agent": userAgent,
};
log("[CONFIG] x-filc-id: \"${settings.xFilcId}\"");
log("[CONFIG] user-agent: \"$userAgent\"");
try {
http.Response res = await http.get(Uri.parse(config), headers: headers);
if (res.statusCode == 200) {
return Config.fromJson(jsonDecode(res.body));
} else if (res.statusCode == 429) {
res = await http.get(Uri.parse(config));
if (res.statusCode == 200) return Config.fromJson(jsonDecode(res.body));
}
throw "HTTP ${res.statusCode}: ${res.body}";
} on Exception catch (error, stacktrace) {
log("ERROR: FilcAPI.getConfig: $error $stacktrace");
}
return null;
}
static Future<List<News>?> getNews() async {
try {
http.Response res = await http.get(Uri.parse(news));
if (res.statusCode == 200) {
return (jsonDecode(res.body) as List).cast<Map>().map((e) => News.fromJson(e)).toList();
} else {
throw "HTTP ${res.statusCode}: ${res.body}";
}
} on Exception catch (error, stacktrace) {
log("ERROR: FilcAPI.getNews: $error $stacktrace");
}
return null;
}
static Future<Supporters?> getSupporters() async {
try {
http.Response res = await http.get(Uri.parse(supporters));
if (res.statusCode == 200) {
return Supporters.fromJson(jsonDecode(res.body));
} else {
throw "HTTP ${res.statusCode}: ${res.body}";
}
} on Exception catch (error, stacktrace) {
log("ERROR: FilcAPI.getSupporters: $error $stacktrace");
}
return null;
}
static Future<List<Release>?> getReleases() async {
try {
http.Response res = await http.get(Uri.parse(releases));
if (res.statusCode == 200) {
return (jsonDecode(res.body) as List).cast<Map>().map((e) => Release.fromJson(e)).toList();
} else {
throw "HTTP ${res.statusCode}: ${res.body}";
}
} on Exception catch (error, stacktrace) {
log("ERROR: FilcAPI.getReleases: $error $stacktrace");
}
return null;
}
static Future<http.StreamedResponse?> downloadRelease(ReleaseDownload release) {
try {
var client = http.Client();
var request = http.Request('GET', Uri.parse(release.url));
return client.send(request);
} on Exception catch (error, stacktrace) {
log("ERROR: FilcAPI.downloadRelease: $error $stacktrace");
return Future.value(null);
}
}
static Future<void> sendReport(ErrorReport report) async {
try {
http.Response res = await http.post(Uri.parse(reportApi), body: {
"os": report.os,
"version": report.version,
"error": report.error,
"stack_trace": report.stack,
});
if (res.statusCode != 200) {
throw "HTTP ${res.statusCode}: ${res.body}";
}
} on Exception catch (error, stacktrace) {
log("ERROR: FilcAPI.sendReport: $error $stacktrace");
}
}
}
class ErrorReport {
String stack;
String os;
String version;
String error;
ErrorReport({
required this.stack,
required this.os,
required this.version,
required this.error,
});
}

View File

@@ -1,115 +1,115 @@
// ignore_for_file: avoid_print, use_build_context_synchronously
import 'package:filcnaplo/utils/jwt.dart';
import 'package:filcnaplo_kreta_api/providers/absence_provider.dart';
import 'package:filcnaplo_kreta_api/providers/event_provider.dart';
import 'package:filcnaplo_kreta_api/providers/exam_provider.dart';
import 'package:filcnaplo_kreta_api/providers/grade_provider.dart';
import 'package:filcnaplo_kreta_api/providers/homework_provider.dart';
import 'package:filcnaplo_kreta_api/providers/message_provider.dart';
import 'package:filcnaplo_kreta_api/providers/note_provider.dart';
import 'package:filcnaplo_kreta_api/providers/timetable_provider.dart';
import 'package:filcnaplo/api/providers/user_provider.dart';
import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/models/user.dart';
import 'package:filcnaplo_kreta_api/client/api.dart';
import 'package:filcnaplo_kreta_api/client/client.dart';
import 'package:filcnaplo_kreta_api/models/student.dart';
import 'package:filcnaplo_kreta_api/models/week.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:filcnaplo/api/nonce.dart';
enum LoginState { missingFields, invalidGrant, failed, normal, inProgress, success }
Nonce getNonce(String nonce, String username, String instituteCode) {
Nonce nonceEncoder = Nonce(key: [98, 97, 83, 115, 120, 79, 119, 108, 85, 49, 106, 77], nonce: nonce);
nonceEncoder.encode(instituteCode.toUpperCase() + nonce + username.toUpperCase());
return nonceEncoder;
}
Future loginApi({
required String username,
required String password,
required String instituteCode,
required BuildContext context,
void Function(User)? onLogin,
void Function()? onSuccess,
}) async {
Provider.of<KretaClient>(context, listen: false).userAgent = Provider.of<SettingsProvider>(context, listen: false).config.userAgent;
Map<String, String> headers = {
"content-type": "application/x-www-form-urlencoded",
};
String nonceStr = await Provider.of<KretaClient>(context, listen: false).getAPI(KretaAPI.nonce, json: false);
Nonce nonce = getNonce(nonceStr, username, instituteCode);
headers.addAll(nonce.header());
Map? res = await Provider.of<KretaClient>(context, listen: false).postAPI(KretaAPI.login,
headers: headers,
body: User.loginBody(
username: username,
password: password,
instituteCode: instituteCode,
));
if (res != null) {
if (res.containsKey("error")) {
if (res["error"] == "invalid_grant") {
return LoginState.invalidGrant;
}
} else {
if (res.containsKey("access_token")) {
try {
Provider.of<KretaClient>(context, listen: false).accessToken = res["access_token"];
Map? studentJson = await Provider.of<KretaClient>(context, listen: false).getAPI(KretaAPI.student(instituteCode));
Student student = Student.fromJson(studentJson!);
var user = User(
username: username,
password: password,
instituteCode: instituteCode,
name: student.name,
student: student,
role: JwtUtils.getRoleFromJWT(res["access_token"])!,
);
if (onLogin != null) onLogin(user);
// Store User in the database
await Provider.of<DatabaseProvider>(context, listen: false).store.storeUser(user);
Provider.of<UserProvider>(context, listen: false).addUser(user);
Provider.of<UserProvider>(context, listen: false).setUser(user.id);
// Get user data
try {
await Future.wait([
Provider.of<GradeProvider>(context, listen: false).fetch(),
Provider.of<TimetableProvider>(context, listen: false).fetch(week: Week.current()),
Provider.of<ExamProvider>(context, listen: false).fetch(),
Provider.of<HomeworkProvider>(context, listen: false).fetch(),
Provider.of<MessageProvider>(context, listen: false).fetchAll(),
Provider.of<NoteProvider>(context, listen: false).fetch(),
Provider.of<EventProvider>(context, listen: false).fetch(),
Provider.of<AbsenceProvider>(context, listen: false).fetch(),
]);
} catch (error) {
print("WARNING: failed to fetch user data: $error");
}
if (onSuccess != null) onSuccess();
return LoginState.success;
} catch (error) {
print("ERROR: loginApi: $error");
// maybe check debug mode
// ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("ERROR: $error")));
return LoginState.failed;
}
}
}
}
return LoginState.failed;
}
// ignore_for_file: avoid_print, use_build_context_synchronously
import 'package:filcnaplo/utils/jwt.dart';
import 'package:filcnaplo_kreta_api/providers/absence_provider.dart';
import 'package:filcnaplo_kreta_api/providers/event_provider.dart';
import 'package:filcnaplo_kreta_api/providers/exam_provider.dart';
import 'package:filcnaplo_kreta_api/providers/grade_provider.dart';
import 'package:filcnaplo_kreta_api/providers/homework_provider.dart';
import 'package:filcnaplo_kreta_api/providers/message_provider.dart';
import 'package:filcnaplo_kreta_api/providers/note_provider.dart';
import 'package:filcnaplo_kreta_api/providers/timetable_provider.dart';
import 'package:filcnaplo/api/providers/user_provider.dart';
import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/models/user.dart';
import 'package:filcnaplo_kreta_api/client/api.dart';
import 'package:filcnaplo_kreta_api/client/client.dart';
import 'package:filcnaplo_kreta_api/models/student.dart';
import 'package:filcnaplo_kreta_api/models/week.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:filcnaplo/api/nonce.dart';
enum LoginState { missingFields, invalidGrant, failed, normal, inProgress, success }
Nonce getNonce(String nonce, String username, String instituteCode) {
Nonce nonceEncoder = Nonce(key: [98, 97, 83, 115, 120, 79, 119, 108, 85, 49, 106, 77], nonce: nonce);
nonceEncoder.encode(instituteCode.toUpperCase() + nonce + username.toUpperCase());
return nonceEncoder;
}
Future loginApi({
required String username,
required String password,
required String instituteCode,
required BuildContext context,
void Function(User)? onLogin,
void Function()? onSuccess,
}) async {
Provider.of<KretaClient>(context, listen: false).userAgent = Provider.of<SettingsProvider>(context, listen: false).config.userAgent;
Map<String, String> headers = {
"content-type": "application/x-www-form-urlencoded",
};
String nonceStr = await Provider.of<KretaClient>(context, listen: false).getAPI(KretaAPI.nonce, json: false);
Nonce nonce = getNonce(nonceStr, username, instituteCode);
headers.addAll(nonce.header());
Map? res = await Provider.of<KretaClient>(context, listen: false).postAPI(KretaAPI.login,
headers: headers,
body: User.loginBody(
username: username,
password: password,
instituteCode: instituteCode,
));
if (res != null) {
if (res.containsKey("error")) {
if (res["error"] == "invalid_grant") {
return LoginState.invalidGrant;
}
} else {
if (res.containsKey("access_token")) {
try {
Provider.of<KretaClient>(context, listen: false).accessToken = res["access_token"];
Map? studentJson = await Provider.of<KretaClient>(context, listen: false).getAPI(KretaAPI.student(instituteCode));
Student student = Student.fromJson(studentJson!);
var user = User(
username: username,
password: password,
instituteCode: instituteCode,
name: student.name,
student: student,
role: JwtUtils.getRoleFromJWT(res["access_token"])!,
);
if (onLogin != null) onLogin(user);
// Store User in the database
await Provider.of<DatabaseProvider>(context, listen: false).store.storeUser(user);
Provider.of<UserProvider>(context, listen: false).addUser(user);
Provider.of<UserProvider>(context, listen: false).setUser(user.id);
// Get user data
try {
await Future.wait([
Provider.of<GradeProvider>(context, listen: false).fetch(),
Provider.of<TimetableProvider>(context, listen: false).fetch(week: Week.current()),
Provider.of<ExamProvider>(context, listen: false).fetch(),
Provider.of<HomeworkProvider>(context, listen: false).fetch(),
Provider.of<MessageProvider>(context, listen: false).fetchAll(),
Provider.of<NoteProvider>(context, listen: false).fetch(),
Provider.of<EventProvider>(context, listen: false).fetch(),
Provider.of<AbsenceProvider>(context, listen: false).fetch(),
]);
} catch (error) {
print("WARNING: failed to fetch user data: $error");
}
if (onSuccess != null) onSuccess();
return LoginState.success;
} catch (error) {
print("ERROR: loginApi: $error");
// maybe check debug mode
// ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("ERROR: $error")));
return LoginState.failed;
}
}
}
}
return LoginState.failed;
}

View File

@@ -1,25 +1,25 @@
import 'dart:convert';
import 'package:crypto/crypto.dart';
class Nonce {
String nonce;
List<int> key;
String? encoded;
Nonce({required this.nonce, required this.key});
Future encode(String message) async {
List<int> messageBytes = utf8.encode(message);
Hmac hmac = Hmac(sha512, key);
Digest digest = hmac.convert(messageBytes);
encoded = base64.encode(digest.bytes);
}
Map<String, String> header() {
return {
"X-Authorizationpolicy-Nonce": nonce,
"X-Authorizationpolicy-Key": encoded ?? "",
"X-Authorizationpolicy-Version": "v2",
};
}
}
import 'dart:convert';
import 'package:crypto/crypto.dart';
class Nonce {
String nonce;
List<int> key;
String? encoded;
Nonce({required this.nonce, required this.key});
Future encode(String message) async {
List<int> messageBytes = utf8.encode(message);
Hmac hmac = Hmac(sha512, key);
Digest digest = hmac.convert(messageBytes);
encoded = base64.encode(digest.bytes);
}
Map<String, String> header() {
return {
"X-Authorizationpolicy-Nonce": nonce,
"X-Authorizationpolicy-Key": encoded ?? "",
"X-Authorizationpolicy-Version": "v2",
};
}
}

View File

@@ -1,30 +1,30 @@
import 'dart:io';
import 'package:filcnaplo/database/query.dart';
import 'package:filcnaplo/database/store.dart';
import 'package:sqflite/sqflite.dart';
// ignore: depend_on_referenced_packages
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
class DatabaseProvider {
// late Database _database;
late DatabaseQuery query;
late UserDatabaseQuery userQuery;
late DatabaseStore store;
late UserDatabaseStore userStore;
Future<void> init() async {
Database db;
if (Platform.isLinux || Platform.isWindows) {
db = await databaseFactoryFfi.openDatabase("app.db");
} else {
db = await openDatabase("app.db");
}
query = DatabaseQuery(db: db);
store = DatabaseStore(db: db);
userQuery = UserDatabaseQuery(db: db);
userStore = UserDatabaseStore(db: db);
}
}
import 'dart:io';
import 'package:filcnaplo/database/query.dart';
import 'package:filcnaplo/database/store.dart';
import 'package:sqflite/sqflite.dart';
// ignore: depend_on_referenced_packages
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
class DatabaseProvider {
// late Database _database;
late DatabaseQuery query;
late UserDatabaseQuery userQuery;
late DatabaseStore store;
late UserDatabaseStore userStore;
Future<void> init() async {
Database db;
if (Platform.isLinux || Platform.isWindows) {
db = await databaseFactoryFfi.openDatabase("app.db");
} else {
db = await openDatabase("app.db");
}
query = DatabaseQuery(db: db);
store = DatabaseStore(db: db);
userQuery = UserDatabaseQuery(db: db);
userStore = UserDatabaseStore(db: db);
}
}

View File

@@ -1,204 +1,204 @@
// ignore_for_file: no_leading_underscores_for_local_identifiers
import 'dart:async';
import 'dart:io';
import 'package:filcnaplo/helpers/subject.dart';
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo_kreta_api/models/lesson.dart';
import 'package:filcnaplo_kreta_api/models/week.dart';
import 'package:filcnaplo/utils/format.dart';
import 'package:filcnaplo_kreta_api/providers/timetable_provider.dart';
import 'package:flutter/foundation.dart';
import 'package:live_activities/live_activities.dart';
import 'package:filcnaplo_mobile_ui/pages/home/live_card/live_card.i18n.dart';
enum LiveCardState { empty, duringLesson, duringBreak, morning, afternoon, night }
class LiveCardProvider extends ChangeNotifier {
Lesson? currentLesson;
Lesson? nextLesson;
Lesson? prevLesson;
List<Lesson>? nextLessons;
LiveCardState currentState = LiveCardState.empty;
late Timer _timer;
late final TimetableProvider _timetable;
late final SettingsProvider _settings;
late Duration _delay;
final _liveActivitiesPlugin = LiveActivities();
String? _latestActivityId;
Map<String, String> _lastActivity = {};
LiveCardProvider({
required TimetableProvider timetable,
required SettingsProvider settings,
}) : _timetable = timetable,
_settings = settings {
_liveActivitiesPlugin.init(appGroupId: "group.filcnaplo.livecard");
_liveActivitiesPlugin.getAllActivitiesIds().then((value) {
_latestActivityId = value.isNotEmpty ? value.first : null;
});
_timer = Timer.periodic(const Duration(seconds: 1), (timer) => update());
_delay = settings.bellDelayEnabled ? Duration(seconds: settings.bellDelay) : Duration.zero;
update();
}
@override
void dispose() {
_timer.cancel();
if (_latestActivityId != null && Platform.isIOS) _liveActivitiesPlugin.endActivity(_latestActivityId!);
super.dispose();
}
// Debugging
static DateTime _now() {
return DateTime.now();
}
String getFloorDifference() {
final prevFloor = prevLesson!.getFloor();
final nextFloor = nextLesson!.getFloor();
if (prevFloor == null || nextFloor == null || prevFloor == nextFloor) {
return "to room";
}
if (nextFloor == 0) {
return "ground floor";
}
if (nextFloor > prevFloor) {
return "up floor";
} else {
return "down floor";
}
}
Map<String, String> toMap() {
switch (currentState) {
case LiveCardState.duringLesson:
return {
"icon": currentLesson != null ? SubjectIcon.resolveName(subject: currentLesson?.subject) : "book",
"index": currentLesson != null ? '${currentLesson!.lessonIndex}. ' : "",
"title": currentLesson != null ? ShortSubject.resolve(subject: currentLesson?.subject).capital() : "",
"subtitle": currentLesson?.room.replaceAll("_", " ") ?? "",
"description": currentLesson?.description ?? "",
"startDate": ((currentLesson?.start.millisecondsSinceEpoch ?? 0) - _delay.inMilliseconds).toString(),
"endDate": ((currentLesson?.end.millisecondsSinceEpoch ?? 0) - _delay.inMilliseconds).toString(),
"nextSubject": nextLesson != null ? ShortSubject.resolve(subject: nextLesson?.subject).capital() : "",
"nextRoom": nextLesson?.room.replaceAll("_", " ") ?? "",
};
case LiveCardState.duringBreak:
final iconFloorMap = {
"to room": "chevron.right.2",
"up floor": "arrow.up.right",
"down floor": "arrow.down.left",
"ground floor": "arrow.down.left",
};
final diff = getFloorDifference();
return {
"icon": iconFloorMap[diff] ?? "cup.and.saucer",
"title": "Szünet",
"description": "go $diff".i18n.fill([diff != "to room" ? (nextLesson!.getFloor() ?? 0) : nextLesson!.room]),
"startDate": ((prevLesson?.end.millisecondsSinceEpoch ?? 0) - _delay.inMilliseconds).toString(),
"endDate": ((nextLesson?.start.millisecondsSinceEpoch ?? 0) - _delay.inMilliseconds).toString(),
"nextSubject": (nextLesson != null ? ShortSubject.resolve(subject: nextLesson?.subject) : "").capital(),
"nextRoom": nextLesson?.room.replaceAll("_", " ") ?? "",
"index": "",
"subtitle": "",
};
default:
return {};
}
}
void update() async {
if (Platform.isIOS) {
final cmap = toMap();
if (!mapEquals(cmap, _lastActivity)) {
_lastActivity = cmap;
if (_lastActivity.isNotEmpty) {
if (_latestActivityId == null) {
_liveActivitiesPlugin.createActivity(_lastActivity).then((value) => _latestActivityId = value);
} else {
_liveActivitiesPlugin.updateActivity(_latestActivityId!, _lastActivity);
}
} else {
if (_latestActivityId != null) _liveActivitiesPlugin.endActivity(_latestActivityId!);
}
}
}
List<Lesson> today = _today(_timetable);
if (today.isEmpty) {
await _timetable.fetch(week: Week.current());
today = _today(_timetable);
}
_delay = _settings.bellDelayEnabled ? Duration(seconds: _settings.bellDelay) : Duration.zero;
final now = _now().add(_delay);
// Filter cancelled lessons #20
// Filter label lessons #128
today = today.where((lesson) => lesson.status?.name != "Elmaradt" && lesson.subject.id != '' && !lesson.isEmpty).toList();
if (today.isNotEmpty) {
// sort
today.sort((a, b) => a.start.compareTo(b.start));
final _lesson = today.firstWhere((l) => l.start.isBefore(now) && l.end.isAfter(now), orElse: () => Lesson.fromJson({}));
if (_lesson.start.year != 0) {
currentLesson = _lesson;
} else {
currentLesson = null;
}
final _next = today.firstWhere((l) => l.start.isAfter(now), orElse: () => Lesson.fromJson({}));
nextLessons = today.where((l) => l.start.isAfter(now)).toList();
if (_next.start.year != 0) {
nextLesson = _next;
} else {
nextLesson = null;
}
final _prev = today.lastWhere((l) => l.end.isBefore(now), orElse: () => Lesson.fromJson({}));
if (_prev.start.year != 0) {
prevLesson = _prev;
} else {
prevLesson = null;
}
}
if (currentLesson != null) {
currentState = LiveCardState.duringLesson;
} else if (nextLesson != null && prevLesson != null) {
currentState = LiveCardState.duringBreak;
} else if (now.hour >= 12 && now.hour < 20) {
currentState = LiveCardState.afternoon;
} else if (now.hour >= 20) {
currentState = LiveCardState.night;
} else if (now.hour >= 5 && now.hour <= 10) {
currentState = LiveCardState.morning;
} else {
currentState = LiveCardState.empty;
}
notifyListeners();
}
bool get show => currentState != LiveCardState.empty;
Duration get delay => _delay;
bool _sameDate(DateTime a, DateTime b) => (a.year == b.year && a.month == b.month && a.day == b.day);
List<Lesson> _today(TimetableProvider p) => (p.getWeek(Week.current()) ?? []).where((l) => _sameDate(l.date, _now())).toList();
}
// ignore_for_file: no_leading_underscores_for_local_identifiers
import 'dart:async';
import 'dart:io';
import 'package:filcnaplo/helpers/subject.dart';
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo_kreta_api/models/lesson.dart';
import 'package:filcnaplo_kreta_api/models/week.dart';
import 'package:filcnaplo/utils/format.dart';
import 'package:filcnaplo_kreta_api/providers/timetable_provider.dart';
import 'package:flutter/foundation.dart';
import 'package:live_activities/live_activities.dart';
import 'package:filcnaplo_mobile_ui/pages/home/live_card/live_card.i18n.dart';
enum LiveCardState { empty, duringLesson, duringBreak, morning, afternoon, night }
class LiveCardProvider extends ChangeNotifier {
Lesson? currentLesson;
Lesson? nextLesson;
Lesson? prevLesson;
List<Lesson>? nextLessons;
LiveCardState currentState = LiveCardState.empty;
late Timer _timer;
late final TimetableProvider _timetable;
late final SettingsProvider _settings;
late Duration _delay;
final _liveActivitiesPlugin = LiveActivities();
String? _latestActivityId;
Map<String, String> _lastActivity = {};
LiveCardProvider({
required TimetableProvider timetable,
required SettingsProvider settings,
}) : _timetable = timetable,
_settings = settings {
_liveActivitiesPlugin.init(appGroupId: "group.filcnaplo.livecard");
_liveActivitiesPlugin.getAllActivitiesIds().then((value) {
_latestActivityId = value.isNotEmpty ? value.first : null;
});
_timer = Timer.periodic(const Duration(seconds: 1), (timer) => update());
_delay = settings.bellDelayEnabled ? Duration(seconds: settings.bellDelay) : Duration.zero;
update();
}
@override
void dispose() {
_timer.cancel();
if (_latestActivityId != null && Platform.isIOS) _liveActivitiesPlugin.endActivity(_latestActivityId!);
super.dispose();
}
// Debugging
static DateTime _now() {
return DateTime.now();
}
String getFloorDifference() {
final prevFloor = prevLesson!.getFloor();
final nextFloor = nextLesson!.getFloor();
if (prevFloor == null || nextFloor == null || prevFloor == nextFloor) {
return "to room";
}
if (nextFloor == 0) {
return "ground floor";
}
if (nextFloor > prevFloor) {
return "up floor";
} else {
return "down floor";
}
}
Map<String, String> toMap() {
switch (currentState) {
case LiveCardState.duringLesson:
return {
"icon": currentLesson != null ? SubjectIcon.resolveName(subject: currentLesson?.subject) : "book",
"index": currentLesson != null ? '${currentLesson!.lessonIndex}. ' : "",
"title": currentLesson != null ? ShortSubject.resolve(subject: currentLesson?.subject).capital() : "",
"subtitle": currentLesson?.room.replaceAll("_", " ") ?? "",
"description": currentLesson?.description ?? "",
"startDate": ((currentLesson?.start.millisecondsSinceEpoch ?? 0) - _delay.inMilliseconds).toString(),
"endDate": ((currentLesson?.end.millisecondsSinceEpoch ?? 0) - _delay.inMilliseconds).toString(),
"nextSubject": nextLesson != null ? ShortSubject.resolve(subject: nextLesson?.subject).capital() : "",
"nextRoom": nextLesson?.room.replaceAll("_", " ") ?? "",
};
case LiveCardState.duringBreak:
final iconFloorMap = {
"to room": "chevron.right.2",
"up floor": "arrow.up.right",
"down floor": "arrow.down.left",
"ground floor": "arrow.down.left",
};
final diff = getFloorDifference();
return {
"icon": iconFloorMap[diff] ?? "cup.and.saucer",
"title": "Szünet",
"description": "go $diff".i18n.fill([diff != "to room" ? (nextLesson!.getFloor() ?? 0) : nextLesson!.room]),
"startDate": ((prevLesson?.end.millisecondsSinceEpoch ?? 0) - _delay.inMilliseconds).toString(),
"endDate": ((nextLesson?.start.millisecondsSinceEpoch ?? 0) - _delay.inMilliseconds).toString(),
"nextSubject": (nextLesson != null ? ShortSubject.resolve(subject: nextLesson?.subject) : "").capital(),
"nextRoom": nextLesson?.room.replaceAll("_", " ") ?? "",
"index": "",
"subtitle": "",
};
default:
return {};
}
}
void update() async {
if (Platform.isIOS) {
final cmap = toMap();
if (!mapEquals(cmap, _lastActivity)) {
_lastActivity = cmap;
if (_lastActivity.isNotEmpty) {
if (_latestActivityId == null) {
_liveActivitiesPlugin.createActivity(_lastActivity).then((value) => _latestActivityId = value);
} else {
_liveActivitiesPlugin.updateActivity(_latestActivityId!, _lastActivity);
}
} else {
if (_latestActivityId != null) _liveActivitiesPlugin.endActivity(_latestActivityId!);
}
}
}
List<Lesson> today = _today(_timetable);
if (today.isEmpty) {
await _timetable.fetch(week: Week.current());
today = _today(_timetable);
}
_delay = _settings.bellDelayEnabled ? Duration(seconds: _settings.bellDelay) : Duration.zero;
final now = _now().add(_delay);
// Filter cancelled lessons #20
// Filter label lessons #128
today = today.where((lesson) => lesson.status?.name != "Elmaradt" && lesson.subject.id != '' && !lesson.isEmpty).toList();
if (today.isNotEmpty) {
// sort
today.sort((a, b) => a.start.compareTo(b.start));
final _lesson = today.firstWhere((l) => l.start.isBefore(now) && l.end.isAfter(now), orElse: () => Lesson.fromJson({}));
if (_lesson.start.year != 0) {
currentLesson = _lesson;
} else {
currentLesson = null;
}
final _next = today.firstWhere((l) => l.start.isAfter(now), orElse: () => Lesson.fromJson({}));
nextLessons = today.where((l) => l.start.isAfter(now)).toList();
if (_next.start.year != 0) {
nextLesson = _next;
} else {
nextLesson = null;
}
final _prev = today.lastWhere((l) => l.end.isBefore(now), orElse: () => Lesson.fromJson({}));
if (_prev.start.year != 0) {
prevLesson = _prev;
} else {
prevLesson = null;
}
}
if (currentLesson != null) {
currentState = LiveCardState.duringLesson;
} else if (nextLesson != null && prevLesson != null) {
currentState = LiveCardState.duringBreak;
} else if (now.hour >= 12 && now.hour < 20) {
currentState = LiveCardState.afternoon;
} else if (now.hour >= 20) {
currentState = LiveCardState.night;
} else if (now.hour >= 5 && now.hour <= 10) {
currentState = LiveCardState.morning;
} else {
currentState = LiveCardState.empty;
}
notifyListeners();
}
bool get show => currentState != LiveCardState.empty;
Duration get delay => _delay;
bool _sameDate(DateTime a, DateTime b) => (a.year == b.year && a.month == b.month && a.day == b.day);
List<Lesson> _today(TimetableProvider p) => (p.getWeek(Week.current()) ?? []).where((l) => _sameDate(l.date, _now())).toList();
}

View File

@@ -1,85 +1,85 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:math';
import 'package:filcnaplo/api/client.dart';
import 'package:filcnaplo/models/news.dart';
import 'package:filcnaplo/models/settings.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class NewsProvider extends ChangeNotifier {
// Private
late List<News> _news;
late int _state;
late int _fresh;
bool show = false;
late BuildContext _context;
// Public
List<News> get news => _news;
int get state => _fresh - 1;
NewsProvider({
List<News> initialNews = const [],
required BuildContext context,
}) {
_news = List.castFrom(initialNews);
_context = context;
}
Future<void> restore() async {
// Load news state from the database
var state_ = Provider.of<SettingsProvider>(_context, listen: false).newsState;
if (state_ == -1) {
var news_ = await FilcAPI.getNews();
if (news_ != null) {
state_ = news_.length;
_news = news_;
}
}
_state = state_;
Provider.of<SettingsProvider>(_context, listen: false).update(newsState: _state);
}
Future<void> fetch() async {
var news_ = await FilcAPI.getNews();
if (news_ == null) return;
_news = news_;
_fresh = news_.length - _state;
if (_fresh < 0) {
_state = news_.length;
Provider.of<SettingsProvider>(_context, listen: false).update(newsState: _state);
}
_fresh = max(_fresh, 0);
if (_fresh > 0) {
show = true;
notifyListeners();
}
}
void lock() => show = false;
void release() {
if (_fresh == 0) return;
_fresh--;
_state++;
Provider.of<SettingsProvider>(_context, listen: false).update(newsState: _state);
if (_fresh > 0) {
show = true;
} else {
show = false;
}
notifyListeners();
}
}
// ignore_for_file: use_build_context_synchronously
import 'dart:math';
import 'package:filcnaplo/api/client.dart';
import 'package:filcnaplo/models/news.dart';
import 'package:filcnaplo/models/settings.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class NewsProvider extends ChangeNotifier {
// Private
late List<News> _news;
late int _state;
late int _fresh;
bool show = false;
late BuildContext _context;
// Public
List<News> get news => _news;
int get state => _fresh - 1;
NewsProvider({
List<News> initialNews = const [],
required BuildContext context,
}) {
_news = List.castFrom(initialNews);
_context = context;
}
Future<void> restore() async {
// Load news state from the database
var state_ = Provider.of<SettingsProvider>(_context, listen: false).newsState;
if (state_ == -1) {
var news_ = await FilcAPI.getNews();
if (news_ != null) {
state_ = news_.length;
_news = news_;
}
}
_state = state_;
Provider.of<SettingsProvider>(_context, listen: false).update(newsState: _state);
}
Future<void> fetch() async {
var news_ = await FilcAPI.getNews();
if (news_ == null) return;
_news = news_;
_fresh = news_.length - _state;
if (_fresh < 0) {
_state = news_.length;
Provider.of<SettingsProvider>(_context, listen: false).update(newsState: _state);
}
_fresh = max(_fresh, 0);
if (_fresh > 0) {
show = true;
notifyListeners();
}
}
void lock() => show = false;
void release() {
if (_fresh == 0) return;
_fresh--;
_state++;
Provider.of<SettingsProvider>(_context, listen: false).update(newsState: _state);
if (_fresh > 0) {
show = true;
} else {
show = false;
}
notifyListeners();
}
}

View File

@@ -1,79 +1,79 @@
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/widgets.dart';
import 'package:http/http.dart' as http;
enum Status { network, maintenance, syncing }
class StatusProvider extends ChangeNotifier {
final List<Status> _stack = [];
double _progress = 0.0;
ConnectivityResult _networkType = ConnectivityResult.none;
ConnectivityResult get networkType => _networkType;
StatusProvider() {
_handleNetworkChanges();
Connectivity().checkConnectivity().then((value) => _networkType = value);
}
Status? getStatus() => _stack.isNotEmpty ? _stack[0] : null;
// Status progress from 0.0 to 1.0
double get progress => _progress;
void _handleNetworkChanges() {
Connectivity().onConnectivityChanged.listen((event) {
_networkType = event;
if (event == ConnectivityResult.none) {
if (!_stack.contains(Status.network)) {
_stack.insert(0, Status.network);
notifyListeners();
}
} else {
if (_stack.contains(Status.network)) {
_stack.remove(Status.network);
notifyListeners();
}
}
});
}
void triggerRequest(http.Response res) {
if (res.headers.containsKey("x-maintenance-mode") || res.statusCode == 503) {
if (!_stack.contains(Status.maintenance)) {
_stack.insert(0, Status.maintenance);
notifyListeners();
}
} else {
if (_stack.contains(Status.maintenance)) {
_stack.remove(Status.maintenance);
notifyListeners();
}
}
}
void triggerSync({required int current, required int max}) {
double prev = _progress;
if (!_stack.contains(Status.syncing)) {
_stack.add(Status.syncing);
_progress = 0.0;
notifyListeners();
}
if (max == 0) {
_progress = 0.0;
} else {
_progress = current / max;
}
if (_progress == 1.0) {
notifyListeners();
// Wait for animation
Future.delayed(const Duration(milliseconds: 250), () {
_stack.remove(Status.syncing);
notifyListeners();
});
} else if (progress != prev) {
notifyListeners();
}
}
}
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/widgets.dart';
import 'package:http/http.dart' as http;
enum Status { network, maintenance, syncing }
class StatusProvider extends ChangeNotifier {
final List<Status> _stack = [];
double _progress = 0.0;
ConnectivityResult _networkType = ConnectivityResult.none;
ConnectivityResult get networkType => _networkType;
StatusProvider() {
_handleNetworkChanges();
Connectivity().checkConnectivity().then((value) => _networkType = value);
}
Status? getStatus() => _stack.isNotEmpty ? _stack[0] : null;
// Status progress from 0.0 to 1.0
double get progress => _progress;
void _handleNetworkChanges() {
Connectivity().onConnectivityChanged.listen((event) {
_networkType = event;
if (event == ConnectivityResult.none) {
if (!_stack.contains(Status.network)) {
_stack.insert(0, Status.network);
notifyListeners();
}
} else {
if (_stack.contains(Status.network)) {
_stack.remove(Status.network);
notifyListeners();
}
}
});
}
void triggerRequest(http.Response res) {
if (res.headers.containsKey("x-maintenance-mode") || res.statusCode == 503) {
if (!_stack.contains(Status.maintenance)) {
_stack.insert(0, Status.maintenance);
notifyListeners();
}
} else {
if (_stack.contains(Status.maintenance)) {
_stack.remove(Status.maintenance);
notifyListeners();
}
}
}
void triggerSync({required int current, required int max}) {
double prev = _progress;
if (!_stack.contains(Status.syncing)) {
_stack.add(Status.syncing);
_progress = 0.0;
notifyListeners();
}
if (max == 0) {
_progress = 0.0;
} else {
_progress = current / max;
}
if (_progress == 1.0) {
notifyListeners();
// Wait for animation
Future.delayed(const Duration(milliseconds: 250), () {
_stack.remove(Status.syncing);
notifyListeners();
});
} else if (progress != prev) {
notifyListeners();
}
}
}

View File

@@ -1,88 +1,88 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:io';
import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/api/providers/status_provider.dart';
import 'package:filcnaplo/api/providers/user_provider.dart';
import 'package:filcnaplo_kreta_api/client/api.dart';
import 'package:filcnaplo_kreta_api/client/client.dart';
import 'package:filcnaplo_kreta_api/models/student.dart';
import 'package:filcnaplo_kreta_api/models/week.dart';
import 'package:filcnaplo_kreta_api/providers/absence_provider.dart';
import 'package:filcnaplo_kreta_api/providers/event_provider.dart';
import 'package:filcnaplo_kreta_api/providers/exam_provider.dart';
import 'package:filcnaplo_kreta_api/providers/grade_provider.dart';
import 'package:filcnaplo_kreta_api/providers/homework_provider.dart';
import 'package:filcnaplo_kreta_api/providers/message_provider.dart';
import 'package:filcnaplo_kreta_api/providers/note_provider.dart';
import 'package:filcnaplo_kreta_api/providers/timetable_provider.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
import 'package:home_widget/home_widget.dart';
// Mutex
bool lock = false;
Future<void> syncAll(BuildContext context) {
if (lock) return Future.value();
// Lock
lock = true;
// ignore: avoid_print
print("INFO Syncing all");
UserProvider user = Provider.of<UserProvider>(context, listen: false);
StatusProvider statusProvider = Provider.of<StatusProvider>(context, listen: false);
List<Future<void>> tasks = [];
int taski = 0;
Future<void> syncStatus(Future<void> future) async {
await future.onError((error, stackTrace) => null);
taski++;
statusProvider.triggerSync(current: taski, max: tasks.length);
}
tasks = [
syncStatus(Provider.of<GradeProvider>(context, listen: false).fetch()),
syncStatus(Provider.of<TimetableProvider>(context, listen: false).fetch(week: Week.current())),
syncStatus(Provider.of<ExamProvider>(context, listen: false).fetch()),
syncStatus(Provider.of<HomeworkProvider>(context, listen: false).fetch(from: DateTime.now().subtract(const Duration(days: 30)))),
syncStatus(Provider.of<MessageProvider>(context, listen: false).fetchAll()),
syncStatus(Provider.of<NoteProvider>(context, listen: false).fetch()),
syncStatus(Provider.of<EventProvider>(context, listen: false).fetch()),
syncStatus(Provider.of<AbsenceProvider>(context, listen: false).fetch()),
// Sync student
syncStatus(() async {
if (user.user == null) return;
Map? studentJson = await Provider.of<KretaClient>(context, listen: false).getAPI(KretaAPI.student(user.instituteCode!));
if (studentJson == null) return;
Student student = Student.fromJson(studentJson);
user.user?.name = student.name;
// Store user
await Provider.of<DatabaseProvider>(context, listen: false).store.storeUser(user.user!);
}()),
];
Future<bool?> updateWidget() async {
try {
return HomeWidget.updateWidget(name: 'widget_timetable.WidgetTimetable');
} on PlatformException catch (exception) {
debugPrint('Error Updating Widget. $exception');
}
return false;
}
return Future.wait(tasks).then((value) {
// Unlock
lock = false;
// Update Widget
if (Platform.isAndroid) updateWidget();
});
}
// ignore_for_file: use_build_context_synchronously
import 'dart:io';
import 'package:filcnaplo/api/providers/database_provider.dart';
import 'package:filcnaplo/api/providers/status_provider.dart';
import 'package:filcnaplo/api/providers/user_provider.dart';
import 'package:filcnaplo_kreta_api/client/api.dart';
import 'package:filcnaplo_kreta_api/client/client.dart';
import 'package:filcnaplo_kreta_api/models/student.dart';
import 'package:filcnaplo_kreta_api/models/week.dart';
import 'package:filcnaplo_kreta_api/providers/absence_provider.dart';
import 'package:filcnaplo_kreta_api/providers/event_provider.dart';
import 'package:filcnaplo_kreta_api/providers/exam_provider.dart';
import 'package:filcnaplo_kreta_api/providers/grade_provider.dart';
import 'package:filcnaplo_kreta_api/providers/homework_provider.dart';
import 'package:filcnaplo_kreta_api/providers/message_provider.dart';
import 'package:filcnaplo_kreta_api/providers/note_provider.dart';
import 'package:filcnaplo_kreta_api/providers/timetable_provider.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
import 'package:home_widget/home_widget.dart';
// Mutex
bool lock = false;
Future<void> syncAll(BuildContext context) {
if (lock) return Future.value();
// Lock
lock = true;
// ignore: avoid_print
print("INFO Syncing all");
UserProvider user = Provider.of<UserProvider>(context, listen: false);
StatusProvider statusProvider = Provider.of<StatusProvider>(context, listen: false);
List<Future<void>> tasks = [];
int taski = 0;
Future<void> syncStatus(Future<void> future) async {
await future.onError((error, stackTrace) => null);
taski++;
statusProvider.triggerSync(current: taski, max: tasks.length);
}
tasks = [
syncStatus(Provider.of<GradeProvider>(context, listen: false).fetch()),
syncStatus(Provider.of<TimetableProvider>(context, listen: false).fetch(week: Week.current())),
syncStatus(Provider.of<ExamProvider>(context, listen: false).fetch()),
syncStatus(Provider.of<HomeworkProvider>(context, listen: false).fetch(from: DateTime.now().subtract(const Duration(days: 30)))),
syncStatus(Provider.of<MessageProvider>(context, listen: false).fetchAll()),
syncStatus(Provider.of<NoteProvider>(context, listen: false).fetch()),
syncStatus(Provider.of<EventProvider>(context, listen: false).fetch()),
syncStatus(Provider.of<AbsenceProvider>(context, listen: false).fetch()),
// Sync student
syncStatus(() async {
if (user.user == null) return;
Map? studentJson = await Provider.of<KretaClient>(context, listen: false).getAPI(KretaAPI.student(user.instituteCode!));
if (studentJson == null) return;
Student student = Student.fromJson(studentJson);
user.user?.name = student.name;
// Store user
await Provider.of<DatabaseProvider>(context, listen: false).store.storeUser(user.user!);
}()),
];
Future<bool?> updateWidget() async {
try {
return HomeWidget.updateWidget(name: 'widget_timetable.WidgetTimetable');
} on PlatformException catch (exception) {
debugPrint('Error Updating Widget. $exception');
}
return false;
}
return Future.wait(tasks).then((value) {
// Unlock
lock = false;
// Update Widget
if (Platform.isAndroid) updateWidget();
});
}

View File

@@ -1,39 +1,39 @@
import 'dart:io';
import 'package:filcnaplo/api/client.dart';
import 'package:filcnaplo/models/release.dart';
import 'package:flutter/material.dart';
class UpdateProvider extends ChangeNotifier {
// Private
late List<Release> _releases;
bool _available = false;
bool get available => _available && _releases.isNotEmpty;
// Public
List<Release> get releases => _releases;
UpdateProvider({
List<Release> initialReleases = const [],
required BuildContext context,
}) {
_releases = List.castFrom(initialReleases);
}
static const currentVersion = String.fromEnvironment("APPVER", defaultValue: "1.0");
Future<void> fetch() async {
if (!Platform.isAndroid) return;
_releases = await FilcAPI.getReleases() ?? [];
_releases.sort((a, b) => -a.version.compareTo(b.version));
// Check for new releases
if (_releases.isNotEmpty) {
_available = _releases.first.version.compareTo(Version.fromString(currentVersion)) == 1;
// ignore: avoid_print
if (_available) print("INFO: New update: ${releases.first.version}");
notifyListeners();
}
}
}
import 'dart:io';
import 'package:filcnaplo/api/client.dart';
import 'package:filcnaplo/models/release.dart';
import 'package:flutter/material.dart';
class UpdateProvider extends ChangeNotifier {
// Private
late List<Release> _releases;
bool _available = false;
bool get available => _available && _releases.isNotEmpty;
// Public
List<Release> get releases => _releases;
UpdateProvider({
List<Release> initialReleases = const [],
required BuildContext context,
}) {
_releases = List.castFrom(initialReleases);
}
static const currentVersion = String.fromEnvironment("APPVER", defaultValue: "1.0");
Future<void> fetch() async {
if (!Platform.isAndroid) return;
_releases = await FilcAPI.getReleases() ?? [];
_releases.sort((a, b) => -a.version.compareTo(b.version));
// Check for new releases
if (_releases.isNotEmpty) {
_available = _releases.first.version.compareTo(Version.fromString(currentVersion)) == 1;
// ignore: avoid_print
if (_available) print("INFO: New update: ${releases.first.version}");
notifyListeners();
}
}
}

View File

@@ -1,78 +1,78 @@
import 'dart:io';
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/models/user.dart';
import 'package:filcnaplo_kreta_api/models/student.dart';
import 'package:flutter/foundation.dart';
import 'package:home_widget/home_widget.dart';
import 'package:flutter/services.dart';
class UserProvider with ChangeNotifier {
final Map<String, User> _users = {};
String? _selectedUserId;
User? get user => _users[_selectedUserId];
// _user properties
String? get instituteCode => user?.instituteCode;
String? get id => user?.id;
String? get name => user?.name;
String? get username => user?.username;
String? get password => user?.password;
Role? get role => user?.role;
Student? get student => user?.student;
String? get nickname => user?.nickname;
String get picture => user?.picture ?? "";
String? get displayName => user?.displayName;
final SettingsProvider _settings;
UserProvider({required SettingsProvider settings}) : _settings = settings;
void setUser(String userId) async {
_selectedUserId = userId;
await _settings.update(lastAccountId: userId);
if (Platform.isAndroid) updateWidget();
notifyListeners();
}
Future<bool?> updateWidget() async {
try {
return HomeWidget.updateWidget(name: 'widget_timetable.WidgetTimetable');
} on PlatformException catch (exception) {
if (kDebugMode) {
print('Error Updating Widget After setUser. $exception');
}
}
return false;
}
void addUser(User user) {
_users[user.id] = user;
if (kDebugMode) {
print("DEBUG: Added User: ${user.id}");
}
}
void removeUser(String userId) async {
_users.removeWhere((key, value) => key == userId);
if (_users.isNotEmpty) {
setUser(_users.keys.first);
} else {
await _settings.update(lastAccountId: "");
}
if (Platform.isAndroid) updateWidget();
notifyListeners();
}
User getUser(String userId) {
return _users[userId]!;
}
List<User> getUsers() {
return _users.values.toList();
}
void refresh() {
notifyListeners();
}
}
import 'dart:io';
import 'package:filcnaplo/models/settings.dart';
import 'package:filcnaplo/models/user.dart';
import 'package:filcnaplo_kreta_api/models/student.dart';
import 'package:flutter/foundation.dart';
import 'package:home_widget/home_widget.dart';
import 'package:flutter/services.dart';
class UserProvider with ChangeNotifier {
final Map<String, User> _users = {};
String? _selectedUserId;
User? get user => _users[_selectedUserId];
// _user properties
String? get instituteCode => user?.instituteCode;
String? get id => user?.id;
String? get name => user?.name;
String? get username => user?.username;
String? get password => user?.password;
Role? get role => user?.role;
Student? get student => user?.student;
String? get nickname => user?.nickname;
String get picture => user?.picture ?? "";
String? get displayName => user?.displayName;
final SettingsProvider _settings;
UserProvider({required SettingsProvider settings}) : _settings = settings;
void setUser(String userId) async {
_selectedUserId = userId;
await _settings.update(lastAccountId: userId);
if (Platform.isAndroid) updateWidget();
notifyListeners();
}
Future<bool?> updateWidget() async {
try {
return HomeWidget.updateWidget(name: 'widget_timetable.WidgetTimetable');
} on PlatformException catch (exception) {
if (kDebugMode) {
print('Error Updating Widget After setUser. $exception');
}
}
return false;
}
void addUser(User user) {
_users[user.id] = user;
if (kDebugMode) {
print("DEBUG: Added User: ${user.id}");
}
}
void removeUser(String userId) async {
_users.removeWhere((key, value) => key == userId);
if (_users.isNotEmpty) {
setUser(_users.keys.first);
} else {
await _settings.update(lastAccountId: "");
}
if (Platform.isAndroid) updateWidget();
notifyListeners();
}
User getUser(String userId) {
return _users[userId]!;
}
List<User> getUsers() {
return _users.values.toList();
}
void refresh() {
notifyListeners();
}
}