Compare commits

...

50 Commits

Author SHA1 Message Date
Márton Kiss
f4d840462e Merge branch 'dev' into master 2025-01-03 15:26:35 +01:00
Marton Kiss
b08b127895 yes 2025-01-03 15:22:29 +01:00
Marton Kiss
d41872e6e2 updated sdk requirement 2025-01-03 15:22:01 +01:00
Marton Kiss
0602c2ea3f optimized and updated things 2025-01-03 15:21:28 +01:00
Kima
afcff10862 some progress in cloud sync and paypal support almost done 2024-12-02 23:13:26 +01:00
Kima
9e187fc04c Merge branch 'dev' of github.com:refilc/naplo into dev 2024-11-16 22:21:26 +01:00
Kima
7d5b97fe00 started working on cloud sync (testing) 2024-11-16 22:21:22 +01:00
Márton Kiss
fd3b21b8e6 Merge pull request #142 from balint1414/dev
gradestreak probléma javítás: nem ignorálja a szöveges értékelést
2024-11-16 19:55:53 +01:00
balint1414
071f682f77 gradestreak: javítás (2) 2024-11-16 15:41:41 +01:00
balint1414
8723f75889 gradestreak probléma javítás: nem ignorálja a szöveges értékelést 2024-11-16 12:25:27 +01:00
Kima
c9666f5333 Merge branch 'dev' of github.com:refilc/naplo into dev 2024-11-15 23:55:05 +01:00
Kima
a218b62742 removed unused dependencies 2024-11-15 23:53:39 +01:00
Kima
41b1d899d0 removed unused dependencies 2024-11-15 23:52:40 +01:00
Márton Kiss
ad18efd340 Merge pull request #131 from balint1414/dev
Fektetett órarend hibás kiírás javítása
2024-11-15 21:00:46 +01:00
balint1414
cf0dc50df5 Fektetett órarend: 0. óra és utolsó óra megjelenítése 2024-11-15 20:43:18 +01:00
Márton Kiss
994d3085bb Merge pull request #141 from refilc/dev
dev to master
2024-11-14 21:14:46 +01:00
Kima
6292708ba0 changed build number 2024-11-14 21:14:13 +01:00
Kima
a26ca67892 Merge branch 'dev' of github.com:refilc/naplo into dev 2024-11-14 21:13:11 +01:00
Kima
f2d6b6079e fixed qr scanner size on smaller phones 2024-11-14 21:13:06 +01:00
Márton Kiss
4faee05823 Merge pull request #140 from refilc/master
everything back to dev
2024-11-14 17:14:34 +01:00
Márton Kiss
864701fd1e Merge pull request #139 from refilc/dev
dev to master
2024-11-14 17:13:45 +01:00
Kima
80d50cd82b Merge branch 'dev' of github.com:refilc/naplo into dev 2024-11-14 17:12:41 +01:00
Kima
521f609707 forgot to push lol 2024-11-14 17:12:38 +01:00
Tihanyi Marcell
96ff70d7d5 Merge pull request #138 from refilc/dev
Bitcode removal, Crash fixed caused by Live Activity
2024-11-14 15:10:46 +01:00
Tihanyi Marcell
658bfe38a3 Bitcode removal, Crash fixed caused by Live Activity 2024-11-14 15:06:38 +01:00
Márton Kiss
a74b2cd3d7 Merge pull request #137 from refilc/dev
dev to master
2024-11-13 21:08:13 +01:00
Kima
414755c777 okay that's it bye 2024-11-13 20:51:24 +01:00
Kima
3708b917c4 finished qr scanner (test) 2024-11-13 20:25:13 +01:00
Kima
986b13de68 changed version number 2024-11-13 19:12:09 +01:00
Kima
d391448870 remove prints 2024-11-13 19:11:08 +01:00
Kima
38d9b5f3b2 fixed login finally 2024-11-13 19:10:48 +01:00
Kima
2dafe5ed02 updated packages, did things and maybe finally fixed login issue 2024-11-12 23:27:14 +01:00
Kima
939761695f working error handling for theme sharing 2024-10-10 20:48:21 +02:00
Kima
f1ba5230fc added theme share error handling for ratelimit response 2024-10-10 18:11:41 +02:00
Kima
a50f449f7c added extra fields in news objects 2024-10-07 22:33:52 +02:00
Kima
fe3ed31830 added new analytics option to db 2024-10-06 23:56:56 +02:00
Kima
0ec33f8631 changed subscription document acceptance 2024-10-06 23:53:24 +02:00
Kima
6634010b97 made re-activation easier 2024-10-02 21:17:03 +02:00
Kima
816ddf58a2 changed how analytics work 2024-10-02 21:06:01 +02:00
Kima
d7741ca1c4 tried testing sync bug and fixed ads even more 2024-09-28 17:33:57 +02:00
Kima
a2cbe5d90b changed version number 2024-09-28 17:04:28 +02:00
Kima
7919d0e284 added grade delay to details and other small shit 2024-09-27 23:07:18 +02:00
Kima
92fe3b7dcd fixed yellow lines at profile image grade streak indicator 2024-09-27 22:02:31 +02:00
Kima
63fd37c31f the ads got more acceptable 2024-09-27 21:37:06 +02:00
Kima
aa10f0672e show ads only in even hours 2024-09-27 21:30:32 +02:00
Kima
a3694b59ec doing something with ads 2024-09-27 21:28:07 +02:00
Kima
9ecee0bb01 hide "ads" if user has plus 2024-09-27 20:51:01 +02:00
balint1414
b9e9bef182 Fektetett órarend hibás kiírás javítása 2024-09-03 18:49:55 +02:00
zypherift
51297ddc09 Merge branch 'dev' of github.com:refilc/naplo into dev 2024-08-22 22:46:13 +02:00
zypherift
4d64705e59 fix for padding 2024-08-22 22:46:11 +02:00
45 changed files with 1623 additions and 282 deletions

2
refilc/.gitignore vendored
View File

@@ -5,9 +5,11 @@
*.swp *.swp
.DS_Store .DS_Store
.atom/ .atom/
.build/
.buildlog/ .buildlog/
.history .history
.svn/ .svn/
.swiftpm/
migrate_working_dir/ migrate_working_dir/
# IntelliJ related # IntelliJ related

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -43,4 +43,21 @@ post_install do |installer|
config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET' config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET'
end end
end end
bitcode_strip_path = `xcrun --find bitcode_strip`.chop!
def strip_bitcode_from_framework(bitcode_strip_path, framework_relative_path)
framework_path = File.join(Dir.pwd, framework_relative_path)
command = "#{bitcode_strip_path} #{framework_path} -r -o #{framework_path}"
puts "Stripping bitcode: #{command}"
system(command)
end
framework_paths = [
"Pods/Shake/Sources/Shake.xcframework/ios-arm64/Shake.framework/Shake",
"Pods/Shake/Sources/Shake.xcframework/ios-arm64_x86_64-simulator/Shake.framework/Shake"
]
framework_paths.each do |framework_relative_path|
strip_bitcode_from_framework(bitcode_strip_path, framework_relative_path)
end
end end

View File

@@ -517,8 +517,8 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 257; CURRENT_PROJECT_VERSION = 280;
DEVELOPMENT_TEAM = 4DKAF249F3; DEVELOPMENT_TEAM = 4J97JVC2FG;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = reFilc; INFOPLIST_KEY_CFBundleDisplayName = reFilc;
@@ -527,8 +527,8 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 5.0.0; MARKETING_VERSION = 5.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.refilc2.naplo; PRODUCT_BUNDLE_IDENTIFIER = hu.qwit.refilc;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@@ -549,8 +549,8 @@
CODE_SIGN_ENTITLEMENTS = livecard/livecard.entitlements; CODE_SIGN_ENTITLEMENTS = livecard/livecard.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 257; CURRENT_PROJECT_VERSION = 276;
DEVELOPMENT_TEAM = 4DKAF249F3; DEVELOPMENT_TEAM = 4J97JVC2FG;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = livecard/Info.plist; INFOPLIST_FILE = livecard/Info.plist;
@@ -563,10 +563,10 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 5.0.0; MARKETING_VERSION = 5.0.5;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.refilc2.naplo.livecardpro; PRODUCT_BUNDLE_IDENTIFIER = hu.qwit.refilc.livecard;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
@@ -591,8 +591,8 @@
CODE_SIGN_ENTITLEMENTS = livecard/livecard.entitlements; CODE_SIGN_ENTITLEMENTS = livecard/livecard.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 257; CURRENT_PROJECT_VERSION = 276;
DEVELOPMENT_TEAM = 4DKAF249F3; DEVELOPMENT_TEAM = 4J97JVC2FG;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = livecard/Info.plist; INFOPLIST_FILE = livecard/Info.plist;
@@ -605,9 +605,9 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 5.0.0; MARKETING_VERSION = 5.0.5;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.refilc2.naplo.livecardpro; PRODUCT_BUNDLE_IDENTIFIER = hu.qwit.refilc.livecard;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
@@ -631,8 +631,8 @@
CODE_SIGN_ENTITLEMENTS = livecard/livecard.entitlements; CODE_SIGN_ENTITLEMENTS = livecard/livecard.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 257; CURRENT_PROJECT_VERSION = 276;
DEVELOPMENT_TEAM = 4DKAF249F3; DEVELOPMENT_TEAM = 4J97JVC2FG;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = livecard/Info.plist; INFOPLIST_FILE = livecard/Info.plist;
@@ -645,9 +645,9 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 5.0.0; MARKETING_VERSION = 5.0.5;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.refilc2.naplo.livecardpro; PRODUCT_BUNDLE_IDENTIFIER = hu.qwit.refilc.livecard;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
@@ -775,8 +775,8 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 257; CURRENT_PROJECT_VERSION = 280;
DEVELOPMENT_TEAM = 4DKAF249F3; DEVELOPMENT_TEAM = 4J97JVC2FG;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = reFilc; INFOPLIST_KEY_CFBundleDisplayName = reFilc;
@@ -785,8 +785,8 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 5.0.0; MARKETING_VERSION = 5.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.refilc2.naplo; PRODUCT_BUNDLE_IDENTIFIER = hu.qwit.refilc;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -803,8 +803,8 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 257; CURRENT_PROJECT_VERSION = 280;
DEVELOPMENT_TEAM = 4DKAF249F3; DEVELOPMENT_TEAM = 4J97JVC2FG;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = reFilc; INFOPLIST_KEY_CFBundleDisplayName = reFilc;
@@ -813,8 +813,8 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 5.0.0; MARKETING_VERSION = 5.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.refilc2.naplo; PRODUCT_BUNDLE_IDENTIFIER = hu.qwit.refilc;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;

View File

@@ -12,7 +12,7 @@
<key>livecard.xcscheme_^#shared#^_</key> <key>livecard.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>83</integer> <integer>86</integer>
</dict> </dict>
</dict> </dict>
</dict> </dict>

View File

