diff --git a/.gitignore b/.gitignore index e64b91c..d3a8c2a 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,6 @@ yarn-error.log # Expo .expo/* + +ios/JecnaapiIOS.xcframework/ +ios/framework.zip diff --git a/android/build.gradle b/android/build.gradle index 6286cff..30cd7ff 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.library' id 'expo-module-gradle-plugin' + id 'org.jetbrains.kotlin.android' } group = 'cz.jzitnik.jecnaapireactnative' @@ -8,11 +9,29 @@ version = '0.1.0' android { namespace "cz.jzitnik.jecnaapireactnative" + compileSdk = 36 + defaultConfig { versionCode 1 versionName "0.1.0" + minSdk = 26 } lintOptions { abortOnError false } + kotlinOptions { + freeCompilerArgs += [ + '-Xskip-metadata-version-check' + ] + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation "io.github.tomhula:jecnaapi-jecna:10.3.5" + implementation "io.github.tomhula:jecnaapi-canteen:10.3.5" + implementation "com.google.code.gson:gson:2.14.0" } diff --git a/android/src/main/java/cz/jzitnik/jecnaapireactnative/JecnaapiReactNativeModule.kt b/android/src/main/java/cz/jzitnik/jecnaapireactnative/JecnaapiReactNativeModule.kt index 1d7f7bb..0474ec6 100644 --- a/android/src/main/java/cz/jzitnik/jecnaapireactnative/JecnaapiReactNativeModule.kt +++ b/android/src/main/java/cz/jzitnik/jecnaapireactnative/JecnaapiReactNativeModule.kt @@ -1,10 +1,222 @@ package cz.jzitnik.jecnaapireactnative +import com.google.gson.Gson +import expo.modules.kotlin.functions.Coroutine import expo.modules.kotlin.modules.Module import expo.modules.kotlin.modules.ModuleDefinition +import io.github.tomhula.jecnaapi.CanteenClient +import io.github.tomhula.jecnaapi.JecnaClient +import io.github.tomhula.jecnaapi.data.canteen.ExchangeItem +import io.github.tomhula.jecnaapi.data.canteen.MenuItem +import io.github.tomhula.jecnaapi.data.notification.NotificationReference +import io.github.tomhula.jecnaapi.data.notification.NotificationReference.NotificationType +import io.github.tomhula.jecnaapi.util.SchoolYear +import io.github.tomhula.jecnaapi.util.SchoolYearHalf +import kotlinx.datetime.LocalDate +import kotlinx.datetime.Month class JecnaapiReactNativeModule : Module() { - override fun definition() = ModuleDefinition { - Name("JecnaapiReactNative") - } + private var client: JecnaClient = JecnaClient(autoLogin = true) + private var canteenClient: CanteenClient = CanteenClient(autoLogin = true) + private val gson = Gson() + + override fun definition() = ModuleDefinition { + Name("JecnaapiReactNative") + + // ========================================== + // JecnaClient Functions + // ========================================== + + AsyncFunction("login") Coroutine { username: String, pass: String -> + return@Coroutine client.login(username, pass) + } + + AsyncFunction("logout") Coroutine { -> + client.logout() + } + + AsyncFunction("isLoggedIn") Coroutine { -> + return@Coroutine client.isLoggedIn() + } + + AsyncFunction("getNewsPage") Coroutine { -> + val news = client.getNewsPage() + return@Coroutine gson.toJson(news) + } + + AsyncFunction("getGradesPage") Coroutine { -> + val grades = client.getGradesPage() + return@Coroutine gson.toJson(grades) + } + + AsyncFunction("getGradesPageByYear") Coroutine { firstCalendarYear: Int, half: String -> + val schoolYear = SchoolYear(firstCalendarYear) + val schoolYearHalf = SchoolYearHalf.valueOf(half.uppercase()) + val grades = client.getGradesPage(schoolYear, schoolYearHalf) + return@Coroutine gson.toJson(grades) + } + + AsyncFunction("getTimetablePage") Coroutine { -> + val timetable = client.getTimetablePage() + return@Coroutine gson.toJson(timetable) + } + + AsyncFunction("getTimetablePageByYear") Coroutine { firstCalendarYear: Int, periodId: Int? -> + val schoolYear = SchoolYear(firstCalendarYear) + val timetable = client.getTimetablePage(schoolYear, periodId) + return@Coroutine gson.toJson(timetable) + } + + AsyncFunction("getAttendancesPage") Coroutine { -> + val attendance = client.getAttendancesPage() + return@Coroutine gson.toJson(attendance) + } + + AsyncFunction("getAttendancesPageByMonth") Coroutine { firstCalendarYear: Int, monthName: String -> + val schoolYear = SchoolYear(firstCalendarYear) + val month = Month.valueOf(monthName.uppercase()) + val attendance = client.getAttendancesPage(schoolYear, month) + return@Coroutine gson.toJson(attendance) + } + + AsyncFunction("getAbsencesPage") Coroutine { -> + val absences = client.getAbsencesPage() + return@Coroutine gson.toJson(absences) + } + + AsyncFunction("getAbsencesPageByYear") Coroutine { firstCalendarYear: Int -> + val schoolYear = SchoolYear(firstCalendarYear) + val absences = client.getAbsencesPage(schoolYear) + return@Coroutine gson.toJson(absences) + } + + AsyncFunction("getTeachersPage") Coroutine { -> + val teachers = client.getTeachersPage() + return@Coroutine gson.toJson(teachers) + } + + AsyncFunction("getTeacher") Coroutine { teacherTag: String -> + val teacher = client.getTeacher(teacherTag) + return@Coroutine gson.toJson(teacher) + } + + AsyncFunction("getRoomsPage") Coroutine { -> + val rooms = client.getRoomsPage() + return@Coroutine gson.toJson(rooms) + } + + AsyncFunction("getRoom") Coroutine { roomCode: String -> + val room = client.getRoom(roomCode) + return@Coroutine gson.toJson(room) + } + + AsyncFunction("getLocker") Coroutine { -> + val locker = client.getLocker() + return@Coroutine gson.toJson(locker) + } + + AsyncFunction("getStudentProfile") Coroutine { -> + val profile = client.getStudentProfile() + return@Coroutine gson.toJson(profile) + } + + AsyncFunction("getStudentProfileByUsername") Coroutine { username: String -> + val profile = client.getStudentProfile(username) + return@Coroutine gson.toJson(profile) + } + + AsyncFunction("getNotifications") Coroutine { -> + val notifications = client.getNotifications() + return@Coroutine gson.toJson(notifications) + } + + AsyncFunction("getNotification") Coroutine { type: String, message: String, recordId: Int -> + val notificationType = NotificationType.valueOf(type.uppercase()) + val notificationRef = NotificationReference(notificationType, message, recordId) + val notification = client.getNotification(notificationRef) + return@Coroutine gson.toJson(notification) + } + + AsyncFunction("getStudentCertificates") Coroutine { -> + val certificates = client.getStudentCertificates() + return@Coroutine gson.toJson(certificates) + } + + AsyncFunction("getDocumentsPage") Coroutine { path: String -> + val documents = client.getDocumentsPage(path) + return@Coroutine gson.toJson(documents) + } + + AsyncFunction("getDocumentsPageDefault") Coroutine { -> + val documents = client.getDocumentsPage() + return@Coroutine gson.toJson(documents) + } + + // ========================================== + // CanteenClient Functions + // ========================================== + + AsyncFunction("canteenLogin") Coroutine { username: String, pass: String -> + return@Coroutine canteenClient.login(username, pass) + } + + AsyncFunction("canteenLogout") Coroutine { -> + canteenClient.logout() + } + + AsyncFunction("canteenIsLoggedIn") Coroutine { -> + return@Coroutine canteenClient.isLoggedIn() + } + + AsyncFunction("canteenGetMenuPage") Coroutine { -> + val menuPage = canteenClient.getMenuPage() + return@Coroutine gson.toJson(menuPage) + } + + AsyncFunction("canteenGetMenuAsync") Coroutine { daysStrings: List -> + val localDates = daysStrings.map { LocalDate.parse(it) } + val dayMenusList = mutableListOf() + canteenClient.getMenuAsync(localDates).collect { dayMenu -> + dayMenusList.add(dayMenu) + } + return@Coroutine gson.toJson(dayMenusList) + } + + AsyncFunction("canteenGetDayMenu") Coroutine { dayString: String -> + val day = LocalDate.parse(dayString) + val dayMenu = canteenClient.getDayMenu(day) + return@Coroutine gson.toJson(dayMenu) + } + + AsyncFunction("canteenGetExchange") Coroutine { -> + val exchange = canteenClient.getExchange() + return@Coroutine gson.toJson(exchange) + } + + AsyncFunction("canteenGetCredit") Coroutine { -> + return@Coroutine canteenClient.getCredit() + } + + AsyncFunction("canteenOrderMenuItem") Coroutine { menuItemJson: String -> + val menuItem = gson.fromJson(menuItemJson, MenuItem::class.java) + return@Coroutine canteenClient.order(menuItem) + } + + AsyncFunction("canteenOrderExchangeItem") Coroutine { number: Int, orderPath: String, dayString: String, amount: Int -> + val day = LocalDate.parse(dayString) + val exchangeItem = ExchangeItem( + number = number, + description = null, + amount = amount, + orderPath = orderPath, + day = day + ) + return@Coroutine canteenClient.order(exchangeItem) + } + + AsyncFunction("canteenPutOnExchange") Coroutine { menuItemJson: String -> + val menuItem = gson.fromJson(menuItemJson, MenuItem::class.java) + return@Coroutine canteenClient.putOnExchange(menuItem) + } + } } diff --git a/example/App.tsx b/example/App.tsx index bf7895d..3e90e7d 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -1,28 +1,109 @@ -import { Button, SafeAreaView, ScrollView, Text, View } from 'react-native'; +import { useState } from 'react'; +import { StyleSheet, Text, View, TextInput, Button, ScrollView, ActivityIndicator } from 'react-native'; +import JecnaapiReactNative from 'jecnaapi-react-native'; export default function App() { - return ( - - - Module API Example - - - ); -} + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [loading, setLoading] = useState(false); + const [result, setResult] = useState('No data yet. Please login.'); + + const handleTestLoginAndFetch = async () => { + if (!username || !password) { + setResult('Error: Enter username and password'); + return; + } + + setLoading(true); + setResult('Logging in...'); + + try { + // 1. Call the raw native Kotlin bridge + const loginSuccess = await JecnaapiReactNative.login(username, password); + + if (loginSuccess) { + setResult('Login Successful! Fetching profile...'); + + // 2. Fetch the raw JSON string + const rawProfileString = await JecnaapiReactNative.getStudentProfile(); + + // 3. Manually parse it (since you kept the default export) + const profile = JSON.parse(rawProfileString); + + // Display it nicely on screen + setResult(JSON.stringify(profile, null, 2)); + } else { + setResult('Login failed. Check credentials.'); + } + } catch (error: any) { + setResult(`Bridge Error: ${error.message}`); + } finally { + setLoading(false); + } + }; -function Group(props: { name: string; children: React.ReactNode }) { return ( - - {props.name} - {props.children} + + JecnaAPI Native Tester + + + + +