forked from firka/student-legacy
Merge branch 'dev' into master
This commit is contained in:
commit
f4d840462e
@ -54,6 +54,9 @@ class FilcAPI {
|
||||
static const payment = "$baseUrl/v4/payment";
|
||||
static const stripeSheet = "$payment/stripe-sheet";
|
||||
|
||||
// Cloud Sync
|
||||
static const cloudSyncApi = "$baseUrl/v4/me/cloud-sync";
|
||||
|
||||
static Future<bool> checkConnectivity() async =>
|
||||
(await Connectivity().checkConnectivity())[0] != ConnectivityResult.none;
|
||||
|
||||
@ -390,6 +393,32 @@ class FilcAPI {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// cloud sync
|
||||
static Future<Map?> cloudSync(Map<String, String> data, String token) async {
|
||||
try {
|
||||
var client = http.Client();
|
||||
|
||||
http.Response res = await client.post(
|
||||
Uri.parse(cloudSyncApi),
|
||||
body: data,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization': 'Bearer $token',
|
||||
},
|
||||
);
|
||||
|
||||
if (res.statusCode != 200) {
|
||||
throw "HTTP ${res.statusCode}: ${res.body}";
|
||||
}
|
||||
|
||||
return jsonDecode(res.body);
|
||||
} on Exception catch (error, stacktrace) {
|
||||
log("ERROR: FilcAPI.cloudSync: $error $stacktrace");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class ErrorReport {
|
||||
|
@ -56,6 +56,9 @@ const settingsDB = DatabaseStruct("settings", {
|
||||
"uwu_mode": int,
|
||||
"new_popups": int,
|
||||
"unseen_new_features": String,
|
||||
"cloud_sync_enabled": int,
|
||||
"cloud_sync_token": String,
|
||||
"local_updated_at": String,
|
||||
// quick settings
|
||||
"q_timetable_lesson_num": int, "q_timetable_sub_tiles": int,
|
||||
"q_subjects_sub_tiles": int,
|
||||
|
22
refilc/lib/models/cloud_sync_data.dart
Normal file
22
refilc/lib/models/cloud_sync_data.dart
Normal file
@ -0,0 +1,22 @@
|
||||
class CloudSyncData {
|
||||
Map settings;
|
||||
List<String> deviceIds;
|
||||
String reFilcPlusId;
|
||||
Map json;
|
||||
|
||||
CloudSyncData({
|
||||
this.settings = const {},
|
||||
this.deviceIds = const [],
|
||||
this.reFilcPlusId = "",
|
||||
required this.json,
|
||||
});
|
||||
|
||||
factory CloudSyncData.fromJson(Map json) {
|
||||
return CloudSyncData(
|
||||
settings: json['settings'] ?? {},
|
||||
deviceIds: List<String>.from(json['device_ids'] ?? []),
|
||||
reFilcPlusId: json['refilc_plus_id'] ?? "",
|
||||
json: json,
|
||||
);
|
||||
}
|
||||
}
|
@ -109,6 +109,9 @@ class SettingsProvider extends ChangeNotifier {
|
||||
bool _uwuMode;
|
||||
bool _newPopups;
|
||||
List<String> _unseenNewFeatures;
|
||||
bool _cloudSyncEnabled;
|
||||
String _cloudSyncToken;
|
||||
DateTime _updatedAt;
|
||||
// quick settings
|
||||
bool _qTimetableLessonNum;
|
||||
bool _qTimetableSubTiles;
|
||||
@ -184,6 +187,9 @@ class SettingsProvider extends ChangeNotifier {
|
||||
required bool uwuMode,
|
||||
required bool newPopups,
|
||||
required List<String> unseenNewFeatures,
|
||||
required bool cloudSyncEnabled,
|
||||
required String cloudSyncToken,
|
||||
required DateTime updatedAt,
|
||||
required bool qTimetableLessonNum,
|
||||
required bool qTimetableSubTiles,
|
||||
required bool qSubjectsSubTiles,
|
||||
@ -256,6 +262,9 @@ class SettingsProvider extends ChangeNotifier {
|
||||
_uwuMode = uwuMode,
|
||||
_newPopups = newPopups,
|
||||
_unseenNewFeatures = unseenNewFeatures,
|
||||
_cloudSyncEnabled = cloudSyncEnabled,
|
||||
_cloudSyncToken = cloudSyncToken,
|
||||
_updatedAt = updatedAt,
|
||||
_qTimetableLessonNum = qTimetableLessonNum,
|
||||
_qTimetableSubTiles = qTimetableSubTiles,
|
||||
_qSubjectsSubTiles = qSubjectsSubTiles;
|
||||
@ -347,6 +356,9 @@ class SettingsProvider extends ChangeNotifier {
|
||||
uwuMode: map['uwu_mode'] == 1,
|
||||
newPopups: map['new_popups'] == 1,
|
||||
unseenNewFeatures: jsonDecode(map["unseen_new_features"]).cast<String>(),
|
||||
cloudSyncEnabled: map['cloud_sync_enabled'] == 1,
|
||||
cloudSyncToken: map['cloud_sync_token'],
|
||||
updatedAt: DateTime.tryParse(map['local_updated_at']) ?? DateTime.now(),
|
||||
qTimetableLessonNum: map['q_timetable_lesson_num'] == 1,
|
||||
qTimetableSubTiles: map['q_timetable_sub_tiles'] == 1,
|
||||
qSubjectsSubTiles: map['q_subjects_sub_tiles'] == 1,
|
||||
@ -426,6 +438,9 @@ class SettingsProvider extends ChangeNotifier {
|
||||
"uwu_mode": _uwuMode ? 1 : 0,
|
||||
"new_popups": _newPopups ? 1 : 0,
|
||||
"unseen_new_features": jsonEncode(_unseenNewFeatures),
|
||||
"cloud_sync_enabled": _cloudSyncEnabled ? 1 : 0,
|
||||
"cloud_sync_token": _cloudSyncToken,
|
||||
"local_updated_at": _updatedAt.toIso8601String(),
|
||||
"q_timetable_lesson_num": _qTimetableLessonNum ? 1 : 0,
|
||||
"q_timetable_sub_tiles": _qTimetableSubTiles ? 1 : 0,
|
||||
"q_subjects_sub_tiles": _qSubjectsSubTiles ? 1 : 0,
|
||||
@ -509,6 +524,9 @@ class SettingsProvider extends ChangeNotifier {
|
||||
uwuMode: false,
|
||||
newPopups: true,
|
||||
unseenNewFeatures: ['grade_exporting'],
|
||||
cloudSyncEnabled: false,
|
||||
cloudSyncToken: '',
|
||||
updatedAt: DateTime.now(),
|
||||
qTimetableLessonNum: true,
|
||||
qTimetableSubTiles: true,
|
||||
qSubjectsSubTiles: true,
|
||||
@ -583,6 +601,9 @@ class SettingsProvider extends ChangeNotifier {
|
||||
bool get uwuMode => _uwuMode;
|
||||
bool get newPopups => _newPopups;
|
||||
List<String> get unseenNewFeatures => _unseenNewFeatures;
|
||||
bool get cloudSyncEnabled => _cloudSyncEnabled;
|
||||
String get cloudSyncToken => _cloudSyncToken;
|
||||
DateTime get updatedAt => _updatedAt;
|
||||
bool get qTimetableLessonNum => _qTimetableLessonNum;
|
||||
bool get qTimetableSubTiles => _qTimetableSubTiles;
|
||||
bool get qSubjectsSubTiles => _qSubjectsSubTiles;
|
||||
@ -597,6 +618,7 @@ class SettingsProvider extends ChangeNotifier {
|
||||
List<Color>? gradeColors,
|
||||
bool? newsEnabled,
|
||||
String? seenNewsId,
|
||||
String? seenNews, // only for restoring from map
|
||||
bool? notificationsEnabled,
|
||||
bool? notificationsGradesEnabled,
|
||||
bool? notificationsAbsencesEnabled,
|
||||
@ -653,6 +675,8 @@ class SettingsProvider extends ChangeNotifier {
|
||||
bool? uwuMode,
|
||||
bool? newPopups,
|
||||
List<String>? unseenNewFeatures,
|
||||
bool? cloudSyncEnabled,
|
||||
String? cloudSyncToken,
|
||||
bool? qTimetableLessonNum,
|
||||
bool? qTimetableSubTiles,
|
||||
bool? qSubjectsSubTiles,
|
||||
@ -675,6 +699,7 @@ class SettingsProvider extends ChangeNotifier {
|
||||
tempList.add(seenNewsId);
|
||||
_seenNews = tempList.join(',');
|
||||
}
|
||||
if (seenNews != null && seenNews != _seenNews) _seenNews = seenNews;
|
||||
if (notificationsEnabled != null &&
|
||||
notificationsEnabled != _notificationsEnabled) {
|
||||
_notificationsEnabled = notificationsEnabled;
|
||||
@ -850,6 +875,12 @@ class SettingsProvider extends ChangeNotifier {
|
||||
if (unseenNewFeatures != null && unseenNewFeatures != _unseenNewFeatures) {
|
||||
_unseenNewFeatures = unseenNewFeatures;
|
||||
}
|
||||
if (cloudSyncEnabled != null && cloudSyncEnabled != _cloudSyncEnabled) {
|
||||
_cloudSyncEnabled = cloudSyncEnabled;
|
||||
}
|
||||
if (cloudSyncToken != null && cloudSyncToken != _cloudSyncToken) {
|
||||
_cloudSyncToken = cloudSyncToken;
|
||||
}
|
||||
if (qTimetableLessonNum != null &&
|
||||
qTimetableLessonNum != _qTimetableLessonNum) {
|
||||
_qTimetableLessonNum = qTimetableLessonNum;
|
||||
@ -861,11 +892,115 @@ class SettingsProvider extends ChangeNotifier {
|
||||
if (qSubjectsSubTiles != null && qSubjectsSubTiles != _qSubjectsSubTiles) {
|
||||
_qSubjectsSubTiles = qSubjectsSubTiles;
|
||||
}
|
||||
// change updated at time
|
||||
_updatedAt = DateTime.now();
|
||||
// store or not
|
||||
if (store) await _database?.store.storeSettings(this);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> updateFromMap({
|
||||
required Map<dynamic, dynamic> map,
|
||||
bool store = true,
|
||||
}) async {
|
||||
print(map);
|
||||
|
||||
await update(
|
||||
store: store,
|
||||
language: map["language"],
|
||||
startPage: Pages.values[map["start_page"] ?? _startPage.index],
|
||||
rounding: map["rounding"],
|
||||
theme: ThemeMode.values[map["theme"] ?? _theme.index],
|
||||
accentColor:
|
||||
AccentColor.values[map["accent_color"] ?? _accentColor.index],
|
||||
gradeColors: [
|
||||
Color(map["grade_color1"] ?? _gradeColors[0].value),
|
||||
Color(map["grade_color2"] ?? _gradeColors[1].value),
|
||||
Color(map["grade_color3"] ?? _gradeColors[2].value),
|
||||
Color(map["grade_color4"] ?? _gradeColors[3].value),
|
||||
Color(map["grade_color5"] ?? _gradeColors[4].value),
|
||||
],
|
||||
newsEnabled: map["news"] == 1,
|
||||
seenNews: map["seen_news"],
|
||||
notificationsEnabled: map["notifications"] == 1,
|
||||
notificationsGradesEnabled: map["notifications_grades"] == 1,
|
||||
notificationsAbsencesEnabled: map["notifications_absences"] == 1,
|
||||
notificationsMessagesEnabled: map["notifications_messages"] == 1,
|
||||
notificationsLessonsEnabled: map["notifications_lessons"] == 1,
|
||||
notificationsBitfield: map["notifications_bitfield"],
|
||||
notificationPollInterval: map["notification_poll_interval"],
|
||||
developerMode: map["developer_mode"] == 1,
|
||||
vibrate:
|
||||
VibrationStrength.values[map["vibration_strength"] ?? _vibrate.index],
|
||||
abWeeks: map["ab_weeks"] == 1,
|
||||
swapABweeks: map["swap_ab_weeks"] == 1,
|
||||
updateChannel:
|
||||
UpdateChannel.values[map["update_channel"] ?? _updateChannel.index],
|
||||
config: Config.fromJson(jsonDecode(map["config"] ?? "{}")),
|
||||
xFilcId: map["x_filc_id"],
|
||||
analyticsEnabled: map["analytics_enabled"] == 1,
|
||||
graphClassAvg: map["graph_class_avg"] == 1,
|
||||
goodStudent: false,
|
||||
presentationMode: map["presentation_mode"] == 1,
|
||||
bellDelayEnabled: map["bell_delay_enabled"] == 1,
|
||||
bellDelay: map["bell_delay"],
|
||||
gradeOpeningFun: map["grade_opening_fun"] == 1,
|
||||
iconPack: Map.fromEntries(
|
||||
IconPack.values.map((e) => MapEntry(e.name, e)))[map["icon_pack"]]!,
|
||||
customAccentColor:
|
||||
Color(map["custom_accent_color"] ?? _customAccentColor.value),
|
||||
customBackgroundColor:
|
||||
Color(map["custom_background_color"] ?? _customBackgroundColor.value),
|
||||
customHighlightColor:
|
||||
Color(map["custom_highlight_color"] ?? _customHighlightColor.value),
|
||||
customIconColor:
|
||||
Color(map["custom_icon_color"] ?? _customIconColor.value),
|
||||
customTextColor:
|
||||
Color(map["custom_text_color"] ?? _customTextColor.value),
|
||||
shadowEffect: map["shadow_effect"] == 1,
|
||||
premiumScopes:
|
||||
jsonDecode(map["premium_scopes"] ?? _premiumScopes).cast<String>(),
|
||||
premiumAccessToken: map["premium_token"],
|
||||
premiumLogin: map["premium_login"],
|
||||
lastAccountId: map["last_account_id"],
|
||||
renamedSubjectsEnabled: map["renamed_subjects_enabled"] == 1,
|
||||
renamedSubjectsItalics: map["renamed_subjects_italics"] == 1,
|
||||
renamedTeachersEnabled: map["renamed_teachers_enabled"] == 1,
|
||||
renamedTeachersItalics: map["renamed_teachers_italics"] == 1,
|
||||
liveActivityColor:
|
||||
Color(map["live_activity_color"] ?? _liveActivityColor),
|
||||
welcomeMessage: map["welcome_message"],
|
||||
appIcon: map["app_icon"],
|
||||
currentThemeId: map['current_theme_id'],
|
||||
currentThemeDisplayName: map['current_theme_display_name'],
|
||||
currentThemeCreator: map['current_theme_creator'],
|
||||
showBreaks: map['show_breaks'] == 1,
|
||||
// pinSetGeneral: map['general_s_pin'],
|
||||
// pinSetPersonalize: map['personalize_s_pin'],
|
||||
// pinSetNotify: map['notify_s_pin'],
|
||||
// pinSetExtras: map['extras_s_pin'],
|
||||
fontFamily: map['font_family'],
|
||||
titleOnlyFont: map['title_only_font'] == 1,
|
||||
plusSessionId: map['plus_session_id'],
|
||||
calSyncRoomLocation: map['cal_sync_room_location'],
|
||||
calSyncShowExams: map['cal_sync_show_exams'] == 1,
|
||||
calSyncShowTeacher: map['cal_sync_show_teacher'] == 1,
|
||||
calSyncRenamed: map['cal_sync_renamed'] == 1,
|
||||
calendarId: map['calendar_id'],
|
||||
navShadow: map['nav_shadow'] == 1,
|
||||
newColors: map['new_colors'] == 1,
|
||||
uwuMode: map['uwu_mode'] == 1,
|
||||
newPopups: map['new_popups'] == 1,
|
||||
unseenNewFeatures:
|
||||
jsonDecode(map["unseen_new_features"] ?? "[]").cast<String>(),
|
||||
cloudSyncEnabled: map['cloud_sync_enabled'] == 1,
|
||||
cloudSyncToken: map['cloud_sync_token'],
|
||||
qTimetableLessonNum: map['q_timetable_lesson_num'] == 1,
|
||||
qTimetableSubTiles: map['q_timetable_sub_tiles'] == 1,
|
||||
qSubjectsSubTiles: map['q_subjects_sub_tiles'] == 1,
|
||||
);
|
||||
}
|
||||
|
||||
void exportJson() {
|
||||
String sets = json.encode(toMap());
|
||||
Clipboard.setData(ClipboardData(text: sets));
|
||||
|
@ -21,6 +21,10 @@ class User {
|
||||
String accessToken;
|
||||
DateTime accessTokenExpire;
|
||||
String refreshToken;
|
||||
// cloud sync
|
||||
// String qwidAccessToken;
|
||||
// DateTime? qwidAccessTokenExpire;
|
||||
// String qwidRefreshToken;
|
||||
|
||||
String get displayName => nickname != '' ? nickname : name;
|
||||
bool get hasStreak => gradeStreak > 0;
|
||||
@ -39,6 +43,9 @@ class User {
|
||||
required this.accessToken,
|
||||
required this.accessTokenExpire,
|
||||
required this.refreshToken,
|
||||
// this.qwidAccessToken = "",
|
||||
// this.qwidAccessTokenExpire,
|
||||
// this.qwidRefreshToken = "",
|
||||
}) {
|
||||
if (id != null) {
|
||||
this.id = id;
|
||||
@ -74,6 +81,11 @@ class User {
|
||||
? map["access_token_expire"]
|
||||
: DateTime.now().toIso8601String()),
|
||||
refreshToken: map["refresh_token"] ?? "",
|
||||
// qwidAccessToken: map["qwid_access_token"] ?? "",
|
||||
// qwidAccessTokenExpire: map["qwid_access_token_expire"] != ""
|
||||
// ? DateTime.parse(map["qwid_access_token_expire"])
|
||||
// : null,
|
||||
// qwidRefreshToken: map["qwid_refresh_token"] ?? "",
|
||||
);
|
||||
}
|
||||
|
||||
@ -92,6 +104,11 @@ class User {
|
||||
"access_token": accessToken,
|
||||
"access_token_expire": accessTokenExpire.toIso8601String(),
|
||||
"refresh_token": refreshToken,
|
||||
// "qwid_access_token": qwidAccessToken,
|
||||
// "qwid_access_token_expire": qwidAccessTokenExpire != null
|
||||
// ? qwidAccessTokenExpire!.toIso8601String()
|
||||
// : "",
|
||||
// "qwid_refresh_token": qwidRefreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ dependencies:
|
||||
crypto: ^3.0.2
|
||||
elegant_notification: ^2.2.0
|
||||
flutter_feather_icons: ^2.0.0+1
|
||||
live_activities: ^1.7.4
|
||||
# live_activities: ^1.7.4
|
||||
animated_flip_counter: ^0.3.4
|
||||
lottie: ^3.1.0
|
||||
rive: ^0.12.4
|
||||
@ -62,10 +62,10 @@ dependencies:
|
||||
flutter_expandable_fab: ^2.0.0
|
||||
uni_links: ^0.5.1
|
||||
url_launcher: ^6.1.6
|
||||
workmanager:
|
||||
git:
|
||||
url: https://github.com/refilc/flutter_workmanager.git
|
||||
ref: v0.5.1
|
||||
# workmanager:
|
||||
# git:
|
||||
# url: https://github.com/refilc/flutter_workmanager.git
|
||||
# ref: v0.5.1
|
||||
flutter_svg: ^2.0.10+1
|
||||
image_picker: ^1.0.7
|
||||
animations: ^2.0.1
|
||||
|
@ -153,7 +153,7 @@ class GradeProvider with ChangeNotifier {
|
||||
for (Grade grade in grs) {
|
||||
if (grade.value.value == 5) {
|
||||
gradeStreak++;
|
||||
} else {
|
||||
} else if (grade.value.value !=0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ class _FSTimetableState extends State<FSTimetable> {
|
||||
body: ListView.builder(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6.0, vertical: 24.0),
|
||||
itemCount: maxLessonCount + 1,
|
||||
itemCount: maxLessonCount + 2,
|
||||
itemBuilder: (context, index) {
|
||||
List<Widget> columns = [];
|
||||
for (int dayIndex = -1; dayIndex < days.length; dayIndex++) {
|
||||
@ -119,10 +119,8 @@ class _FSTimetableState extends State<FSTimetable> {
|
||||
|
||||
if (lessons.isEmpty) continue;
|
||||
|
||||
int lsnIndx = int.tryParse(lessons.first.lessonIndex) ?? 1;
|
||||
final dayOffset = lsnIndx == 0 ? 1 : lsnIndx;
|
||||
|
||||
if (index == 0 && dayIndex >= 0) {
|
||||
// if (index == 0 || dayIndex >=0) {
|
||||
columns.add(
|
||||
SizedBox(
|
||||
width: colw,
|
||||
@ -141,16 +139,10 @@ class _FSTimetableState extends State<FSTimetable> {
|
||||
continue;
|
||||
}
|
||||
|
||||
final lessonIndex = index - dayOffset;
|
||||
|
||||
Lesson? lsn = lessons.firstWhereOrNull(
|
||||
(e) => e.lessonIndex == (index - 1).toString());
|
||||
|
||||
if (lessonIndex < 0 ||
|
||||
lessonIndex > lessons.length ||
|
||||
(index == 1 && lsnIndx != 0) ||
|
||||
(lsnIndx != 0 && lessonIndex - 1 == -1) ||
|
||||
lsn == null) {
|
||||
if (lsn == null) {
|
||||
columns.add(SizedBox(width: colw));
|
||||
continue;
|
||||
}
|
||||
@ -259,4 +251,4 @@ class _FSTimetableState extends State<FSTimetable> {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -98,11 +98,48 @@ class PlusPlanCard extends StatelessWidget {
|
||||
onTap: () {
|
||||
// pop dialog
|
||||
Navigator.of(context).pop();
|
||||
// start payment process
|
||||
Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (context) {
|
||||
return PremiumActivationView(product: id);
|
||||
}));
|
||||
// show payment option selector
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.0)),
|
||||
title: Text('payment_method'.i18n),
|
||||
content: Text('select_payment_method'.i18n),
|
||||
actions: [
|
||||
ActionButton(
|
||||
label: "stripe".i18n,
|
||||
onTap: () {
|
||||
// pop dialog
|
||||
Navigator.of(context).pop();
|
||||
// start payment process
|
||||
Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (context) {
|
||||
return PremiumActivationView(
|
||||
product: id,
|
||||
paymentProvider: "stripe",
|
||||
);
|
||||
}));
|
||||
},
|
||||
),
|
||||
ActionButton(
|
||||
label: "paypal".i18n,
|
||||
onTap: () {
|
||||
// pop dialog
|
||||
Navigator.of(context).pop();
|
||||
// start payment process
|
||||
Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (context) {
|
||||
return PremiumActivationView(
|
||||
product: id,
|
||||
paymentProvider: "paypal",
|
||||
);
|
||||
}));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
|
@ -47,11 +47,16 @@ extension SettingsLocalization on String {
|
||||
"rfp_16": "Private leaks and informations about upcoming features",
|
||||
"rfp_17": "Grade exporting",
|
||||
"rfp_18": "Viewing exported grades",
|
||||
// docs popup
|
||||
// docs and payment method popup
|
||||
"docs": "Documents",
|
||||
"docs_acceptance":
|
||||
"By pressing the \"Next\" button, you accept reFilc's Terms and Conditions for subscriptions (available at the following link: filc.one/pay-terms) and our Privacy Policy (available at the following link: filc.one/pay-privacy).",
|
||||
"next": "Next",
|
||||
"payment_method": "Payment Method",
|
||||
"select_payment_method":
|
||||
"Please select a preferred payment method! Credit card payments are handled by Stripe, which also supports Apple Pay, Google Pay and Revolut Pay.",
|
||||
"stripe": "Credit Card",
|
||||
"paypal": "PayPal",
|
||||
// other
|
||||
"and": " and ",
|
||||
"every": "Every ",
|
||||
@ -106,11 +111,16 @@ extension SettingsLocalization on String {
|
||||
"rfp_16": "Privát betekintések és információk közelgő újításokról",
|
||||
"rfp_17": "Jegy exportálás",
|
||||
"rfp_18": "Exportált jegyek megtekintése",
|
||||
// docs popup
|
||||
// docs and payment method popup
|
||||
"docs": "Dokumentumok",
|
||||
"docs_acceptance":
|
||||
"A \"Tovább\" gombra kattintva elfogadod a reFilc előfizetésekkel kapcsolatos Általános Szerződési Feltételeit (elérhető az alábbi link-en: filc.one/pay-terms), valamint Adatkezelési Tájékoztatónkat (elérhető az alábbi link-en: filc.one/pay-privacy).",
|
||||
"next": "Tovább",
|
||||
"payment_method": "Fizetési mód",
|
||||
"select_payment_method":
|
||||
"Kérlek válassz egy fizetési módot! A bankkártyás fizetést a Stripe biztosítja, mely támogat Apple Pay-t, Google Pay-t és Revolut Pay-t is.",
|
||||
"stripe": "Bankkártya",
|
||||
"paypal": "PayPal",
|
||||
// other
|
||||
"and": " és ",
|
||||
"every": "Minden ",
|
||||
@ -167,11 +177,16 @@ extension SettingsLocalization on String {
|
||||
"rfp_16": "Private Leaks und Informationen über kommende Funktionen",
|
||||
"rfp_17": "Notenexport",
|
||||
"rfp_18": "Anzeigen exportierter Noten",
|
||||
// docs popup
|
||||
// docs and payment method popup
|
||||
"docs": "Dokumente",
|
||||
"docs_acceptance":
|
||||
"Durch Drücken der Schaltfläche \"Weiter\" akzeptieren Sie die Allgemeinen Geschäftsbedingungen von reFilc für Abonnements (verfügbar unter folgendem Link: filc.one/pay-terms) und unsere Datenschutzrichtlinie (verfügbar unter folgendem Link: filc.one/pay-privacy).",
|
||||
"next": "Weiter",
|
||||
"payment_method": "Zahlungsmethode",
|
||||
"select_payment_method":
|
||||
"Bitte wählen Sie eine bevorzugte Zahlungsmethode aus! Kreditkartenzahlungen werden von Stripe abgewickelt, der auch Apple Pay, Google Pay und Revolut Pay unterstützt.",
|
||||
"stripe": "Kreditkarte",
|
||||
"paypal": "PayPal",
|
||||
// other
|
||||
"and": " und ",
|
||||
"every": "Jeder ",
|
||||
|
170
refilc_mobile_ui/lib/screens/login/qwid_login.dart
Normal file
170
refilc_mobile_ui/lib/screens/login/qwid_login.dart
Normal file
@ -0,0 +1,170 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
class QwIDLoginWidget extends StatefulWidget {
|
||||
const QwIDLoginWidget({super.key, required this.onLogin});
|
||||
|
||||
// final String selectedSchool;
|
||||
final void Function(String code) onLogin;
|
||||
|
||||
@override
|
||||
State<QwIDLoginWidget> createState() => _QwIDLoginWidgetState();
|
||||
}
|
||||
|
||||
class _QwIDLoginWidgetState extends State<QwIDLoginWidget>
|
||||
with TickerProviderStateMixin {
|
||||
late final WebViewController controller;
|
||||
late AnimationController _animationController;
|
||||
var loadingPercentage = 0;
|
||||
var currentUrl = '';
|
||||
bool _hasFadedIn = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_animationController = AnimationController(
|
||||
vsync: this, // Use the TickerProviderStateMixin
|
||||
duration: const Duration(milliseconds: 350),
|
||||
);
|
||||
|
||||
controller = WebViewController()
|
||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||
..setNavigationDelegate(NavigationDelegate(
|
||||
onNavigationRequest: (n) async {
|
||||
if (n.url.startsWith('refilc://oauth2-callback/qwid')) {
|
||||
setState(() {
|
||||
loadingPercentage = 0;
|
||||
currentUrl = n.url;
|
||||
});
|
||||
|
||||
// final String instituteCode = widget.selectedSchool;
|
||||
// if (!n.url.startsWith(
|
||||
// 'https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect?code=')) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
String longLivedToken = n.url
|
||||
.replaceAll('refilc://oauth2-callback/qwid?access_token=', '');
|
||||
|
||||
widget.onLogin(longLivedToken);
|
||||
// Future.delayed(const Duration(milliseconds: 500), () {
|
||||
// Navigator.of(context).pop();
|
||||
// });
|
||||
// Navigator.of(context).pop();
|
||||
|
||||
return NavigationDecision.prevent;
|
||||
} else {
|
||||
return NavigationDecision.navigate;
|
||||
}
|
||||
},
|
||||
onPageStarted: (url) async {
|
||||
// setState(() {
|
||||
// loadingPercentage = 0;
|
||||
// currentUrl = url;
|
||||
// });
|
||||
|
||||
// // final String instituteCode = widget.selectedSchool;
|
||||
// if (!url.startsWith(
|
||||
// 'https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect?code=')) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// List<String> requiredThings = url
|
||||
// .replaceAll(
|
||||
// 'https://mobil.e-kreta.hu/ellenorzo-student/prod/oauthredirect?code=',
|
||||
// '')
|
||||
// .replaceAll(
|
||||
// '&scope=openid%20email%20offline_access%20kreta-ellenorzo-webapi.public%20kreta-eugyintezes-webapi.public%20kreta-fileservice-webapi.public%20kreta-mobile-global-webapi.public%20kreta-dkt-webapi.public%20kreta-ier-webapi.public&state=refilc_student_mobile&session_state=',
|
||||
// ':')
|
||||
// .split(':');
|
||||
|
||||
// String code = requiredThings[0];
|
||||
// // String sessionState = requiredThings[1];
|
||||
|
||||
// widget.onLogin(code);
|
||||
// // Future.delayed(const Duration(milliseconds: 500), () {
|
||||
// // Navigator.of(context).pop();
|
||||
// // });
|
||||
// // Navigator.of(context).pop();
|
||||
},
|
||||
onProgress: (progress) {
|
||||
setState(() {
|
||||
loadingPercentage = progress;
|
||||
});
|
||||
},
|
||||
onPageFinished: (url) {
|
||||
setState(() {
|
||||
loadingPercentage = 100;
|
||||
});
|
||||
},
|
||||
))
|
||||
..loadRequest(
|
||||
Uri.parse(
|
||||
'https://qwid.qwit.dev/oauth2/authorize?client_id=99aa103a-0bd7-43e0-8421-3bb0b2f6adb1&scope=*&redirect_uri=https://api.refilc.hu/v4/oauth2/callback/app/qwid&response_type=code'), // &institute_code=${widget.selectedSchool}
|
||||
);
|
||||
}
|
||||
|
||||
// Future<void> loadLoginUrl() async {
|
||||
// String nonceStr = await Provider.of<KretaClient>(context, listen: false)
|
||||
// .getAPI(KretaAPI.nonce, json: false);
|
||||
|
||||
// Nonce nonce = getNonce(nonceStr, );
|
||||
// }
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// Step 3: Dispose of the animation controller
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Trigger the fade-in animation only once when loading reaches 100%
|
||||
if (loadingPercentage == 100 && !_hasFadedIn) {
|
||||
_animationController.forward(); // Play the animation
|
||||
_hasFadedIn =
|
||||
true; // Set the flag to true, so the animation is not replayed
|
||||
}
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
// Webview that will be displayed only when the loading is 100%
|
||||
if (loadingPercentage == 100)
|
||||
FadeTransition(
|
||||
opacity: Tween<double>(begin: 0, end: 1).animate(
|
||||
CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: Curves.easeIn,
|
||||
),
|
||||
),
|
||||
child: WebViewWidget(
|
||||
controller: controller,
|
||||
),
|
||||
),
|
||||
|
||||
// Show the CircularProgressIndicator while loading is not 100%
|
||||
if (loadingPercentage < 100)
|
||||
Center(
|
||||
child: TweenAnimationBuilder(
|
||||
tween: Tween<double>(begin: 0, end: loadingPercentage / 100.0),
|
||||
duration: const Duration(milliseconds: 300),
|
||||
builder: (context, double value, child) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
value: value, // Smoothly animates the progress
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -68,6 +68,7 @@ import 'package:refilc_mobile_ui/screens/settings/user/profile_pic.dart';
|
||||
// import 'package:refilc_plus/ui/mobile/settings/welcome_message.dart';
|
||||
// import 'package:refilc_mobile_ui/screens/error_screen.dart';
|
||||
import 'package:refilc_mobile_ui/screens/error_report_screen.dart';
|
||||
import 'submenu/cloud_sync_screen.dart';
|
||||
import 'submenu/general_screen.dart';
|
||||
import 'package:refilc_plus/ui/mobile/plus/settings_inline.dart';
|
||||
|
||||
@ -428,6 +429,13 @@ class SettingsScreenState extends State<SettingsScreen>
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0), bottom: Radius.circular(4.0)),
|
||||
),
|
||||
// cloud-sync
|
||||
const MenuCloudSyncSettings(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(4.0),
|
||||
bottom: Radius.circular(4.0),
|
||||
),
|
||||
),
|
||||
// open dcs (digital collaboration space)
|
||||
PanelButton(
|
||||
onPressed: () => _openDKT(user.user!),
|
||||
|
@ -0,0 +1,252 @@
|
||||
// import 'package:refilc/models/settings.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:refilc/api/client.dart';
|
||||
import 'package:refilc/api/providers/database_provider.dart';
|
||||
import 'package:refilc/api/providers/user_provider.dart';
|
||||
import 'package:refilc/models/settings.dart';
|
||||
import 'package:refilc/theme/colors/colors.dart';
|
||||
import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
|
||||
import 'package:refilc_mobile_ui/common/splitted_panel/splitted_panel.dart';
|
||||
import 'package:refilc_mobile_ui/screens/login/qwid_login.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:refilc/models/cloud_sync_data.dart';
|
||||
// import 'package:provider/provider.dart';
|
||||
import 'submenu_screen.i18n.dart';
|
||||
|
||||
class MenuCloudSyncSettings extends StatelessWidget {
|
||||
const MenuCloudSyncSettings({
|
||||
super.key,
|
||||
this.borderRadius = const BorderRadius.vertical(
|
||||
top: Radius.circular(4.0), bottom: Radius.circular(4.0)),
|
||||
});
|
||||
|
||||
final BorderRadius borderRadius;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PanelButton(
|
||||
onPressed: () => Navigator.of(context, rootNavigator: true).push(
|
||||
CupertinoPageRoute(
|
||||
builder: (context) => const CloudSyncSettingsScreen()),
|
||||
),
|
||||
title: Text("cloud_sync".i18n),
|
||||
leading: Icon(
|
||||
FeatherIcons.uploadCloud,
|
||||
size: 22.0,
|
||||
color: AppColors.of(context).text.withOpacity(0.95),
|
||||
),
|
||||
borderRadius: borderRadius,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CloudSyncSettingsScreen extends StatefulWidget {
|
||||
const CloudSyncSettingsScreen({super.key});
|
||||
|
||||
@override
|
||||
CloudSyncSettingsScreenState createState() => CloudSyncSettingsScreenState();
|
||||
}
|
||||
|
||||
class CloudSyncSettingsScreenState extends State<CloudSyncSettingsScreen> {
|
||||
late SettingsProvider settingsProvider;
|
||||
late UserProvider user;
|
||||
|
||||
String longLivedToken = '';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SettingsProvider settingsProvider = Provider.of<SettingsProvider>(context);
|
||||
// UserProvider user = Provider.of<UserProvider>(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
surfaceTintColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
leading: BackButton(color: AppColors.of(context).text),
|
||||
title: Text(
|
||||
"cloud_sync".i18n,
|
||||
style: TextStyle(color: AppColors.of(context).text),
|
||||
),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
|
||||
child: Column(
|
||||
children: [
|
||||
SplittedPanel(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
cardPadding: const EdgeInsets.all(4.0),
|
||||
isSeparated: true,
|
||||
children: [
|
||||
PanelButton(
|
||||
padding: const EdgeInsets.only(left: 14.0, right: 6.0),
|
||||
onPressed: () async {
|
||||
showModalBottomSheet(
|
||||
backgroundColor: Colors.transparent,
|
||||
context: context,
|
||||
isScrollControlled:
|
||||
true, // This ensures the modal accommodates input fields properly
|
||||
builder: (BuildContext context) {
|
||||
return Container(
|
||||
height: MediaQuery.of(context).size.height * 0.9 +
|
||||
MediaQuery.of(context).viewInsets.bottom,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFDAE4F7),
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(24.0),
|
||||
topLeft: Radius.circular(24.0),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 18),
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFB9C8E5),
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(2.0),
|
||||
topLeft: Radius.circular(2.0),
|
||||
),
|
||||
),
|
||||
width: 40,
|
||||
height: 4,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 14, left: 14, bottom: 24),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.circular(16),
|
||||
),
|
||||
child: QwIDLoginWidget(
|
||||
onLogin: (String token) {
|
||||
setState(() {
|
||||
longLivedToken = token;
|
||||
});
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
).then((value) {
|
||||
// After closing the modal bottom sheet, check if the code is set
|
||||
if (longLivedToken.isNotEmpty) {
|
||||
// Call your API after retrieving the code
|
||||
settingsProvider.update(
|
||||
cloudSyncToken: longLivedToken,
|
||||
store: true,
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('login_successful'.i18n)));
|
||||
}
|
||||
});
|
||||
},
|
||||
trailingDivider: true,
|
||||
title: Text(
|
||||
"qwit_sign_in".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(
|
||||
settingsProvider.gradeOpeningFun ? .95 : .25),
|
||||
),
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
SwitchListTile(
|
||||
value: settingsProvider.cloudSyncEnabled,
|
||||
onChanged: (value) {
|
||||
settingsProvider.update(
|
||||
cloudSyncEnabled: value,
|
||||
store: true,
|
||||
);
|
||||
},
|
||||
title: Text("cloud_sync_enabled".i18n),
|
||||
),
|
||||
PanelButton(
|
||||
padding: const EdgeInsets.only(left: 14.0, right: 6.0),
|
||||
onPressed: () async {
|
||||
if (settingsProvider.cloudSyncToken.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('sign_in_first'.i18n),
|
||||
),
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
FilcAPI.cloudSync(
|
||||
{
|
||||
"settings": jsonEncode(settingsProvider.toMap()),
|
||||
// "device_ids": [
|
||||
// settingsProvider.xFilcId,
|
||||
// ],
|
||||
// "refilc_plus_id": settingsProvider.plusSessionId,
|
||||
},
|
||||
settingsProvider.cloudSyncToken,
|
||||
).then((response) {
|
||||
if (response == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('sync_failed'.i18n),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
CloudSyncData cloudSyncData = CloudSyncData.fromJson(
|
||||
response['data']['cloud_sync_data']);
|
||||
|
||||
settingsProvider.updateFromMap(
|
||||
map: cloudSyncData.settings,
|
||||
store: true,
|
||||
);
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('sync_successful'.i18n),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
trailingDivider: true,
|
||||
title: Text(
|
||||
"sync_now".i18n,
|
||||
style: TextStyle(
|
||||
color: AppColors.of(context).text.withOpacity(
|
||||
settingsProvider.gradeOpeningFun ? .95 : .25),
|
||||
),
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(12.0),
|
||||
bottom: Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -32,6 +32,8 @@ extension SettingsLocalization on String {
|
||||
"understand": "I understand",
|
||||
"theme_share_failed": "An error occurred while sharing the theme.",
|
||||
"theme_share_ratelimit": "You can only share 1 theme per minute.",
|
||||
// cloud sync
|
||||
"cloud_sync": "Cloud Sync",
|
||||
},
|
||||
"hu_hu": {
|
||||
"general": "Általános",
|
||||
@ -62,6 +64,8 @@ extension SettingsLocalization on String {
|
||||
"understand": "Értem",
|
||||
"theme_share_failed": "Hiba történt a téma megosztása közben.",
|
||||
"theme_share_ratelimit": "Csak 1 témát oszthatsz meg percenként.",
|
||||
// cloud sync
|
||||
"cloud_sync": "Felhő szinkronizálás",
|
||||
},
|
||||
"de_de": {
|
||||
"general": "Allgemeine",
|
||||
@ -93,6 +97,8 @@ extension SettingsLocalization on String {
|
||||
"theme_share_failed":
|
||||
"Beim Teilen des Themas ist ein Fehler aufgetreten.",
|
||||
"theme_share_ratelimit": "Sie können nur 1 Thema pro Minute teilen.",
|
||||
// cloud sync
|
||||
"cloud_sync": "Cloud-Synchronisierung",
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -31,7 +31,7 @@ dependencies:
|
||||
animations: ^2.0.11
|
||||
animated_list_plus: ^0.5.0
|
||||
confetti: ^0.7.0
|
||||
live_activities: ^1.9.1+1
|
||||
# live_activities: ^1.9.1+1
|
||||
animated_flip_counter: ^0.3.4
|
||||
lottie: ^3.1.0
|
||||
rive: ^0.12.4
|
||||
@ -51,7 +51,7 @@ dependencies:
|
||||
rounded_expansion_tile:
|
||||
git:
|
||||
url: https://github.com/kimaah/rounded_expansion_tile.git
|
||||
go_router: ^14.2.0
|
||||
# go_router: ^14.2.0
|
||||
flutter_expandable_fab: ^2.0.0
|
||||
intl: ^0.19.0
|
||||
i18n_extension: ^12.0.1
|
||||
@ -67,7 +67,7 @@ dependencies:
|
||||
uuid: ^4.3.3
|
||||
maps_launcher: ^2.2.0
|
||||
google_fonts: ^6.1.0
|
||||
flutter_any_logo: ^1.1.1
|
||||
# flutter_any_logo: ^1.1.1
|
||||
custom_sliding_segmented_control: ^1.8.1
|
||||
get_it: ^7.6.7
|
||||
xml: ^6.5.0
|
||||
|
Loading…
x
Reference in New Issue
Block a user