@@ -3,7 +3,7 @@ import background_fetch
import ActivityKit import ActivityKit
import Flutter import Flutter
@UIApplicationMain @main
@objc class AppDelegate: FlutterAppDelegate { @objc class AppDelegate: FlutterAppDelegate {
private var methodChannel: FlutterMethodChannel? private var methodChannel: FlutterMethodChannel?

View File

@@ -1,40 +1,44 @@
import Foundation import Foundation
import ActivityKit
public struct LessonData { public struct LessonData {
var color: String var color: String
var icon: String var icon: String
var index: String var index: String
var title: String var title: String
var subtitle: String var subtitle: String
var description: String var description: String
var startDate: Date var startDate: Date
var endDate: Date var endDate: Date
var date: ClosedRange<Date> var date: ClosedRange<Date>
var nextSubject: String var nextSubject: String
var nextRoom: String var nextRoom: String
init(from dictionary: [String: Any]) {
self.color = dictionary["color"] as? String ?? ""
self.icon = dictionary["icon"] as? String ?? ""
self.index = dictionary["index"] as? String ?? ""
self.title = dictionary["title"] as? String ?? ""
self.subtitle = dictionary["subtitle"] as? String ?? ""
self.description = dictionary["description"] as? String ?? ""
self.nextSubject = dictionary["nextSubject"] as? String ?? ""
self.nextRoom = dictionary["nextRoom"] as? String ?? ""
init(from dictionary: [String: Any]) { if let startDateStr = dictionary["startDate"] as? String, let startDateInt = Int(startDateStr) {
self.color = dictionary["color"] as? String ?? "" self.startDate = Date(timeIntervalSince1970: TimeInterval(startDateInt) / 1000)
self.icon = dictionary["icon"] as? String ?? "" } else {
self.index = dictionary["index"] as? String ?? "" self.startDate = Date()
self.title = dictionary["title"] as? String ?? ""
self.subtitle = dictionary["subtitle"] as? String ?? ""
self.description = dictionary["description"] as? String ?? ""
self.nextSubject = dictionary["nextSubject"] as? String ?? ""
self.nextRoom = dictionary["nextRoom"] as? String ?? ""
if let startDateStr = dictionary["startDate"] as? String, let startDateInt = Int(startDateStr) {
self.startDate = Date(timeIntervalSince1970: TimeInterval(startDateInt) / 1000)
} else {
self.startDate = Date()
}
if let endDateStr = dictionary["endDate"] as? String, let endDateInt = Int(endDateStr) {
self.endDate = Date(timeIntervalSince1970: TimeInterval(endDateInt) / 1000)
} else {
self.endDate = Date()
}
date = self.startDate...self.endDate
} }
if let endDateStr = dictionary["endDate"] as? String, let endDateInt = Int(endDateStr) {
self.endDate = Date(timeIntervalSince1970: TimeInterval(endDateInt) / 1000)
} else {
self.endDate = self.startDate
}
if self.startDate <= self.endDate {
self.date = self.startDate...self.endDate
} else {
self.date = self.endDate...self.endDate
}
}
} }

View File

@@ -64,19 +64,11 @@ struct LockScreenLiveActivityView: View {
.padding(.trailing, 90) .padding(.trailing, 90)
} else { } else {
MultilineTextView(text: "\(context.state.index) \(context.state.title)", limit: 25) MultilineTextView(text: "\(context.state.index) \(context.state.title) - \(context.state.subtitle)", limit: 25)
.font(.body) .font(.body)
.bold() .bold()
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
} }
//Terem
if (!context.state.subtitle.isEmpty) {
Text(context.state.subtitle)
.italic()
.bold()
.font(.system(size: 13))
}
} }
// Leírás // Leírás
@@ -92,10 +84,8 @@ struct LockScreenLiveActivityView: View {
.resizable() .resizable()
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: CGFloat(8), height: CGFloat(8)) .frame(width: CGFloat(8), height: CGFloat(8))
Text(context.state.nextSubject) Text("\(context.state.nextSubject) - \(context.state.nextRoom)")
.font(.caption) .font(.caption)
Text(context.state.nextRoom)
.font(.caption2)
} }
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
} else { } else {
@@ -197,33 +187,23 @@ struct LiveCardWidget: Widget {
} else { } else {
// Amikor óra van, expanded DynamicIsland // Amikor óra van, expanded DynamicIsland
MultilineTextView(text: "\(context.state.index) \(context.state.title)", limit: 25) MultilineTextView(text: "\(context.state.index) \(context.state.title) - \(context.state.subtitle)", limit: 25)
.lineLimit(1) .lineLimit(1)
.font(.body) .font(.body)
.bold() .bold()
.padding(.trailing, -35) .padding(.trailing, -35)
Text(context.state.subtitle) Spacer(minLength: 2)
.lineLimit(1)
.italic()
.bold()
.font(.system(size: 13))
.padding(.trailing, -50)
Spacer(minLength: 5)
if(context.state.nextRoom != "" && context.state.nextSubject != "") { if(context.state.nextRoom != "" && context.state.nextSubject != "") {
Text("Következő óra és terem:") Text("Következő óra és terem:")
.font(.system(size: 14)) .font(.system(size: 14))
.padding(.trailing, -35) .padding(.trailing, -45)
Spacer(minLength: 2) Spacer(minLength: 2)
Text(context.state.nextSubject)
.modifier(DynamicFontSizeModifier(text: context.state.nextSubject)) Text("\(context.state.nextSubject) - \(context.state.nextRoom)")
.padding(.trailing, -35) .modifier(DynamicFontSizeModifier(text: "\(context.state.nextSubject) - \(context.state.nextRoom)"))
Text(context.state.nextRoom) .padding(.trailing, 35)
// ignore: based on nextSubject characters, I check that the font size of the room is the same as the next subject.
.modifier(DynamicFontSizeModifier(text: context.state.nextSubject))
.padding(.trailing, -35)
} else { } else {
Text("Ez az utolsó óra! Kitartást!") Text("Ez az utolsó óra! Kitartást!")
.font(.system(size: 14)) .font(.system(size: 14))
@@ -327,3 +307,66 @@ struct DynamicFontSizeModifier: ViewModifier {
} }
} }
} }
struct LiveCardWidget_Previews: PreviewProvider {
static let attributes = LiveActivitiesAppAttributes()
static let duringLessonExmaple = LiveActivitiesAppAttributes.ContentState(
color: "#FF5733",
icon: "bell",
index: "1.",
title: "Math Class",
subtitle: "101",
description: "Algebra lesson",
startDate: Date(),
endDate: Date().addingTimeInterval(3000),
date: Date()...Date().addingTimeInterval(3000), // 50 minutes later
nextSubject: "Physics",
nextRoom: "102"
)
static let inBreak = LiveActivitiesAppAttributes.ContentState(
color: "#FF5733",
icon: "house",
index: "",
title: "Szünet",
subtitle: "Menj a(z) 122 terembe.",
description: "",
startDate: Date(),
endDate: Date().addingTimeInterval(3000),
date: Date()...Date().addingTimeInterval(3000), // 50 minutes later
nextSubject: "Physics",
nextRoom: "122"
)
static let lastLesson = LiveActivitiesAppAttributes.ContentState(
color: "#00ff00",
icon: "bell",
index: "6.",
title: "Math Class",
subtitle: "",
description: "Lorem Ipsum",
startDate: Date(),
endDate: Date().addingTimeInterval(3000),
date: Date()...Date().addingTimeInterval(3000), // 50 minutes later
nextSubject: "",
nextRoom: ""
)
static var previews: some View {
// Dynamic Island Compact
Group {
attributes
.previewContext(duringLessonExmaple, viewKind: .dynamicIsland(.compact))
.previewDisplayName("During Lesson")
attributes
.previewContext(inBreak, viewKind: .dynamicIsland(.compact))
.previewDisplayName("In Break")
attributes
.previewContext(lastLesson, viewKind: .dynamicIsland(.compact))
.previewDisplayName("During Last Lesson")
}
}
}

View File

@@ -18,11 +18,11 @@ import 'package:connectivity_plus/connectivity_plus.dart';
class FilcAPI { class FilcAPI {
// API base // API base
static const baseUrl = "https://api.refilc.hu"; static const baseUrl = "https://api.refilcapp.hu";
// Public API // Public API
static const schoolList = "$baseUrl/v3/public/school-list"; static const schoolList = "$baseUrl/v3/public/school-list";
static const news = "$baseUrl/v3/public/news"; static const news = "$baseUrl/v4/public/news";
static const supporters = "$baseUrl/v3/public/supporters"; static const supporters = "$baseUrl/v3/public/supporters";
// Private API // Private API
@@ -51,9 +51,12 @@ class FilcAPI {
static const gradeColorsByID = "$gradeColorsGet/"; static const gradeColorsByID = "$gradeColorsGet/";
// Payment API // Payment API
static const payment = "$baseUrl/v3/payment"; static const payment = "$baseUrl/v4/payment";
static const stripeSheet = "$payment/stripe-sheet"; static const stripeSheet = "$payment/stripe-sheet";
// Cloud Sync
static const cloudSyncApi = "$baseUrl/v4/me/cloud-sync";
static Future<bool> checkConnectivity() async => static Future<bool> checkConnectivity() async =>
(await Connectivity().checkConnectivity())[0] != ConnectivityResult.none; (await Connectivity().checkConnectivity())[0] != ConnectivityResult.none;
@@ -93,10 +96,14 @@ class FilcAPI {
"x-filc-id": settings.xFilcId, "x-filc-id": settings.xFilcId,
"user-agent": userAgent, "user-agent": userAgent,
// platform things // platform things
"rf-platform": Platform.operatingSystem, "rf-platform":
"rf-platform-version": Platform.operatingSystemVersion, settings.analyticsEnabled ? Platform.operatingSystem : "unknown",
"rf-app-version": "rf-platform-version": settings.analyticsEnabled
const String.fromEnvironment("APPVER", defaultValue: "?"), ? Platform.operatingSystemVersion
: "unknown",
"rf-app-version": settings.analyticsEnabled
? const String.fromEnvironment("APPVER", defaultValue: "?")
: "unknown",
"rf-uinid": settings.xFilcId, "rf-uinid": settings.xFilcId,
}; };
@@ -231,7 +238,7 @@ class FilcAPI {
} }
// sharing // sharing
static Future<void> addSharedTheme(SharedTheme theme) async { static Future<int> addSharedTheme(SharedTheme theme) async {
try { try {
theme.json.remove('json'); theme.json.remove('json');
theme.json['is_public'] = theme.isPublic.toString(); theme.json['is_public'] = theme.isPublic.toString();
@@ -263,13 +270,19 @@ class FilcAPI {
headers: {'Content-Type': 'application/x-www-form-urlencoded'}, headers: {'Content-Type': 'application/x-www-form-urlencoded'},
); );
if (res.statusCode != 201) { // if (res.statusCode != 201) {
throw "HTTP ${res.statusCode}: ${res.body}"; // throw "HTTP ${res.statusCode}: ${res.body}";
// }
if (res.statusCode == 201) {
log('Shared theme successfully with ID: ${theme.id}');
} }
log('Shared theme successfully with ID: ${theme.id}'); return res.statusCode;
} on Exception catch (error, stacktrace) { } on Exception catch (error, stacktrace) {
log("ERROR: FilcAPI.addSharedTheme: $error $stacktrace"); log("ERROR: FilcAPI.addSharedTheme: $error $stacktrace");
return 696;
} }
} }
@@ -303,8 +316,7 @@ class FilcAPI {
return null; return null;
} }
static Future<void> addSharedGradeColors( static Future<int> addSharedGradeColors(SharedGradeColors gradeColors) async {
SharedGradeColors gradeColors) async {
try { try {
gradeColors.json.remove('json'); gradeColors.json.remove('json');
gradeColors.json['is_public'] = gradeColors.isPublic.toString(); gradeColors.json['is_public'] = gradeColors.isPublic.toString();
@@ -320,13 +332,19 @@ class FilcAPI {
headers: {'Content-Type': 'application/x-www-form-urlencoded'}, headers: {'Content-Type': 'application/x-www-form-urlencoded'},
); );
if (res.statusCode != 201) { // if (res.statusCode != 201) {
throw "HTTP ${res.statusCode}: ${res.body}"; // throw "HTTP ${res.statusCode}: ${res.body}";
// }
if (res.statusCode == 201) {
log('Shared grade colors successfully with ID: ${gradeColors.id}');
} }
log('Shared grade colors successfully with ID: ${gradeColors.id}'); return res.statusCode;
} on Exception catch (error, stacktrace) { } on Exception catch (error, stacktrace) {
log("ERROR: FilcAPI.addSharedGradeColors: $error $stacktrace"); log("ERROR: FilcAPI.addSharedGradeColors: $error $stacktrace");
return 696;
} }
} }
@@ -375,6 +393,32 @@ class FilcAPI {
return null; 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 { class ErrorReport {

View File

@@ -65,8 +65,11 @@ Future loginAPI({
parents: ['Teszt András', 'Teszt Linda'], parents: ['Teszt András', 'Teszt Linda'],
json: {"a": "b"}, json: {"a": "b"},
address: '1117 Budapest, Gábor Dénes utca 4.', address: '1117 Budapest, Gábor Dénes utca 4.',
gradeDelay: 0,
), ),
role: Role.parent, role: Role.parent,
accessToken: '',
accessTokenExpire: DateTime.now(),
refreshToken: '', refreshToken: '',
); );
@@ -153,6 +156,8 @@ Future loginAPI({
name: student.name, name: student.name,
student: student, student: student,
role: JwtUtils.getRoleFromJWT(res["access_token"])!, role: JwtUtils.getRoleFromJWT(res["access_token"])!,
accessToken: res["access_token"],
accessTokenExpire: DateTime.now(),
refreshToken: '', refreshToken: '',
); );
@@ -234,6 +239,15 @@ Future newLoginAPI({
if (res != null) { if (res != null) {
if (kDebugMode) { if (kDebugMode) {
print(res); print(res);
// const splitSize = 1000;
// RegExp exp = RegExp(r"\w{" "$splitSize" "}");
// // String str = "0102031522";
// Iterable<Match> matches = exp.allMatches(res.toString());
// var list = matches.map((m) => m.group(0));
// list.forEach((e) {
// print(e);
// });
} }
if (res.containsKey("error")) { if (res.containsKey("error")) {
@@ -266,6 +280,9 @@ Future newLoginAPI({
name: student.name, name: student.name,
student: student, student: student,
role: role, role: role,
accessToken: res["access_token"],
accessTokenExpire:
DateTime.now().add(Duration(seconds: (res["expires_in"] - 30))),
refreshToken: res["refresh_token"], refreshToken: res["refresh_token"],
); );

View File

@@ -2,6 +2,7 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:refilc/api/providers/database_provider.dart'; import 'package:refilc/api/providers/database_provider.dart';
import 'package:refilc/api/providers/status_provider.dart'; import 'package:refilc/api/providers/status_provider.dart';
import 'package:refilc/api/providers/user_provider.dart'; import 'package:refilc/api/providers/user_provider.dart';
@@ -28,7 +29,7 @@ import 'liveactivity/platform_channel.dart';
// Mutex // Mutex
bool lock = false; bool lock = false;
Future<void> syncAll(BuildContext context) { Future<void> syncAll(BuildContext context) async {
if (lock) return Future.value(); if (lock) return Future.value();
// Lock // Lock
lock = true; lock = true;
@@ -40,6 +41,12 @@ Future<void> syncAll(BuildContext context) {
StatusProvider statusProvider = StatusProvider statusProvider =
Provider.of<StatusProvider>(context, listen: false); Provider.of<StatusProvider>(context, listen: false);
// check if access token isn't expired
// if (user.user?.accessToken == null) {
// lock = false;
// return Future.value();
// }
List<Future<void>> tasks = []; List<Future<void>> tasks = [];
int taski = 0; int taski = 0;
@@ -50,6 +57,49 @@ Future<void> syncAll(BuildContext context) {
} }
tasks = [ tasks = [
// refresh login
syncStatus(() async {
// print(user.user?.accessTokenExpire);
// print('${user.user?.accessToken ?? "no token"} - ACCESS TOKEN');
// user.user!.accessToken = "";
if (user.user == null) {
Navigator.of(context).pushNamedAndRemoveUntil("login", (_) => false);
lock = false;
return Future.value();
}
if (user.user!.accessToken.replaceAll(" ", "") == "") {
String uid = user.user!.id;
user.removeUser(uid);
await Provider.of<DatabaseProvider>(context, listen: false)
.store
.removeUser(uid);
Navigator.of(context).pushNamedAndRemoveUntil("login", (_) => false);
lock = false;
return;
}
if (user.user!.accessTokenExpire.isBefore(DateTime.now())) {
String authRes = await Provider.of<KretaClient>(context, listen: false)
.refreshLogin() ??
'';
if (authRes != 'success') {
if (kDebugMode) print('ERROR: failed to refresh login');
lock = false;
return Future.value();
} else {
if (kDebugMode) print('INFO: access token refreshed');
}
} else {
if (kDebugMode) print('INFO: access token is not expired');
}
}()),
syncStatus(Provider.of<GradeProvider>(context, listen: false).fetch()), syncStatus(Provider.of<GradeProvider>(context, listen: false).fetch()),
syncStatus(Provider.of<TimetableProvider>(context, listen: false) syncStatus(Provider.of<TimetableProvider>(context, listen: false)
.fetch(week: Week.current())), .fetch(week: Week.current())),
@@ -71,6 +121,8 @@ Future<void> syncAll(BuildContext context) {
if (studentJson == null) return; if (studentJson == null) return;
Student student = Student.fromJson(studentJson); Student student = Student.fromJson(studentJson);
// print(studentJson);
user.user?.name = student.name; user.user?.name = student.name;
// Store user // Store user
@@ -89,13 +141,11 @@ Future<void> syncAll(BuildContext context) {
return false; return false;
} }
return Future.wait(tasks).then((value) { return Future.wait(tasks).then((value) {
// Unlock // Unlock
lock = false; lock = false;
if(Platform.isIOS && LiveCardProvider.hasActivityStarted == true){ if (Platform.isIOS && LiveCardProvider.hasActivityStarted == true) {
PlatformChannel.endLiveActivity(); PlatformChannel.endLiveActivity();
LiveCardProvider.hasActivityStarted = false; LiveCardProvider.hasActivityStarted = false;
} }

View File

@@ -237,7 +237,7 @@ class App extends StatelessWidget {
}, },
onGenerateRoute: (settings) => rootNavigator(settings), onGenerateRoute: (settings) => rootNavigator(settings),
initialRoute: initialRoute:
user.getUsers().isNotEmpty ? "navigation" : "login", (user.getUsers().isNotEmpty) ? "navigation" : "login",
); );
}, },
); );

View File

@@ -27,7 +27,8 @@ const settingsDB = DatabaseStruct("settings", {
"notifications_absences": int, "notifications_absences": int,
"notifications_messages": int, "notifications_messages": int,
"notifications_lessons": int, // notifications "notifications_lessons": int, // notifications
"x_filc_id": String, "graph_class_avg": int, "presentation_mode": int, "x_filc_id": String, "graph_class_avg": int,
"analytics_enabled": int, "presentation_mode": int,
"bell_delay": int, "bell_delay_enabled": int, "bell_delay": int, "bell_delay_enabled": int,
"grade_opening_fun": int, "icon_pack": String, "premium_scopes": String, "grade_opening_fun": int, "icon_pack": String, "premium_scopes": String,
"premium_token": String, "premium_login": String, "premium_token": String, "premium_login": String,
@@ -55,6 +56,9 @@ const settingsDB = DatabaseStruct("settings", {
"uwu_mode": int, "uwu_mode": int,
"new_popups": int, "new_popups": int,
"unseen_new_features": String, "unseen_new_features": String,
"cloud_sync_enabled": int,
"cloud_sync_token": String,
"local_updated_at": String,
// quick settings // quick settings
"q_timetable_lesson_num": int, "q_timetable_sub_tiles": int, "q_timetable_lesson_num": int, "q_timetable_sub_tiles": int,
"q_subjects_sub_tiles": int, "q_subjects_sub_tiles": int,
@@ -66,6 +70,7 @@ const usersDB = DatabaseStruct("users", {
"institute_code": String, "student": String, "role": int, "institute_code": String, "student": String, "role": int,
"nickname": String, "picture": String, // premium only (it's now plus btw) "nickname": String, "picture": String, // premium only (it's now plus btw)
"grade_streak": int, "grade_streak": int,
"access_token": String, "access_token_expire": String,
"refresh_token": String, "refresh_token": String,
}); });
const userDataDB = DatabaseStruct("user_data", { const userDataDB = DatabaseStruct("user_data", {
@@ -140,6 +145,8 @@ Future<Database> initDB(DatabaseProvider database) async {
"nickname": "", "nickname": "",
"picture": "", "picture": "",
"grade_streak": 0, "grade_streak": 0,
"access_token": "",
"access_token_expire": "",
"refresh_token": "", "refresh_token": "",
}, },
); );

View 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,
);
}
}

View File

@@ -7,6 +7,8 @@ class News {
String platform; String platform;
bool emergency; bool emergency;
DateTime expireDate; DateTime expireDate;
List<String>? appVersions;
String? specificAppId;
Map? json; Map? json;
News({ News({
@@ -18,6 +20,8 @@ class News {
required this.platform, required this.platform,
required this.emergency, required this.emergency,
required this.expireDate, required this.expireDate,
this.appVersions,
this.specificAppId,
this.json, this.json,
}); });
@@ -31,6 +35,10 @@ class News {
platform: json["platform"] ?? "", platform: json["platform"] ?? "",
emergency: json["emergency"] ?? false, emergency: json["emergency"] ?? false,
expireDate: DateTime.parse(json["expire_date"] ?? ''), expireDate: DateTime.parse(json["expire_date"] ?? ''),
appVersions: json["app_versions"] != null
? List<String>.from(json["app_versions"])
: null,
specificAppId: json["specific_app_id"],
json: json, json: json,
); );
} }

View File

@@ -60,6 +60,7 @@ class SettingsProvider extends ChangeNotifier {
UpdateChannel _updateChannel; UpdateChannel _updateChannel;
Config _config; Config _config;
String _xFilcId; String _xFilcId;
bool _analyticsEnabled;
bool _graphClassAvg; bool _graphClassAvg;
bool _goodStudent; bool _goodStudent;
bool _presentationMode; bool _presentationMode;
@@ -108,6 +109,9 @@ class SettingsProvider extends ChangeNotifier {
bool _uwuMode; bool _uwuMode;
bool _newPopups; bool _newPopups;
List<String> _unseenNewFeatures; List<String> _unseenNewFeatures;
bool _cloudSyncEnabled;
String _cloudSyncToken;
DateTime _updatedAt;
// quick settings // quick settings
bool _qTimetableLessonNum; bool _qTimetableLessonNum;
bool _qTimetableSubTiles; bool _qTimetableSubTiles;
@@ -137,6 +141,7 @@ class SettingsProvider extends ChangeNotifier {
required UpdateChannel updateChannel, required UpdateChannel updateChannel,
required Config config, required Config config,
required String xFilcId, required String xFilcId,
required bool analyticsEnabled,
required bool graphClassAvg, required bool graphClassAvg,
required bool goodStudent, required bool goodStudent,
required bool presentationMode, required bool presentationMode,
@@ -182,6 +187,9 @@ class SettingsProvider extends ChangeNotifier {
required bool uwuMode, required bool uwuMode,
required bool newPopups, required bool newPopups,
required List<String> unseenNewFeatures, required List<String> unseenNewFeatures,
required bool cloudSyncEnabled,
required String cloudSyncToken,
required DateTime updatedAt,
required bool qTimetableLessonNum, required bool qTimetableLessonNum,
required bool qTimetableSubTiles, required bool qTimetableSubTiles,
required bool qSubjectsSubTiles, required bool qSubjectsSubTiles,
@@ -208,6 +216,7 @@ class SettingsProvider extends ChangeNotifier {
_updateChannel = updateChannel, _updateChannel = updateChannel,
_config = config, _config = config,
_xFilcId = xFilcId, _xFilcId = xFilcId,
_analyticsEnabled = analyticsEnabled,
_graphClassAvg = graphClassAvg, _graphClassAvg = graphClassAvg,
_goodStudent = goodStudent, _goodStudent = goodStudent,
_presentationMode = presentationMode, _presentationMode = presentationMode,
@@ -253,6 +262,9 @@ class SettingsProvider extends ChangeNotifier {
_uwuMode = uwuMode, _uwuMode = uwuMode,
_newPopups = newPopups, _newPopups = newPopups,
_unseenNewFeatures = unseenNewFeatures, _unseenNewFeatures = unseenNewFeatures,
_cloudSyncEnabled = cloudSyncEnabled,
_cloudSyncToken = cloudSyncToken,
_updatedAt = updatedAt,
_qTimetableLessonNum = qTimetableLessonNum, _qTimetableLessonNum = qTimetableLessonNum,
_qTimetableSubTiles = qTimetableSubTiles, _qTimetableSubTiles = qTimetableSubTiles,
_qSubjectsSubTiles = qSubjectsSubTiles; _qSubjectsSubTiles = qSubjectsSubTiles;
@@ -297,6 +309,7 @@ class SettingsProvider extends ChangeNotifier {
updateChannel: UpdateChannel.values[map["update_channel"]], updateChannel: UpdateChannel.values[map["update_channel"]],
config: Config.fromJson(configMap ?? {}), config: Config.fromJson(configMap ?? {}),
xFilcId: map["x_filc_id"], xFilcId: map["x_filc_id"],
analyticsEnabled: map["analytics_enabled"] == 1,
graphClassAvg: map["graph_class_avg"] == 1, graphClassAvg: map["graph_class_avg"] == 1,
goodStudent: false, goodStudent: false,
presentationMode: map["presentation_mode"] == 1, presentationMode: map["presentation_mode"] == 1,
@@ -343,6 +356,9 @@ class SettingsProvider extends ChangeNotifier {
uwuMode: map['uwu_mode'] == 1, uwuMode: map['uwu_mode'] == 1,
newPopups: map['new_popups'] == 1, newPopups: map['new_popups'] == 1,
unseenNewFeatures: jsonDecode(map["unseen_new_features"]).cast<String>(), 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, qTimetableLessonNum: map['q_timetable_lesson_num'] == 1,
qTimetableSubTiles: map['q_timetable_sub_tiles'] == 1, qTimetableSubTiles: map['q_timetable_sub_tiles'] == 1,
qSubjectsSubTiles: map['q_subjects_sub_tiles'] == 1, qSubjectsSubTiles: map['q_subjects_sub_tiles'] == 1,
@@ -377,6 +393,7 @@ class SettingsProvider extends ChangeNotifier {
"notification_poll_interval": _notificationPollInterval, "notification_poll_interval": _notificationPollInterval,
"config": jsonEncode(config.json), "config": jsonEncode(config.json),
"x_filc_id": _xFilcId, "x_filc_id": _xFilcId,
"analytics_enabled": _analyticsEnabled ? 1 : 0,
"graph_class_avg": _graphClassAvg ? 1 : 0, "graph_class_avg": _graphClassAvg ? 1 : 0,
"presentation_mode": _presentationMode ? 1 : 0, "presentation_mode": _presentationMode ? 1 : 0,
"bell_delay_enabled": _bellDelayEnabled ? 1 : 0, "bell_delay_enabled": _bellDelayEnabled ? 1 : 0,
@@ -421,6 +438,9 @@ class SettingsProvider extends ChangeNotifier {
"uwu_mode": _uwuMode ? 1 : 0, "uwu_mode": _uwuMode ? 1 : 0,
"new_popups": _newPopups ? 1 : 0, "new_popups": _newPopups ? 1 : 0,
"unseen_new_features": jsonEncode(_unseenNewFeatures), "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_lesson_num": _qTimetableLessonNum ? 1 : 0,
"q_timetable_sub_tiles": _qTimetableSubTiles ? 1 : 0, "q_timetable_sub_tiles": _qTimetableSubTiles ? 1 : 0,
"q_subjects_sub_tiles": _qSubjectsSubTiles ? 1 : 0, "q_subjects_sub_tiles": _qSubjectsSubTiles ? 1 : 0,
@@ -458,6 +478,7 @@ class SettingsProvider extends ChangeNotifier {
updateChannel: UpdateChannel.stable, updateChannel: UpdateChannel.stable,
config: Config.fromJson({}), config: Config.fromJson({}),
xFilcId: const Uuid().v4(), xFilcId: const Uuid().v4(),
analyticsEnabled: true,
graphClassAvg: false, graphClassAvg: false,
goodStudent: false, goodStudent: false,
presentationMode: false, presentationMode: false,
@@ -503,6 +524,9 @@ class SettingsProvider extends ChangeNotifier {
uwuMode: false, uwuMode: false,
newPopups: true, newPopups: true,
unseenNewFeatures: ['grade_exporting'], unseenNewFeatures: ['grade_exporting'],
cloudSyncEnabled: false,
cloudSyncToken: '',
updatedAt: DateTime.now(),
qTimetableLessonNum: true, qTimetableLessonNum: true,
qTimetableSubTiles: true, qTimetableSubTiles: true,
qSubjectsSubTiles: true, qSubjectsSubTiles: true,
@@ -532,6 +556,7 @@ class SettingsProvider extends ChangeNotifier {
UpdateChannel get updateChannel => _updateChannel; UpdateChannel get updateChannel => _updateChannel;
Config get config => _config; Config get config => _config;
String get xFilcId => _xFilcId; String get xFilcId => _xFilcId;
bool get analyticsEnabled => _analyticsEnabled;
bool get graphClassAvg => _graphClassAvg; bool get graphClassAvg => _graphClassAvg;
bool get goodStudent => _goodStudent; bool get goodStudent => _goodStudent;
bool get presentationMode => _presentationMode; bool get presentationMode => _presentationMode;
@@ -576,6 +601,9 @@ class SettingsProvider extends ChangeNotifier {
bool get uwuMode => _uwuMode; bool get uwuMode => _uwuMode;
bool get newPopups => _newPopups; bool get newPopups => _newPopups;
List<String> get unseenNewFeatures => _unseenNewFeatures; List<String> get unseenNewFeatures => _unseenNewFeatures;
bool get cloudSyncEnabled => _cloudSyncEnabled;
String get cloudSyncToken => _cloudSyncToken;
DateTime get updatedAt => _updatedAt;
bool get qTimetableLessonNum => _qTimetableLessonNum; bool get qTimetableLessonNum => _qTimetableLessonNum;
bool get qTimetableSubTiles => _qTimetableSubTiles; bool get qTimetableSubTiles => _qTimetableSubTiles;
bool get qSubjectsSubTiles => _qSubjectsSubTiles; bool get qSubjectsSubTiles => _qSubjectsSubTiles;
@@ -590,6 +618,7 @@ class SettingsProvider extends ChangeNotifier {
List<Color>? gradeColors, List<Color>? gradeColors,
bool? newsEnabled, bool? newsEnabled,
String? seenNewsId, String? seenNewsId,
String? seenNews, // only for restoring from map
bool? notificationsEnabled, bool? notificationsEnabled,
bool? notificationsGradesEnabled, bool? notificationsGradesEnabled,
bool? notificationsAbsencesEnabled, bool? notificationsAbsencesEnabled,
@@ -604,6 +633,7 @@ class SettingsProvider extends ChangeNotifier {
UpdateChannel? updateChannel, UpdateChannel? updateChannel,
Config? config, Config? config,
String? xFilcId, String? xFilcId,
bool? analyticsEnabled,
bool? graphClassAvg, bool? graphClassAvg,
bool? goodStudent, bool? goodStudent,
bool? presentationMode, bool? presentationMode,
@@ -645,6 +675,8 @@ class SettingsProvider extends ChangeNotifier {
bool? uwuMode, bool? uwuMode,
bool? newPopups, bool? newPopups,
List<String>? unseenNewFeatures, List<String>? unseenNewFeatures,
bool? cloudSyncEnabled,
String? cloudSyncToken,
bool? qTimetableLessonNum, bool? qTimetableLessonNum,
bool? qTimetableSubTiles, bool? qTimetableSubTiles,
bool? qSubjectsSubTiles, bool? qSubjectsSubTiles,
@@ -667,6 +699,7 @@ class SettingsProvider extends ChangeNotifier {
tempList.add(seenNewsId); tempList.add(seenNewsId);
_seenNews = tempList.join(','); _seenNews = tempList.join(',');
} }
if (seenNews != null && seenNews != _seenNews) _seenNews = seenNews;
if (notificationsEnabled != null && if (notificationsEnabled != null &&
notificationsEnabled != _notificationsEnabled) { notificationsEnabled != _notificationsEnabled) {
_notificationsEnabled = notificationsEnabled; _notificationsEnabled = notificationsEnabled;
@@ -708,6 +741,9 @@ class SettingsProvider extends ChangeNotifier {
} }
if (config != null && config != _config) _config = config; if (config != null && config != _config) _config = config;
if (xFilcId != null && xFilcId != _xFilcId) _xFilcId = xFilcId; if (xFilcId != null && xFilcId != _xFilcId) _xFilcId = xFilcId;
if (analyticsEnabled != null && analyticsEnabled != _analyticsEnabled) {
_analyticsEnabled = analyticsEnabled;
}
if (graphClassAvg != null && graphClassAvg != _graphClassAvg) { if (graphClassAvg != null && graphClassAvg != _graphClassAvg) {
_graphClassAvg = graphClassAvg; _graphClassAvg = graphClassAvg;
} }
@@ -839,6 +875,12 @@ class SettingsProvider extends ChangeNotifier {
if (unseenNewFeatures != null && unseenNewFeatures != _unseenNewFeatures) { if (unseenNewFeatures != null && unseenNewFeatures != _unseenNewFeatures) {
_unseenNewFeatures = unseenNewFeatures; _unseenNewFeatures = unseenNewFeatures;
} }
if (cloudSyncEnabled != null && cloudSyncEnabled != _cloudSyncEnabled) {
_cloudSyncEnabled = cloudSyncEnabled;
}
if (cloudSyncToken != null && cloudSyncToken != _cloudSyncToken) {
_cloudSyncToken = cloudSyncToken;
}
if (qTimetableLessonNum != null && if (qTimetableLessonNum != null &&
qTimetableLessonNum != _qTimetableLessonNum) { qTimetableLessonNum != _qTimetableLessonNum) {
_qTimetableLessonNum = qTimetableLessonNum; _qTimetableLessonNum = qTimetableLessonNum;
@@ -850,11 +892,115 @@ class SettingsProvider extends ChangeNotifier {
if (qSubjectsSubTiles != null && qSubjectsSubTiles != _qSubjectsSubTiles) { if (qSubjectsSubTiles != null && qSubjectsSubTiles != _qSubjectsSubTiles) {
_qSubjectsSubTiles = qSubjectsSubTiles; _qSubjectsSubTiles = qSubjectsSubTiles;
} }
// change updated at time
_updatedAt = DateTime.now();
// store or not // store or not
if (store) await _database?.store.storeSettings(this); if (store) await _database?.store.storeSettings(this);
notifyListeners(); 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() { void exportJson() {
String sets = json.encode(toMap()); String sets = json.encode(toMap());
Clipboard.setData(ClipboardData(text: sets)); Clipboard.setData(ClipboardData(text: sets));

View File

@@ -18,7 +18,13 @@ class User {
String picture; String picture;
int gradeStreak; int gradeStreak;
// new login method // new login method
String accessToken;
DateTime accessTokenExpire;
String refreshToken; String refreshToken;
// cloud sync
// String qwidAccessToken;
// DateTime? qwidAccessTokenExpire;
// String qwidRefreshToken;
String get displayName => nickname != '' ? nickname : name; String get displayName => nickname != '' ? nickname : name;
bool get hasStreak => gradeStreak > 0; bool get hasStreak => gradeStreak > 0;
@@ -34,7 +40,12 @@ class User {
this.nickname = "", this.nickname = "",
this.picture = "", this.picture = "",
this.gradeStreak = 0, this.gradeStreak = 0,
required this.accessToken,
required this.accessTokenExpire,
required this.refreshToken, required this.refreshToken,
// this.qwidAccessToken = "",
// this.qwidAccessTokenExpire,
// this.qwidRefreshToken = "",
}) { }) {
if (id != null) { if (id != null) {
this.id = id; this.id = id;
@@ -59,12 +70,22 @@ class User {
birth: DateTime.now(), birth: DateTime.now(),
yearId: '1', yearId: '1',
parents: [], parents: [],
gradeDelay: 0,
), ),
role: Role.values[map["role"] ?? 0], role: Role.values[map["role"] ?? 0],
nickname: map["nickname"] ?? "", nickname: map["nickname"] ?? "",
picture: map["picture"] ?? "", picture: map["picture"] ?? "",
gradeStreak: map["grade_streak"] ?? 0, gradeStreak: map["grade_streak"] ?? 0,
accessToken: map["access_token"] ?? "",
accessTokenExpire: DateTime.parse(map["access_token_expire"] != ""
? map["access_token_expire"]
: DateTime.now().toIso8601String()),
refreshToken: map["refresh_token"] ?? "", 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"] ?? "",
); );
} }
@@ -80,7 +101,14 @@ class User {
"nickname": nickname, "nickname": nickname,
"picture": picture, "picture": picture,
"grade_streak": gradeStreak, "grade_streak": gradeStreak,
"access_token": accessToken,
"access_token_expire": accessTokenExpire.toIso8601String(),
"refresh_token": refreshToken, "refresh_token": refreshToken,
// "qwid_access_token": qwidAccessToken,
// "qwid_access_token_expire": qwidAccessTokenExpire != null
// ? qwidAccessTokenExpire!.toIso8601String()
// : "",
// "qwid_refresh_token": qwidRefreshToken,
}; };
} }

View File

@@ -178,7 +178,8 @@ Future<List<DateWidget>> getFilterWidgets(FilterType activeData,
// Ads // Ads
case FilterType.ads: case FilterType.ads:
if (adProvider.available) { if (adProvider.available) {
items = ad_filter.getWidgets(adProvider.ads); items = ad_filter.getWidgets(
adProvider.ads, context);
} }
break; break;
} }

View File

@@ -1,23 +1,70 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:refilc/models/ad.dart'; import 'package:refilc/models/ad.dart';
import 'package:refilc/ui/date_widget.dart'; import 'package:refilc/ui/date_widget.dart';
import 'package:refilc_mobile_ui/common/widgets/ad/ad_tile.dart';
import 'package:refilc_mobile_ui/common/widgets/ad/ad_viewable.dart' as mobile; import 'package:refilc_mobile_ui/common/widgets/ad/ad_viewable.dart' as mobile;
import 'package:refilc_mobile_ui/plus/plus_screen.dart';
import 'package:refilc_plus/providers/plus_provider.dart';
import 'package:uuid/uuid.dart';
List<DateWidget> getWidgets(List<Ad> providerAds) { List<DateWidget> getWidgets(List<Ad> providerAds, BuildContext context) {
List<DateWidget> items = []; List<DateWidget> items = [];
bool hasPlus = Provider.of<PlusProvider>(context).hasPremium;
DateWidget plusWidget = DateWidget(
key: const Uuid().v4(),
date: DateTime.now(),
widget: AdTile(
Ad(
title: 'reFilc+',
description:
'Fizess elő reFilc+-ra, rejtsd el a hirdetéseket és támogasd az app működését!',
author: '',
logoUrl: Uri.parse('https://refilc.hu/image/brand/logo.png'),
overridePremium: false,
date: DateTime(2007, 6, 29, 9, 41),
expireDate: DateTime.now().add(const Duration(days: 11)),
launchUrl: Uri.parse('https://refilc.hu/plus'),
),
onTap: () => Navigator.of(context, rootNavigator: true)
.push(MaterialPageRoute(builder: (context) {
return const PlusScreen();
})),
padding: const EdgeInsets.symmetric(horizontal: 5.0),
showExternalIcon: false,
),
);
if (providerAds.isNotEmpty) { if (providerAds.isNotEmpty) {
for (var ad in providerAds) { for (var ad in providerAds) {
if (ad.date.isBefore(DateTime.now()) && if (ad.date.isBefore(DateTime.now()) &&
ad.expireDate.isAfter(DateTime.now())) { ad.expireDate.isAfter(DateTime.now()) &&
providerAds.sort((a, b) => -a.date.compareTo(b.date)); DateTime.now().hour.isOdd) {
if (!hasPlus || ad.overridePremium) {
providerAds.sort((a, b) => -a.date.compareTo(b.date));
items.add(DateWidget( items.add(DateWidget(
key: ad.description, key: ad.description,
date: ad.date, date: ad.date,
widget: mobile.AdViewable(ad), widget: mobile.AdViewable(ad),
)); ));
}
} else {
if (DateTime.now().weekday == DateTime.saturday &&
items.isEmpty &&
!hasPlus) {
items.add(plusWidget);
}
} }
} }
} else {
if (DateTime.now().weekday == DateTime.saturday &&
items.isEmpty &&
!hasPlus) {
items.add(plusWidget);
}
} }
return items; return items;

View File

@@ -3,10 +3,10 @@ description: "Egy nem hivatalos e-KRÉTA kliens, diákoktól diákoknak."
homepage: https://refilc.hu homepage: https://refilc.hu
publish_to: "none" publish_to: "none"
version: 5.0.4+274 version: 5.0.6+277
environment: environment:
sdk: ">=3.3.2 <=3.4.3" sdk: ">=3.3.2 <=3.6.0"
dependencies: dependencies:
flutter: flutter:
@@ -39,17 +39,17 @@ dependencies:
# ref: master # ref: master
path_provider: ^2.0.2 path_provider: ^2.0.2
permission_handler: ^11.0.1 permission_handler: ^11.0.1
share_plus: ^9.0.0 share_plus: ^10.0.3
connectivity_plus: ^6.0.3 connectivity_plus: ^6.0.3
flutter_displaymode: ^0.6.0 flutter_displaymode: ^0.6.0
quick_actions: ^1.0.1 quick_actions: ^1.0.1
animated_list_plus: ^0.5.0 animated_list_plus: ^0.5.0
dynamic_color: ^1.2.2 dynamic_color: ^1.2.2
material_color_utilities: ^0.8.0 material_color_utilities: ^0.11.1
crypto: ^3.0.2 crypto: ^3.0.2
elegant_notification: ^2.2.0 elegant_notification: ^2.2.0
flutter_feather_icons: ^2.0.0+1 flutter_feather_icons: ^2.0.0+1
live_activities: ^1.7.4 # live_activities: ^1.7.4
animated_flip_counter: ^0.3.4 animated_flip_counter: ^0.3.4
lottie: ^3.1.0 lottie: ^3.1.0
rive: ^0.12.4 rive: ^0.12.4
@@ -62,15 +62,15 @@ dependencies:
flutter_expandable_fab: ^2.0.0 flutter_expandable_fab: ^2.0.0
uni_links: ^0.5.1 uni_links: ^0.5.1
url_launcher: ^6.1.6 url_launcher: ^6.1.6
workmanager: # workmanager:
git: # git:
url: https://github.com/refilc/flutter_workmanager.git # url: https://github.com/refilc/flutter_workmanager.git
ref: v0.5.1 # ref: v0.5.1
flutter_svg: ^2.0.10+1 flutter_svg: ^2.0.10+1
image_picker: ^1.0.7 image_picker: ^1.0.7
animations: ^2.0.1 animations: ^2.0.1
background_fetch: ^1.1.5 background_fetch: ^1.1.5
flutter_local_notifications: ^17.1.2 flutter_local_notifications: ^18.0.1
package_info_plus: ^8.0.0 package_info_plus: ^8.0.0
screenshot: ^3.0.0 screenshot: ^3.0.0
flutter_staggered_grid_view: ^0.7.0 flutter_staggered_grid_view: ^0.7.0
@@ -83,16 +83,16 @@ dependencies:
extension_google_sign_in_as_googleapis_auth: ^2.0.12 extension_google_sign_in_as_googleapis_auth: ^2.0.12
maps_launcher: ^2.2.0 maps_launcher: ^2.2.0
google_fonts: ^6.1.0 google_fonts: ^6.1.0
flutter_stripe: ^10.0.0 # flutter_stripe: ^11.3.0
get_it: ^7.6.7 get_it: ^7.6.7
xml: ^6.5.0 xml: ^6.5.0
carousel_slider: ^4.2.1 carousel_slider: ^5.0.0
flutter_portal: ^1.1.4 flutter_portal: ^1.1.4
shake_flutter: ^17.0.0 shake_flutter: ^17.0.0
dev_dependencies: dev_dependencies:
flutter_lints: ^4.0.0 flutter_lints: ^5.0.0
flutter_launcher_icons: "^0.13.1" flutter_launcher_icons: ^0.14.2
flutter_native_splash: "^2.3.10" flutter_native_splash: "^2.3.10"
sqflite_common_ffi: ^2.0.0+3 sqflite_common_ffi: ^2.0.0+3

View File

@@ -2,7 +2,7 @@ name: refilc_desktop_ui
publish_to: "none" publish_to: "none"
environment: environment:
sdk: ">=3.3.2 <=3.4.3" sdk: ">=3.3.2 <=3.6.0"
dependencies: dependencies:
flutter: flutter:

View File

@@ -28,7 +28,7 @@ class KretaClient {
late final DatabaseProvider _database; late final DatabaseProvider _database;
late final StatusProvider _status; late final StatusProvider _status;
bool _loginRefreshing = false; // bool _loginRefreshing = false;
KretaClient({ KretaClient({
this.accessToken, this.accessToken,
@@ -67,10 +67,14 @@ class KretaClient {
headerMap = {}; headerMap = {};
} }
if (accessToken == null || accessToken == '') {
accessToken = _user.user?.accessToken;
}
try { try {
http.Response? res; http.Response? res;
for (int i = 0; i < 3; i++) { for (int i = 0; i < 2; i++) {
if (autoHeader) { if (autoHeader) {
if (!headerMap.containsKey("authorization") && accessToken != null) { if (!headerMap.containsKey("authorization") && accessToken != null) {
headerMap["authorization"] = "Bearer $accessToken"; headerMap["authorization"] = "Bearer $accessToken";
@@ -85,13 +89,15 @@ class KretaClient {
if (res.statusCode == 401) { if (res.statusCode == 401) {
headerMap.remove("authorization"); headerMap.remove("authorization");
await refreshLogin(); print("DEBUG: 401 error, refreshing login");
print("DEBUG: 401 error, URL: $url");
// await refreshLogin();
} else { } else {
break; break;
} }
// Wait before retrying // Wait before retrying
await Future.delayed(const Duration(milliseconds: 500)); await Future.delayed(const Duration(milliseconds: 1500));
} }
if (res == null) throw "Login error"; if (res == null) throw "Login error";
@@ -129,10 +135,14 @@ class KretaClient {
headerMap = {}; headerMap = {};
} }
if (accessToken == null || accessToken == '') {
accessToken = _user.user?.accessToken;
}
try { try {
http.Response? res; http.Response? res;
for (int i = 0; i < 3; i++) { for (int i = 0; i < 2; i++) {
if (autoHeader) { if (autoHeader) {
if (!headerMap.containsKey("authorization") && accessToken != null) { if (!headerMap.containsKey("authorization") && accessToken != null) {
headerMap["authorization"] = "Bearer $accessToken"; headerMap["authorization"] = "Bearer $accessToken";
@@ -150,11 +160,14 @@ class KretaClient {
res = await client.post(Uri.parse(url), headers: headerMap, body: body); res = await client.post(Uri.parse(url), headers: headerMap, body: body);
if (res.statusCode == 401) { if (res.statusCode == 401) {
await refreshLogin(); // await refreshLogin();
headerMap.remove("authorization"); headerMap.remove("authorization");
} else { } else {
break; break;
} }
// Wait before retrying
await Future.delayed(const Duration(milliseconds: 1500));
} }
if (res == null) throw "Login error"; if (res == null) throw "Login error";
@@ -187,6 +200,10 @@ class KretaClient {
headerMap = {}; headerMap = {};
} }
if (accessToken == null || accessToken == '') {
accessToken = _user.user?.accessToken;
}
try { try {
http.StreamedResponse? res; http.StreamedResponse? res;
@@ -217,7 +234,7 @@ class KretaClient {
if (res.statusCode == 401) { if (res.statusCode == 401) {
headerMap.remove("authorization"); headerMap.remove("authorization");
await refreshLogin(); // await refreshLogin();
} else { } else {
break; break;
} }
@@ -237,8 +254,8 @@ class KretaClient {
} }
Future<String?> refreshLogin() async { Future<String?> refreshLogin() async {
if (_loginRefreshing) return null; // if (_loginRefreshing) return null;
_loginRefreshing = true; // _loginRefreshing = true;
User? loginUser = _user.user; User? loginUser = _user.user;
if (loginUser == null) return null; if (loginUser == null) return null;
@@ -257,8 +274,8 @@ class KretaClient {
refreshToken ??= loginUser.refreshToken; refreshToken ??= loginUser.refreshToken;
// print("REFRESH TOKEN BELOW"); print("REFRESH TOKEN BELOW");
// print(refreshToken); print(refreshToken);
if (refreshToken != null) { if (refreshToken != null) {
// print("REFRESHING LOGIN"); // print("REFRESHING LOGIN");
@@ -268,8 +285,8 @@ class KretaClient {
refreshToken: loginUser.refreshToken, refreshToken: loginUser.refreshToken,
instituteCode: loginUser.instituteCode, instituteCode: loginUser.instituteCode,
)); ));
// print("REFRESH RESPONSE BELOW"); print("REFRESH RESPONSE BELOW");
// print(res); print(res);
if (res != null) { if (res != null) {
if (res.containsKey("error")) { if (res.containsKey("error")) {
// remove user if refresh token expired // remove user if refresh token expired
@@ -287,6 +304,11 @@ class KretaClient {
if (res.containsKey("access_token")) { if (res.containsKey("access_token")) {
accessToken = res["access_token"]; accessToken = res["access_token"];
loginUser.accessToken = res["access_token"];
loginUser.accessTokenExpire =
DateTime.now().add(Duration(seconds: (res["expires_in"] - 30)));
_database.store.storeUser(loginUser);
_user.refresh();
} }
if (res.containsKey("refresh_token")) { if (res.containsKey("refresh_token")) {
refreshToken = res["refresh_token"]; refreshToken = res["refresh_token"];
@@ -297,15 +319,20 @@ class KretaClient {
if (res.containsKey("id_token")) { if (res.containsKey("id_token")) {
idToken = res["id_token"]; idToken = res["id_token"];
} }
_loginRefreshing = false; // _loginRefreshing = false;
print('successful refresh');
return 'success';
} else { } else {
_loginRefreshing = false; // _loginRefreshing = false;
return null;
} }
} else { } else {
_loginRefreshing = false; // _loginRefreshing = false;
return null;
} }
return null; // return null;
} }
Future<void> logout() async { Future<void> logout() async {

View File

@@ -11,6 +11,8 @@ class Student {
String? address; String? address;
String? groupId; String? groupId;
List<String> parents; List<String> parents;
int gradeDelay;
String? bankAccount;
// List<String> parentsPhone; // List<String> parentsPhone;
String? className; String? className;
@@ -22,6 +24,8 @@ class Student {
required this.yearId, required this.yearId,
this.address, this.address,
required this.parents, required this.parents,
required this.gradeDelay,
this.bankAccount,
// required this.parentsPhone, // required this.parentsPhone,
this.json, this.json,
}); });
@@ -57,6 +61,10 @@ class Student {
: null : null
: null, : null,
parents: parents, parents: parents,
gradeDelay: json["Intezmeny"]["TestreszabasBeallitasok"]
["ErtekelesekMegjelenitesenekKesleltetesenekMerteke"] ??
0,
bankAccount: json["Bankszamla"]["BankszamlaSzam"],
json: json, json: json,
); );
} }

View File

@@ -153,7 +153,7 @@ class GradeProvider with ChangeNotifier {
for (Grade grade in grs) { for (Grade grade in grs) {
if (grade.value.value == 5) { if (grade.value.value == 5) {
gradeStreak++; gradeStreak++;
} else { } else if (grade.value.value !=0) {
break; break;
} }
} }

View File

@@ -19,7 +19,7 @@ class ShareProvider extends ChangeNotifier {
// } // }
// themes // themes
Future<SharedTheme> shareCurrentTheme( Future<(SharedTheme?, int)> shareCurrentTheme(
BuildContext context, { BuildContext context, {
bool isPublic = false, bool isPublic = false,
bool shareNick = true, bool shareNick = true,
@@ -56,9 +56,13 @@ class ShareProvider extends ChangeNotifier {
}; };
SharedTheme theme = SharedTheme.fromJson(themeJson, gradeColors); SharedTheme theme = SharedTheme.fromJson(themeJson, gradeColors);
FilcAPI.addSharedTheme(theme); int shareResult = await FilcAPI.addSharedTheme(theme);
return theme; if (shareResult == 201) {
return (theme, 201);
} else {
return (null, shareResult);
}
} }
Future<SharedTheme?> getThemeById(BuildContext context, Future<SharedTheme?> getThemeById(BuildContext context,
@@ -142,7 +146,7 @@ class ShareProvider extends ChangeNotifier {
} }
// grade colors // grade colors
Future<SharedGradeColors> shareCurrentGradeColors( Future<(SharedGradeColors?, int)> shareCurrentGradeColors(
BuildContext context, { BuildContext context, {
bool isPublic = false, bool isPublic = false,
bool shareNick = true, bool shareNick = true,
@@ -162,9 +166,13 @@ class ShareProvider extends ChangeNotifier {
}; };
SharedGradeColors gradeColors = SharedGradeColors.fromJson(gradeColorsJson); SharedGradeColors gradeColors = SharedGradeColors.fromJson(gradeColorsJson);
FilcAPI.addSharedGradeColors(gradeColors); int shareResult = await FilcAPI.addSharedGradeColors(gradeColors);
return gradeColors; if (shareResult == 201) {
return (gradeColors, 201);
} else {
return (null, shareResult);
}
} }
Future<SharedGradeColors?> getGradeColorsById(BuildContext context, Future<SharedGradeColors?> getGradeColorsById(BuildContext context,

View File

@@ -2,7 +2,7 @@ name: refilc_kreta_api
publish_to: "none" publish_to: "none"
environment: environment:
sdk: ">=3.3.2 <=3.4.3" sdk: ">=3.3.2 <=3.6.0"
dependencies: dependencies:
flutter: flutter:

View File

@@ -266,12 +266,16 @@ class _ProfileImageState extends State<ProfileImage> {
child: Transform.translate( child: Transform.translate(
offset: Offset(-widget.radius / 4, -widget.radius / 4), offset: Offset(-widget.radius / 4, -widget.radius / 4),
child: Container( child: Container(
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
child: Text( child: Image.asset(
'🔥', 'assets/images/apple_fire_emoji.png',
style: TextStyle(fontSize: widget.radius * 0.8), width: widget.radius,
), )
), // Text(
// '🔥',
// style: TextStyle(fontSize: widget.radius * 0.8),
// ),
),
), ),
), ),
), ),

View File

@@ -5,11 +5,13 @@ import 'package:refilc_mobile_ui/common/panel/panel_button.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart';
class AdTile extends StatelessWidget { class AdTile extends StatelessWidget {
const AdTile(this.ad, {super.key, this.onTap, this.padding}); const AdTile(this.ad,
{super.key, this.onTap, this.padding, this.showExternalIcon = true});
final Ad ad; final Ad ad;
final Function()? onTap; final Function()? onTap;
final EdgeInsetsGeometry? padding; final EdgeInsetsGeometry? padding;
final bool showExternalIcon;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -28,6 +30,7 @@ class AdTile extends StatelessWidget {
Text( Text(
ad.description, ad.description,
style: TextStyle( style: TextStyle(
fontSize: 14.5,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColors.of(context).text.withOpacity(0.7), color: AppColors.of(context).text.withOpacity(0.7),
), ),
@@ -38,6 +41,8 @@ class AdTile extends StatelessWidget {
? ClipRRect( ? ClipRRect(
borderRadius: BorderRadius.circular(50.0), borderRadius: BorderRadius.circular(50.0),
child: Image.network( child: Image.network(
width: 42.0,
height: 42.0,
ad.logoUrl.toString(), ad.logoUrl.toString(),
errorBuilder: (context, error, stackTrace) { errorBuilder: (context, error, stackTrace) {
ad.logoUrl = null; ad.logoUrl = null;
@@ -46,7 +51,12 @@ class AdTile extends StatelessWidget {
), ),
) )
: null, : null,
trailing: const Icon(FeatherIcons.externalLink), trailing: showExternalIcon
? const Icon(
FeatherIcons.externalLink,
size: 20.0,
)
: null,
), ),
); );
} }

View File

@@ -12,6 +12,7 @@ class AdViewable extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AdTile( return AdTile(
padding: const EdgeInsets.symmetric(horizontal: 5.0),
ad, ad,
onTap: () => launchUrl( onTap: () => launchUrl(
ad.launchUrl, ad.launchUrl,

View File

@@ -75,7 +75,7 @@ class _FSTimetableState extends State<FSTimetable> {
body: ListView.builder( body: ListView.builder(
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.symmetric(horizontal: 6.0, vertical: 24.0), padding: const EdgeInsets.symmetric(horizontal: 6.0, vertical: 24.0),
itemCount: maxLessonCount + 1, itemCount: maxLessonCount + 2,
itemBuilder: (context, index) { itemBuilder: (context, index) {
List<Widget> columns = []; List<Widget> columns = [];
for (int dayIndex = -1; dayIndex < days.length; dayIndex++) { for (int dayIndex = -1; dayIndex < days.length; dayIndex++) {
@@ -119,10 +119,8 @@ class _FSTimetableState extends State<FSTimetable> {
if (lessons.isEmpty) continue; 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) {
// if (index == 0 || dayIndex >=0) {
columns.add( columns.add(
SizedBox( SizedBox(
width: colw, width: colw,
@@ -141,16 +139,10 @@ class _FSTimetableState extends State<FSTimetable> {
continue; continue;
} }
final lessonIndex = index - dayOffset;
Lesson? lsn = lessons.firstWhereOrNull( Lesson? lsn = lessons.firstWhereOrNull(
(e) => e.lessonIndex == (index - 1).toString()); (e) => e.lessonIndex == (index - 1).toString());
if (lessonIndex < 0 || if (lsn == null) {
lessonIndex > lessons.length ||
(index == 1 && lsnIndx != 0) ||
(lsnIndx != 0 && lessonIndex - 1 == -1) ||
lsn == null) {
columns.add(SizedBox(width: colw)); columns.add(SizedBox(width: colw));
continue; continue;
} }
@@ -259,4 +251,4 @@ class _FSTimetableState extends State<FSTimetable> {
), ),
); );
} }
} }

View File

@@ -1,10 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:refilc/models/settings.dart'; import 'package:refilc/models/settings.dart';
import 'package:refilc_mobile_ui/common/action_button.dart';
import 'package:refilc_plus/providers/plus_provider.dart'; import 'package:refilc_plus/providers/plus_provider.dart';
import 'package:refilc_plus/ui/mobile/plus/activation_view/activation_view.dart'; import 'package:refilc_plus/ui/mobile/plus/activation_view/activation_view.dart';
import 'package:refilc_mobile_ui/plus/plus_screen.i18n.dart'; import 'package:refilc_mobile_ui/plus/plus_screen.i18n.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:uuid/uuid.dart';
class PlusPlanCard extends StatelessWidget { class PlusPlanCard extends StatelessWidget {
const PlusPlanCard({ const PlusPlanCard({
@@ -38,33 +40,39 @@ class PlusPlanCard extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
if (!docsAccepted) { // if (!docsAccepted) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar( // ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text( // content: Text(
"El kell fogadnod az ÁSZF-et és az Adatkezelési Tájékoztatót!", // "El kell fogadnod az ÁSZF-et és az Adatkezelési Tájékoztatót!",
style: // style:
TextStyle(color: Colors.black, fontWeight: FontWeight.bold), // TextStyle(color: Colors.black, fontWeight: FontWeight.bold),
), // ),
backgroundColor: Colors.white, // backgroundColor: Colors.white,
)); // ));
return; // return;
} // }
if (Provider.of<SettingsProvider>(context, listen: false).xFilcId == if (Provider.of<SettingsProvider>(context, listen: false).xFilcId ==
"none") { "none") {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar( Provider.of<SettingsProvider>(context, listen: false)
content: Text( .update(xFilcId: const Uuid().v4(), store: true);
"Be kell kapcsolnod a Névtelen Analitikát a beállítások főoldalán, mielőtt reFilc+ előfizetést vásárolnál!",
style:
TextStyle(color: Colors.black, fontWeight: FontWeight.bold),
),
backgroundColor: Colors.white,
));
return;
} }
// if (Provider.of<SettingsProvider>(context, listen: false).xFilcId ==
// "none") {
// ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
// content: Text(
// "Be kell kapcsolnod a Névtelen Analitikát a beállítások főoldalán, mielőtt reFilc+ előfizetést vásárolnál!",
// style:
// TextStyle(color: Colors.black, fontWeight: FontWeight.bold),
// ),
// backgroundColor: Colors.white,
// ));
// return;
// }
if (Provider.of<PlusProvider>(context, listen: false).hasPremium) { if (Provider.of<PlusProvider>(context, listen: false).hasPremium) {
if (!active) { if (!active) {
launchUrl( launchUrl(
@@ -77,9 +85,66 @@ class PlusPlanCard extends StatelessWidget {
return; return;
} }
Navigator.of(context).push(MaterialPageRoute(builder: (context) { showDialog(
return PremiumActivationView(product: id); context: context,
})); builder: (context) => AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0)),
title: Text('docs'.i18n),
content: Text('docs_acceptance'.i18n),
actions: [
ActionButton(
label: "next".i18n,
onTap: () {
// pop dialog
Navigator.of(context).pop();
// 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",
);
}));
},
),
],
),
);
},
),
],
),
);
}, },
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(

View File

@@ -1,5 +1,6 @@
// ignore_for_file: use_build_context_synchronously // ignore_for_file: use_build_context_synchronously
import 'package:flutter/services.dart';
import 'package:refilc_mobile_ui/plus/plus_screen.i18n.dart'; import 'package:refilc_mobile_ui/plus/plus_screen.i18n.dart';
import 'package:refilc_mobile_ui/plus/components/plan_card.dart'; import 'package:refilc_mobile_ui/plus/components/plan_card.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart';
@@ -335,6 +336,46 @@ class PlusScreenState extends State<PlusScreen> {
contentPadding: contentPadding:
const EdgeInsets.only(left: 15.0, right: 10.0), const EdgeInsets.only(left: 15.0, right: 10.0),
onTap: () async { onTap: () async {
// try clipboard re-activation
final data = await Clipboard.getData("text/plain");
if (data != null &&
data.text != null &&
data.text != "") {
// activate using clipboard data
final result = await context
.read<PlusProvider>()
.auth
.finishAuth(data.text!);
if (!result && mounted) {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content: Text(
"Sikertelen aktiválás. Kérlek próbáld újra később!",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold),
),
backgroundColor: Colors.red,
));
} else {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content: Text(
"Sikeres aktiválás!",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold),
),
backgroundColor: Colors.green,
));
Future.delayed(const Duration(seconds: 2),
() => Navigator.of(context).pop());
}
}
// try re-activation using refresh
final result = await context final result = await context
.read<PlusProvider>() .read<PlusProvider>()
.auth .auth
@@ -379,41 +420,41 @@ class PlusScreenState extends State<PlusScreen> {
), ),
), ),
// aszf warning // aszf warning
const SizedBox( // const SizedBox(
height: 18.0, // height: 18.0,
), // ),
Container( // Container(
decoration: BoxDecoration( // decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.0), // borderRadius: BorderRadius.circular(16.0),
border: Border.all( // border: Border.all(
color: Colors.black.withOpacity(0.2), // color: Colors.black.withOpacity(0.2),
), // ),
), // ),
child: CheckboxListTile( // child: CheckboxListTile(
side: // side:
const BorderSide(color: Colors.black, width: 2.0), // const BorderSide(color: Colors.black, width: 2.0),
contentPadding: // contentPadding:
const EdgeInsets.only(left: 15.0, right: 10.0), // const EdgeInsets.only(left: 15.0, right: 10.0),
value: docsAccepted, // value: docsAccepted,
onChanged: (value) { // onChanged: (value) {
setState(() { // setState(() {
docsAccepted = !docsAccepted; // docsAccepted = !docsAccepted;
}); // });
}, // },
// title: Text( // // title: Text(
// 'show_lifetime'.i18n, // // 'show_lifetime'.i18n,
// style: const TextStyle( // // style: const TextStyle(
// color: Colors.black, // // color: Colors.black,
// fontWeight: FontWeight.w500, // // fontWeight: FontWeight.w500,
// ), // // ),
// ), // // ),
subtitle: const Text( // subtitle: const Text(
'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)?', // '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)?',
textAlign: TextAlign.start, // textAlign: TextAlign.start,
style: TextStyle(color: Colors.black), // style: TextStyle(color: Colors.black),
), // ),
), // ),
), // ),
// CheckboxListTile(value: false, onChanged: onChanged) // CheckboxListTile(value: false, onChanged: onChanged)
// Padding( // Padding(
// padding: const EdgeInsets.symmetric(horizontal: 12.0), // padding: const EdgeInsets.symmetric(horizontal: 12.0),

View File

@@ -47,13 +47,24 @@ extension SettingsLocalization on String {
"rfp_16": "Private leaks and informations about upcoming features", "rfp_16": "Private leaks and informations about upcoming features",
"rfp_17": "Grade exporting", "rfp_17": "Grade exporting",
"rfp_18": "Viewing exported grades", "rfp_18": "Viewing exported grades",
// 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 // other
"and": " and ", "and": " and ",
"every": "Every ", "every": "Every ",
"benefit": " benefit", "benefit": " benefit",
"show_lifetime": "Show Lifetime Plans", "show_lifetime": "Show Lifetime Plans",
"more_soon": "More coming soon...", "more_soon": "More coming soon...",
"faq_dc": "To redeem your benefits, contact us on Discord in DMs!", "faq_dc":
"To redeem your Discord-related benefits, contact us on Discord in DMs!",
"reactivate": "Reactivate Existing Subscription", "reactivate": "Reactivate Existing Subscription",
}, },
"hu_hu": { "hu_hu": {
@@ -100,6 +111,16 @@ extension SettingsLocalization on String {
"rfp_16": "Privát betekintések és információk közelgő újításokról", "rfp_16": "Privát betekintések és információk közelgő újításokról",
"rfp_17": "Jegy exportálás", "rfp_17": "Jegy exportálás",
"rfp_18": "Exportált jegyek megtekintése", "rfp_18": "Exportált jegyek megtekintése",
// 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 // other
"and": " és ", "and": " és ",
"every": "Minden ", "every": "Minden ",
@@ -107,7 +128,7 @@ extension SettingsLocalization on String {
"show_lifetime": "Örökre szóló csomagok", "show_lifetime": "Örökre szóló csomagok",
"more_soon": "Hamarosan mégtöbb finomság...", "more_soon": "Hamarosan mégtöbb finomság...",
"faq_dc": "faq_dc":
"Az előnyök beváltásához írj nekünk Discord-on privát üzenetet!", "A Discord-al kapcsolatos előnyök beváltásához írj nekünk Discord-on privát üzenetet!",
"reactivate": "Meglévő előfizetés újraaktiválása", "reactivate": "Meglévő előfizetés újraaktiválása",
}, },
"de_de": { "de_de": {
@@ -156,6 +177,16 @@ extension SettingsLocalization on String {
"rfp_16": "Private Leaks und Informationen über kommende Funktionen", "rfp_16": "Private Leaks und Informationen über kommende Funktionen",
"rfp_17": "Notenexport", "rfp_17": "Notenexport",
"rfp_18": "Anzeigen exportierter Noten", "rfp_18": "Anzeigen exportierter Noten",
// 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 // other
"and": " und ", "and": " und ",
"every": "Jeder ", "every": "Jeder ",

View File

@@ -1,21 +1,17 @@
// import 'dart:async'; // import 'dart:async';
import 'package:refilc/api/client.dart'; import 'package:refilc/api/client.dart';
import 'dart:io' show Platform;
import 'package:refilc/api/login.dart'; import 'package:refilc/api/login.dart';
import 'package:refilc/theme/colors/colors.dart'; import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc_mobile_ui/common/bottom_sheet_menu/rounded_bottom_sheet.dart';
import 'package:refilc_mobile_ui/common/custom_snack_bar.dart'; import 'package:refilc_mobile_ui/common/custom_snack_bar.dart';
import 'package:refilc_mobile_ui/common/system_chrome.dart'; import 'package:refilc_mobile_ui/common/system_chrome.dart';
import 'package:refilc_mobile_ui/common/widgets/absence/absence_display.dart';
import 'package:refilc_mobile_ui/screens/login/login_button.dart';
import 'package:refilc_mobile_ui/screens/login/login_input.dart';
import 'package:refilc_mobile_ui/screens/login/school_input/school_input.dart'; import 'package:refilc_mobile_ui/screens/login/school_input/school_input.dart';
import 'package:refilc_mobile_ui/screens/settings/privacy_view.dart'; import 'package:refilc_mobile_ui/screens/settings/privacy_view.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'login_screen.i18n.dart'; import 'login_screen.i18n.dart';
import 'package:carousel_slider/carousel_slider.dart'; import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:refilc_mobile_ui/screens/login/kreten_login.dart'; //new library for new web login import 'package:refilc_mobile_ui/screens/login/kreten_login.dart'; //new library for new web login
class LoginScreen extends StatefulWidget { class LoginScreen extends StatefulWidget {
@@ -79,13 +75,19 @@ class LoginScreenState extends State<LoginScreen> {
}); });
} }
double paddingTop = 0;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
precacheImage(const AssetImage('assets/images/showcase1.png'), context); precacheImage(const AssetImage('assets/images/showcase1.png'), context);
precacheImage(const AssetImage('assets/images/showcase2.png'), context); precacheImage(const AssetImage('assets/images/showcase2.png'), context);
precacheImage(const AssetImage('assets/images/showcase3.png'), context); precacheImage(const AssetImage('assets/images/showcase3.png'), context);
precacheImage(const AssetImage('assets/images/showcase4.png'), context); precacheImage(const AssetImage('assets/images/showcase4.png'), context);
bool selected = false;
if (Platform.isIOS) {
paddingTop = 0;
} else if (Platform.isAndroid) {
paddingTop = 20;
}
return Scaffold( return Scaffold(
body: Container( body: Container(
@@ -102,7 +104,7 @@ class LoginScreenState extends State<LoginScreen> {
children: [ children: [
// app icon // app icon
Padding( Padding(
padding: const EdgeInsets.only(left: 24, top: 20), padding: EdgeInsets.only(left: 24, top: paddingTop),
child: Row( child: Row(
children: [ children: [
Image.asset( Image.asset(
@@ -447,6 +449,7 @@ class LoginScreenState extends State<LoginScreen> {
// }), // }),
// ); // );
// } // }
// ignore: non_constant_identifier_names
void _NewLoginAPI({required BuildContext context}) { void _NewLoginAPI({required BuildContext context}) {
String code = codeController.text; String code = codeController.text;

View 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
),
],
);
},
),
),
],
);
}
}

View File

@@ -56,6 +56,16 @@ class AccountView extends StatelessWidget {
Detail( Detail(
title: "parents".plural(user.student.parents.length), title: "parents".plural(user.student.parents.length),
description: user.student.parents.join(", ")), description: user.student.parents.join(", ")),
if (user.student.gradeDelay > 0)
Detail(
title: "grade_delay".i18n,
description: "hrs".i18n.fill([user.student.gradeDelay]),
),
// if ((user.student.bankAccount ?? "").isNotEmpty)
// Detail(
// title: "bank_account".i18n,
// description: (user.student.bankAccount ?? "not_provided".i18n),
// ),
const SizedBox( const SizedBox(
height: 10.0, height: 10.0,
), ),

View File

@@ -10,6 +10,8 @@ extension Localization on String {
"address": "Home address", "address": "Home address",
"parents": "Parent(s)", "parents": "Parent(s)",
"parents_phone": "Parents' phone number: ", "parents_phone": "Parents' phone number: ",
"grade_delay": "Grade visibility delay",
"hrs": "%s hour(s)",
}, },
"hu_hu": { "hu_hu": {
"birthdate": "Születési dátum", "birthdate": "Születési dátum",
@@ -17,6 +19,8 @@ extension Localization on String {
"class": "Osztály", "class": "Osztály",
"address": "Lakcím", "address": "Lakcím",
"parents": "Szülő(k)", "parents": "Szülő(k)",
"grade_delay": "Jegy megjelenítési késleltetés",
"hrs": "%s óra",
}, },
"de_de": { "de_de": {
"birthdate": "Geburtsdatum", "birthdate": "Geburtsdatum",
@@ -24,6 +28,8 @@ extension Localization on String {
"class": "Klasse", "class": "Klasse",
"address": "Wohnanschrift", "address": "Wohnanschrift",
"parents": "Elter(n)", "parents": "Elter(n)",
"grade_delay": "Notenverzögerung",
"hrs": "%s Stunde(n)",
}, },
}; };

View File

@@ -42,6 +42,7 @@ import 'package:refilc_mobile_ui/screens/settings/accounts/account_view.dart';
import 'package:refilc_mobile_ui/screens/settings/notifications_screen.dart'; import 'package:refilc_mobile_ui/screens/settings/notifications_screen.dart';
import 'package:refilc_mobile_ui/screens/settings/privacy_view.dart'; import 'package:refilc_mobile_ui/screens/settings/privacy_view.dart';
import 'package:refilc_mobile_ui/screens/settings/settings_helper.dart'; import 'package:refilc_mobile_ui/screens/settings/settings_helper.dart';
import 'package:refilc_mobile_ui/screens/settings/submenu/code_scanner.dart';
import 'package:refilc_mobile_ui/screens/settings/submenu/extras_screen.dart'; import 'package:refilc_mobile_ui/screens/settings/submenu/extras_screen.dart';
import 'package:refilc_mobile_ui/screens/settings/submenu/personalize_screen.dart'; import 'package:refilc_mobile_ui/screens/settings/submenu/personalize_screen.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@@ -67,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_plus/ui/mobile/settings/welcome_message.dart';
// import 'package:refilc_mobile_ui/screens/error_screen.dart'; // import 'package:refilc_mobile_ui/screens/error_screen.dart';
import 'package:refilc_mobile_ui/screens/error_report_screen.dart'; import 'package:refilc_mobile_ui/screens/error_report_screen.dart';
import 'submenu/cloud_sync_screen.dart';
import 'submenu/general_screen.dart'; import 'submenu/general_screen.dart';
import 'package:refilc_plus/ui/mobile/plus/settings_inline.dart'; import 'package:refilc_plus/ui/mobile/plus/settings_inline.dart';
@@ -427,6 +429,13 @@ class SettingsScreenState extends State<SettingsScreen>
borderRadius: const BorderRadius.vertical( borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0), bottom: Radius.circular(4.0)), 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) // open dcs (digital collaboration space)
PanelButton( PanelButton(
onPressed: () => _openDKT(user.user!), onPressed: () => _openDKT(user.user!),
@@ -782,10 +791,14 @@ class SettingsScreenState extends State<SettingsScreen>
color: AppColors.of(context).text.withOpacity(0.75), color: AppColors.of(context).text.withOpacity(0.75),
), ),
), ),
leading: const Text( leading: Image.asset(
"🔥", 'assets/images/apple_fire_emoji.png',
style: TextStyle(fontSize: 22.0), width: 24.0,
), ),
// leading: const Text(
// "🔥",
// style: TextStyle(fontSize: 22.0),
// ),
trailing: Text( trailing: Text(
"${user.gradeStreak}", "${user.gradeStreak}",
style: TextStyle( style: TextStyle(
@@ -1008,14 +1021,15 @@ class SettingsScreenState extends State<SettingsScreen>
children: [ children: [
PanelButton( PanelButton(
leading: Icon( leading: Icon(
FeatherIcons.map, Icons.qr_code,
size: 22.0, size: 22.0,
color: AppColors.of(context).text.withOpacity(0.95), color: AppColors.of(context).text.withOpacity(0.95),
), ),
title: Text("stickermap".i18n), title: Text("qr_scanner".i18n),
onPressed: () => launchUrl( onPressed: () => Navigator.of(context).push(
Uri.parse("https://stickermap.refilc.hu"), MaterialPageRoute(
mode: LaunchMode.inAppBrowserView, builder: (context) => const CodeScannerScreen(),
),
), ),
borderRadius: const BorderRadius.vertical( borderRadius: const BorderRadius.vertical(
top: Radius.circular(12.0), top: Radius.circular(12.0),
@@ -1030,6 +1044,22 @@ class SettingsScreenState extends State<SettingsScreen>
), ),
title: Text("news".i18n), title: Text("news".i18n),
onPressed: () => _openNews(context), onPressed: () => _openNews(context),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(4.0),
bottom: Radius.circular(4.0),
),
),
PanelButton(
leading: Icon(
FeatherIcons.map,
size: 22.0,
color: AppColors.of(context).text.withOpacity(0.95),
),
title: Text("stickermap".i18n),
onPressed: () => launchUrl(
Uri.parse("https://stickermap.refilc.hu"),
mode: LaunchMode.inAppBrowserView,
),
borderRadius: const BorderRadius.vertical( borderRadius: const BorderRadius.vertical(
top: Radius.circular(4.0), top: Radius.circular(4.0),
bottom: Radius.circular(12.0), bottom: Radius.circular(12.0),
@@ -1173,7 +1203,7 @@ class SettingsScreenState extends State<SettingsScreen>
secondary: Icon( secondary: Icon(
FeatherIcons.barChart2, FeatherIcons.barChart2,
size: 22.0, size: 22.0,
color: settings.xFilcId != "none" color: settings.analyticsEnabled
? AppColors.of(context).text.withOpacity(0.95) ? AppColors.of(context).text.withOpacity(0.95)
: AppColors.of(context).text.withOpacity(.25), : AppColors.of(context).text.withOpacity(.25),
), ),
@@ -1183,28 +1213,29 @@ class SettingsScreenState extends State<SettingsScreen>
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
fontSize: 16.0, fontSize: 16.0,
color: AppColors.of(context).text.withOpacity( color: AppColors.of(context).text.withOpacity(
settings.xFilcId != "none" ? 1.0 : .5), settings.analyticsEnabled ? 1.0 : .5),
), ),
), ),
subtitle: Text( subtitle: Text(
"Anonymous Usage Analytics".i18n, "Anonymous Usage Analytics".i18n,
style: TextStyle( style: TextStyle(
color: AppColors.of(context).text.withOpacity( color: AppColors.of(context)
settings.xFilcId != "none" ? .5 : .2), .text
.withOpacity(settings.analyticsEnabled ? .5 : .2),
), ),
), ),
onChanged: (v) { onChanged: (v) {
String newId; // String newId;
if (v == false) { // if (v == false) {
newId = "none"; // newId = "none";
} else if (settings.xFilcId == "none") { // } else if (settings.xFilcId == "none") {
newId = SettingsProvider.defaultSettings().xFilcId; // newId = SettingsProvider.defaultSettings().xFilcId;
} else { // } else {
newId = settings.xFilcId; // newId = settings.xFilcId;
} // }
settings.update(xFilcId: newId); settings.update(analyticsEnabled: v);
}, },
value: settings.xFilcId != "none", value: settings.analyticsEnabled,
activeColor: Theme.of(context).colorScheme.secondary, activeColor: Theme.of(context).colorScheme.secondary,
), ),
), ),

View File

@@ -131,6 +131,11 @@ extension SettingsLocalization on String {
"feedback": "Feedback", "feedback": "Feedback",
"other": "Other", "other": "Other",
"stickermap": "Sticker Map", "stickermap": "Sticker Map",
"qr_scanner": "QR Scanner",
"camera_perm_error":
"Camera permission is required to scan QR codes.",
"invalid_qr_code": "Invalid QR code!",
"success": "Success!",
}, },
"hu_hu": { "hu_hu": {
"heads_up": "Figyelem!", "heads_up": "Figyelem!",
@@ -260,6 +265,11 @@ extension SettingsLocalization on String {
"feedback": "Visszajelzés", "feedback": "Visszajelzés",
"other": "Egyéb", "other": "Egyéb",
"stickermap": "Matrica térkép", "stickermap": "Matrica térkép",
"qr_scanner": "QR Kódolvasó",
"camera_perm_error":
"A kamera engedély szükséges a QR kódok beolvasásához.",
"invalid_qr_code": "Érvénytelen QR kód!",
"success": "Siker!",
}, },
"de_de": { "de_de": {
"heads_up": "Achtung!", "heads_up": "Achtung!",
@@ -389,6 +399,11 @@ extension SettingsLocalization on String {
"feedback": "Feedback", "feedback": "Feedback",
"other": "Sonstiges", "other": "Sonstiges",
"stickermap": "Sticker Map", "stickermap": "Sticker Map",
"qr_scanner": "QR-Scanner",
"camera_perm_error":
"Kameraberechtigung ist erforderlich, um QR-Codes zu scannen.",
"invalid_qr_code": "Ungültiger QR-Code!",
"success": "Erfolg!",
}, },
}; };

View File

@@ -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),
),
),
],
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,172 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart';
import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc_mobile_ui/common/custom_snack_bar.dart';
import 'package:refilc_mobile_ui/screens/settings/settings_screen.i18n.dart';
import 'package:url_launcher/url_launcher.dart';
class CodeScannerScreen extends StatefulWidget {
const CodeScannerScreen({super.key});
@override
State<StatefulWidget> createState() => _CodeScannerScreenState();
}
class _CodeScannerScreenState extends State<CodeScannerScreen> {
Barcode? result;
QRViewController? controller;
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
@override
void reassemble() {
super.reassemble();
if (Platform.isAndroid) {
controller!.pauseCamera();
}
controller!.resumeCamera();
}
// @override
// void initState() {
// super.initState();
// controller!.resumeCamera();
// }
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.transparent,
title: Text('qr_scanner'.i18n),
leading: const BackButton(),
actions: [
IconButton(
icon: FutureBuilder(
future: controller?.getFlashStatus(),
builder: (context, snapshot) {
return Icon(
snapshot.data == true
? FeatherIcons.zapOff
: FeatherIcons.zap,
);
},
),
onPressed: () async {
await controller?.toggleFlash();
setState(() {});
},
),
],
),
body: _buildQrView(context),
);
}
Widget _buildQrView(BuildContext context) {
var scanArea = (MediaQuery.of(context).size.width < 400 ||
MediaQuery.of(context).size.height < 400)
? 250.0
: 280.0;
return QRView(
key: qrKey,
onQRViewCreated: _onQRViewCreated,
overlay: QrScannerOverlayShape(
borderColor: Theme.of(context).primaryColor,
borderRadius: 10,
borderLength: 30,
borderWidth: 10,
cutOutSize: scanArea,
),
onPermissionSet: (ctrl, p) => _onPermissionSet(context, ctrl, p),
);
}
void _onQRViewCreated(QRViewController controller) {
setState(() {
this.controller = controller;
});
controller.scannedDataStream.listen((scanData) {
// controller.pauseCamera();
if (result?.code == scanData.code) return;
setState(() {
result = scanData;
});
if (scanData.code != null) {
if (scanData.code!.startsWith('qw://')) {
// String data = scanData.code!.replaceFirst('qw://', '');
// check the qr id from api
// TODO: this qr shit
} else if (scanData.code!.startsWith('https://') ||
scanData.code!.startsWith('http://')) {
Uri uri =
Uri.parse(scanData.code!.replaceFirst('http://', 'https://'));
// print(uri);
if (uri.host.contains('refilc.hu') ||
uri.host.contains('refilcapp.hu') ||
uri.host.contains('filc.one')) {
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
content: Text("success".i18n,
style: const TextStyle(color: Colors.white)),
backgroundColor: const Color(0xFF00A900),
context: context,
));
// launch refilc url
Future.delayed(const Duration(seconds: 1), () {
Navigator.of(context).pop();
launchUrl(uri, mode: LaunchMode.inAppBrowserView);
});
} else {
// show invalid code error
// Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
content: Text("invalid_qr_code".i18n,
style: const TextStyle(color: Colors.white)),
backgroundColor: AppColors.of(context).red,
context: context,
));
controller.resumeCamera();
}
} else {
// show invalid code error
// Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
content: Text("invalid_qr_code".i18n,
style: const TextStyle(color: Colors.white)),
backgroundColor: AppColors.of(context).red,
context: context,
));
controller.resumeCamera();
}
}
});
}
void _onPermissionSet(BuildContext context, QRViewController ctrl, bool p) {
if (!p) {
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
content: Text("camera_perm_error".i18n,
style: const TextStyle(color: Colors.white)),
backgroundColor: AppColors.of(context).red,
context: context,
));
}
}
@override
void dispose() {
controller?.dispose();
super.dispose();
}
}

View File

@@ -4,9 +4,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart'; import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
// import 'package:refilc/models/settings.dart'; // import 'package:refilc/models/settings.dart';
import 'package:refilc/models/shared_theme.dart'; import 'package:refilc/theme/colors/colors.dart';
import 'package:refilc_kreta_api/providers/share_provider.dart'; import 'package:refilc_kreta_api/providers/share_provider.dart';
import 'package:refilc_mobile_ui/common/action_button.dart'; import 'package:refilc_mobile_ui/common/action_button.dart';
import 'package:refilc_mobile_ui/common/custom_snack_bar.dart';
import 'package:refilc_mobile_ui/common/splitted_panel/splitted_panel.dart'; import 'package:refilc_mobile_ui/common/splitted_panel/splitted_panel.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import 'submenu_screen.i18n.dart'; import 'submenu_screen.i18n.dart';
@@ -131,15 +132,59 @@ class ShareThemeDialogState extends State<ShareThemeDialog> {
), ),
onPressed: () async { onPressed: () async {
// share the fucking theme // share the fucking theme
SharedGradeColors gradeColors = var (gradeColors, gradeColorsStatus) =
await shareProvider.shareCurrentGradeColors(context); await shareProvider.shareCurrentGradeColors(context);
SharedTheme theme = await shareProvider.shareCurrentTheme(
if (gradeColorsStatus == 429) {
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
content: Text("theme_share_ratelimit".i18n,
style: const TextStyle(color: Colors.white)),
backgroundColor: AppColors.of(context).red,
context: context,
));
return;
} else if (gradeColorsStatus != 201) {
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
content: Text("theme_share_failed".i18n,
style: const TextStyle(color: Colors.white)),
backgroundColor: AppColors.of(context).red,
context: context,
));
return;
}
var (theme, themeStatus) = await shareProvider.shareCurrentTheme(
context, context,
gradeColors: gradeColors, gradeColors: gradeColors!,
isPublic: isPublic, isPublic: isPublic,
displayName: _title.text, displayName: _title.text,
); );
if (themeStatus == 429) {
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
content: Text("theme_share_ratelimit".i18n,
style: const TextStyle(color: Colors.white)),
backgroundColor: AppColors.of(context).red,
context: context,
));
return;
} else if (themeStatus != 201) {
ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar(
content: Text("theme_share_failed".i18n,
style: const TextStyle(color: Colors.white)),
backgroundColor: AppColors.of(context).red,
context: context,
));
return;
}
// print(theme);
// print(themeStatus);
// save theme id in settings // save theme id in settings
// Provider.of<SettingsProvider>(context, listen: false) // Provider.of<SettingsProvider>(context, listen: false)
// .update(currentThemeId: theme.id); // .update(currentThemeId: theme.id);
@@ -149,7 +194,7 @@ class ShareThemeDialogState extends State<ShareThemeDialog> {
// show the share popup // show the share popup
Share.share( Share.share(
theme.id, theme!.id,
subject: 'share_subj_theme'.i18n, subject: 'share_subj_theme'.i18n,
); );
}, },

View File

@@ -30,6 +30,10 @@ extension SettingsLocalization on String {
"share_disclaimer": "share_disclaimer":
"By sharing the theme, you agree that the nickname you set and all settings of the theme will be shared publicly.", "By sharing the theme, you agree that the nickname you set and all settings of the theme will be shared publicly.",
"understand": "I understand", "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": { "hu_hu": {
"general": "Általános", "general": "Általános",
@@ -58,6 +62,10 @@ extension SettingsLocalization on String {
"share_disclaimer": "share_disclaimer":
"A téma megosztásával elfogadod, hogy az általad beállított becenév és a téma minden beállítása nyilvánosan megosztásra kerüljön.", "A téma megosztásával elfogadod, hogy az általad beállított becenév és a téma minden beállítása nyilvánosan megosztásra kerüljön.",
"understand": "Értem", "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": { "de_de": {
"general": "Allgemeine", "general": "Allgemeine",
@@ -86,6 +94,11 @@ extension SettingsLocalization on String {
"share_disclaimer": "share_disclaimer":
"Durch das Teilen des Themes erklären Sie sich damit einverstanden, dass der von Ihnen festgelegte Spitzname und alle Einstellungen des Themes öffentlich geteilt werden.", "Durch das Teilen des Themes erklären Sie sich damit einverstanden, dass der von Ihnen festgelegte Spitzname und alle Einstellungen des Themes öffentlich geteilt werden.",
"understand": "Ich verstehe", "understand": "Ich verstehe",
"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",
}, },
}; };

View File

@@ -2,7 +2,7 @@ name: refilc_mobile_ui
publish_to: "none" publish_to: "none"
environment: environment:
sdk: ">=3.3.2 <=3.4.3" sdk: ">=3.3.2 <=3.6.0"
dependencies: dependencies:
flutter: flutter:
@@ -31,7 +31,7 @@ dependencies:
animations: ^2.0.11 animations: ^2.0.11
animated_list_plus: ^0.5.0 animated_list_plus: ^0.5.0
confetti: ^0.7.0 confetti: ^0.7.0
live_activities: ^1.9.1+1 # live_activities: ^1.9.1+1
animated_flip_counter: ^0.3.4 animated_flip_counter: ^0.3.4
lottie: ^3.1.0 lottie: ^3.1.0
rive: ^0.12.4 rive: ^0.12.4
@@ -51,14 +51,14 @@ dependencies:
rounded_expansion_tile: rounded_expansion_tile:
git: git:
url: https://github.com/kimaah/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 flutter_expandable_fab: ^2.0.0
intl: ^0.19.0 intl: ^0.19.0
i18n_extension: ^12.0.1 i18n_extension: ^12.0.1
auto_size_text: ^3.0.0 auto_size_text: ^3.0.0
connectivity_plus: ^6.0.3 connectivity_plus: ^6.0.3
collection: ^1.18.0 collection: ^1.18.0
share_plus: ^9.0.0 share_plus: ^10.0.3
image_picker: ^1.0.7 image_picker: ^1.0.7
path_provider: ^2.1.2 path_provider: ^2.1.2
image_crop: image_crop:
@@ -67,16 +67,17 @@ dependencies:
uuid: ^4.3.3 uuid: ^4.3.3
maps_launcher: ^2.2.0 maps_launcher: ^2.2.0
google_fonts: ^6.1.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 custom_sliding_segmented_control: ^1.8.1
get_it: ^7.6.7 get_it: ^7.6.7
xml: ^6.5.0 xml: ^6.5.0
markdown: ^7.2.2 markdown: ^7.2.2
carousel_slider: ^4.2.1 carousel_slider: ^5.0.0
flutter_portal: ^1.1.4 flutter_portal: ^1.1.4
webview_flutter: ^4.8.0 webview_flutter: ^4.8.0
file_picker: ^8.0.5 file_picker: ^8.0.5
shake_flutter: ^17.0.0 shake_flutter: ^17.0.0
qr_code_scanner_plus: ^2.0.6
dev_dependencies: dev_dependencies:
flutter_lints: ^4.0.0 flutter_lints: ^4.0.0