├── src ├── app │ ├── app.component.scss │ ├── tabs │ │ ├── tabs.page.scss │ │ ├── tabs.module.ts │ │ ├── tabs.page.ts │ │ ├── tabs.page.html │ │ ├── tabs-routing.module.ts │ │ └── tabs.router.module.ts │ ├── pages │ │ ├── welcome │ │ │ ├── welcome.page.scss │ │ │ ├── welcome-routing.module.ts │ │ │ ├── welcome.page.ts │ │ │ ├── welcome.module.ts │ │ │ └── welcome.page.html │ │ └── universities │ │ │ ├── universities.page.scss │ │ │ ├── universities-routing.module.ts │ │ │ ├── universities.module.ts │ │ │ ├── universities.page.ts │ │ │ └── universities.page.html │ ├── app.component.html │ ├── shared │ │ ├── models │ │ │ ├── login-form.model.ts │ │ │ ├── course.model.ts │ │ │ ├── student.model.ts │ │ │ ├── info.model.ts │ │ │ ├── grades.model.ts │ │ │ └── semester.model.ts │ │ ├── services │ │ │ ├── env.service.ts │ │ │ ├── api.service.ts │ │ │ ├── update.service.ts │ │ │ ├── theme-mode.service.ts │ │ │ ├── permission.service.ts │ │ │ ├── university.service.ts │ │ │ ├── toast.service.ts │ │ │ ├── routing.service.ts │ │ │ ├── network.service.ts │ │ │ ├── student.service.ts │ │ │ ├── notification.service.ts │ │ │ └── storage.service.ts │ │ └── shared.module.ts │ ├── offline-login │ │ ├── offline-login.page.scss │ │ ├── offline-login.module.ts │ │ ├── offline-login.page.html │ │ └── offline-login.page.ts │ ├── modals │ │ ├── faq │ │ │ ├── faq.page.scss │ │ │ ├── faq-routing.module.ts │ │ │ ├── faq.page.ts │ │ │ ├── faq.module.ts │ │ │ └── faq.page.html │ │ ├── settings │ │ │ ├── settings.page.scss │ │ │ ├── settings-routing.module.ts │ │ │ ├── settings.module.ts │ │ │ ├── settings.page.html │ │ │ └── settings.page.ts │ │ └── about │ │ │ ├── about-routing.module.ts │ │ │ ├── about.module.ts │ │ │ ├── about.page.scss │ │ │ ├── about.page.ts │ │ │ └── about.page.html │ ├── app.scss │ ├── login │ │ ├── login-routing.module.ts │ │ ├── login.module.ts │ │ ├── login.page.scss │ │ └── login.page.html │ ├── tab3 │ │ ├── tab3.page.scss │ │ ├── tab3.module.ts │ │ ├── tab3.page.ts │ │ └── tab3.page.html │ ├── tab2 │ │ ├── tab2.module.ts │ │ ├── tab2.page.scss │ │ ├── tab2.page.ts │ │ └── tab2.page.html │ ├── tab1 │ │ ├── tab1.module.ts │ │ ├── tab1.page.scss │ │ └── tab1.page.html │ ├── guard │ │ └── auth.guard.ts │ ├── app-routing.module.ts │ ├── app.module.ts │ └── app.component.ts ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── assets │ ├── ekpa-logo.png │ ├── unipi-logo.png │ ├── uniwa-logo.png │ ├── icon │ │ └── favicon.png │ ├── panteion-logo.png │ └── logo.svg ├── zone-flags.ts ├── main.ts ├── test.ts ├── index.html ├── karma.conf.js ├── global.scss └── polyfills.ts ├── resources ├── icon.png ├── splash.png └── store icons │ └── app gallery logo.png ├── ionic.config.json ├── android ├── app │ ├── src │ │ ├── main │ │ │ ├── assets │ │ │ │ ├── panteion.crt │ │ │ │ └── capacitor.config.json │ │ │ ├── res │ │ │ │ ├── drawable │ │ │ │ │ └── splash.png │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ │ ├── ic_launcher_unistudents.png │ │ │ │ │ ├── ic_launcher_unistudents_round.png │ │ │ │ │ └── ic_launcher_unistudents_foreground.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ │ ├── ic_launcher_unistudents.png │ │ │ │ │ ├── ic_launcher_unistudents_round.png │ │ │ │ │ └── ic_launcher_unistudents_foreground.png │ │ │ │ ├── drawable-land-hdpi │ │ │ │ │ └── splash.png │ │ │ │ ├── drawable-land-mdpi │ │ │ │ │ └── splash.png │ │ │ │ ├── drawable-land-xhdpi │ │ │ │ │ └── splash.png │ │ │ │ ├── drawable-port-hdpi │ │ │ │ │ └── splash.png │ │ │ │ ├── drawable-port-mdpi │ │ │ │ │ └── splash.png │ │ │ │ ├── drawable-port-xhdpi │ │ │ │ │ └── splash.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ │ ├── ic_launcher_unistudents.png │ │ │ │ │ ├── ic_launcher_unistudents_round.png │ │ │ │ │ └── ic_launcher_unistudents_foreground.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ │ ├── ic_launcher_unistudents.png │ │ │ │ │ ├── ic_launcher_unistudents_round.png │ │ │ │ │ └── ic_launcher_unistudents_foreground.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_round.png │ │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ │ ├── ic_launcher_unistudents.png │ │ │ │ │ ├── ic_launcher_unistudents_round.png │ │ │ │ │ └── ic_launcher_unistudents_foreground.png │ │ │ │ ├── drawable-land-xxhdpi │ │ │ │ │ └── splash.png │ │ │ │ ├── drawable-land-xxxhdpi │ │ │ │ │ └── splash.png │ │ │ │ ├── drawable-port-xxhdpi │ │ │ │ │ └── splash.png │ │ │ │ ├── drawable-port-xxxhdpi │ │ │ │ │ └── splash.png │ │ │ │ ├── drawable-hdpi │ │ │ │ │ └── ic_notification.png │ │ │ │ ├── drawable-mdpi │ │ │ │ │ └── ic_notification.png │ │ │ │ ├── drawable-xhdpi │ │ │ │ │ └── ic_notification.png │ │ │ │ ├── drawable-xxhdpi │ │ │ │ │ └── ic_notification.png │ │ │ │ ├── values │ │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ │ ├── ic_launcher_unistudents_background.xml │ │ │ │ │ ├── strings.xml │ │ │ │ │ └── styles.xml │ │ │ │ ├── xml │ │ │ │ │ ├── config.xml │ │ │ │ │ ├── file_paths.xml │ │ │ │ │ └── network_security_config.xml │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ │ ├── ic_launcher.xml │ │ │ │ │ ├── ic_launcher_round.xml │ │ │ │ │ ├── ic_launcher_unistudents.xml │ │ │ │ │ └── ic_launcher_unistudents_round.xml │ │ │ │ ├── drawable-anydpi-v24 │ │ │ │ │ └── ic_notification.xml │ │ │ │ ├── layout │ │ │ │ │ └── activity_main.xml │ │ │ │ ├── drawable-v24 │ │ │ │ │ └── ic_launcher_foreground.xml │ │ │ │ └── raw │ │ │ │ │ └── panteion.pem │ │ │ ├── ic_launcher_unistudents-playstore.png │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── unipi │ │ │ │ │ └── students │ │ │ │ │ ├── common │ │ │ │ │ ├── StringHelper.java │ │ │ │ │ ├── HttpStatus.java │ │ │ │ │ └── UserAgentGenerator.java │ │ │ │ │ ├── model │ │ │ │ │ ├── ResponseEntity.java │ │ │ │ │ ├── Student.java │ │ │ │ │ ├── Course.java │ │ │ │ │ ├── Grades.java │ │ │ │ │ ├── Semester.java │ │ │ │ │ └── Info.java │ │ │ │ │ ├── plugin │ │ │ │ │ ├── PermissionPlugin.java │ │ │ │ │ ├── FCMPlugin.java │ │ │ │ │ └── ScrapePlugin.java │ │ │ │ │ ├── MainActivity.java │ │ │ │ │ ├── service │ │ │ │ │ └── StorageService.java │ │ │ │ │ └── worker │ │ │ │ │ └── ScrapeDataWorker.java │ │ │ └── AndroidManifest.xml │ │ ├── test │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── getcapacitor │ │ │ │ └── myapp │ │ │ │ └── ExampleUnitTest.java │ │ └── androidTest │ │ │ └── java │ │ │ └── com │ │ │ └── getcapacitor │ │ │ └── myapp │ │ │ └── ExampleInstrumentedTest.java │ ├── capacitor.build.gradle │ └── build.gradle ├── settings.gradle ├── capacitor.settings.gradle ├── variables.gradle ├── build.gradle └── gradle.properties ├── e2e ├── src │ ├── app.po.ts │ └── app.e2e-spec.ts ├── tsconfig.e2e.json └── protractor.conf.js ├── tsconfig.app.json ├── capacitor.config.json ├── tsconfig.spec.json ├── browserslist ├── .gitignore ├── tsconfig.json ├── karma.conf.js ├── LICENSE ├── tslint.json ├── package.json ├── README.md └── angular.json /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/tabs/tabs.page.scss: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/pages/welcome/welcome.page.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/resources/icon.png -------------------------------------------------------------------------------- /resources/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/resources/splash.png -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/assets/ekpa-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/src/assets/ekpa-logo.png -------------------------------------------------------------------------------- /src/assets/unipi-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/src/assets/unipi-logo.png -------------------------------------------------------------------------------- /src/assets/uniwa-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/src/assets/uniwa-logo.png -------------------------------------------------------------------------------- /src/assets/icon/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/src/assets/icon/favicon.png -------------------------------------------------------------------------------- /src/assets/panteion-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/src/assets/panteion-logo.png -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/app/shared/models/login-form.model.ts: -------------------------------------------------------------------------------- 1 | export class LoginForm { 2 | username: string; 3 | password: string; 4 | } 5 | -------------------------------------------------------------------------------- /ionic.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "integrations": { 4 | "capacitor": {} 5 | }, 6 | "type": "angular" 7 | } 8 | -------------------------------------------------------------------------------- /android/app/src/main/assets/panteion.crt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/assets/panteion.crt -------------------------------------------------------------------------------- /resources/store icons/app gallery logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/resources/store icons/app gallery logo.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/drawable/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/drawable-land-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/drawable-land-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/drawable-land-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/drawable-port-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/drawable-port-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/drawable-port-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/drawable-land-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/drawable-land-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/drawable-port-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/drawable-port-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/ic_launcher_unistudents-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/ic_launcher_unistudents-playstore.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/drawable-hdpi/ic_notification.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/drawable-mdpi/ic_notification.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/drawable-xhdpi/ic_notification.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_notification.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src/app/shared/models/course.model.ts: -------------------------------------------------------------------------------- 1 | export class Course { 2 | id: string; 3 | name: string; 4 | type: string; 5 | grade: string; 6 | examPeriod: string; 7 | } 8 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_unistudents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_unistudents.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_unistudents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_unistudents.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_unistudents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_unistudents.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /src/zone-flags.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Prevents Angular change detection from 3 | * running with certain Web Component callbacks 4 | */ 5 | (window as any).__Zone_disable_customElements = true; 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_unistudents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_unistudents.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_unistudents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_unistudents.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_unistudents_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_unistudents_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_unistudents_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_unistudents_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_unistudents_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_unistudents_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_unistudents_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_unistudents_round.png -------------------------------------------------------------------------------- /src/app/shared/models/student.model.ts: -------------------------------------------------------------------------------- 1 | import { Info } from './info.model'; 2 | import { Grades } from './grades.model'; 3 | 4 | export class Student { 5 | info: Info; 6 | grades: Grades; 7 | } 8 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_unistudents_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_unistudents_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_unistudents_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_unistudents_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_unistudents_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_unistudents_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_unistudents_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_unistudents_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_unistudents_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_unistudents_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/ic_launcher_unistudents_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_unistudents_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UniStudents/unistudents-app/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_unistudents_foreground.png -------------------------------------------------------------------------------- /src/app/shared/models/info.model.ts: -------------------------------------------------------------------------------- 1 | export class Info { 2 | aem: string; 3 | firstName: string; 4 | lastName: string; 5 | department: string; 6 | semester: string; 7 | registrationYear: string; 8 | } 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | include ':capacitor-cordova-android-plugins' 3 | project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/') 4 | 5 | apply from: 'capacitor.settings.gradle' -------------------------------------------------------------------------------- /src/app/shared/models/grades.model.ts: -------------------------------------------------------------------------------- 1 | import { Semester } from './semester.model'; 2 | 3 | export class Grades { 4 | totalPassedCourses: string; 5 | totalAverageGrade: string; 6 | totalEcts: string; 7 | semesters: Semester[]; 8 | } 9 | -------------------------------------------------------------------------------- /src/app/shared/models/semester.model.ts: -------------------------------------------------------------------------------- 1 | import { Course } from './course.model'; 2 | 3 | export class Semester { 4 | id: number; 5 | passedCourses: number; 6 | gradeAverage: string; 7 | ects: string; 8 | courses: Course[]; 9 | } 10 | -------------------------------------------------------------------------------- /src/app/offline-login/offline-login.page.scss: -------------------------------------------------------------------------------- 1 | .text-error-title { 2 | font-size: 20px; 3 | font-weight: bold; 4 | margin-top: 30px; 5 | } 6 | 7 | .text-error-subtitle { 8 | font-size: 15px; 9 | margin-bottom: 20px; 10 | margin-top: 20px; 11 | } -------------------------------------------------------------------------------- /android/app/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getPageTitle() { 9 | return element(by.css('ion-title')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/app/shared/services/env.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable({ 4 | providedIn: 'root' 5 | }) 6 | export class EnvService { 7 | 8 | API_URL = 'https://unistudents.herokuapp.com'; 9 | 10 | constructor() { } 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "include": [ 8 | "src/**/*.ts" 9 | ], 10 | "exclude": [ 11 | "src/test.ts", 12 | "src/**/*.spec.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/modals/faq/faq.page.scss: -------------------------------------------------------------------------------- 1 | ion-content { 2 | --padding-end: 20px; 3 | --padding-start: 20px; 4 | } 5 | 6 | 7 | .back-btn { 8 | text-transform: none; 9 | } 10 | 11 | h5 { 12 | margin-top: 30px; 13 | margin-bottom: 0; 14 | } 15 | 16 | p { 17 | margin-top: 10px; 18 | } 19 | -------------------------------------------------------------------------------- /src/app/modals/settings/settings.page.scss: -------------------------------------------------------------------------------- 1 | ion-list { 2 | background: none; 3 | } 4 | 5 | .back-btn { 6 | text-transform: none; 7 | } 8 | 9 | .ios .header { 10 | margin-bottom: 10px; 11 | } 12 | 13 | .beta { 14 | padding: 0 10px; 15 | color: #F96601; 16 | font-weight: 500; 17 | } 18 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /capacitor.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "com.unistudents.app.ios", 3 | "appName": "UniStudents", 4 | "bundledWebRuntime": false, 5 | "npmClient": "npm", 6 | "webDir": "www", 7 | "cordova": {}, 8 | "plugins": { 9 | "PushNotifications": { 10 | "presentationOptions": ["badge", "sound", "alert"] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | UniStudents 4 | UniStudents 5 | com.unistudents.app 6 | com.unistudents.app 7 | 8 | -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('new App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getPageTitle()).toContain('Tab One'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_unistudents.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/assets/capacitor.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "com.unistudents.app.ios", 3 | "appName": "UniStudents", 4 | "bundledWebRuntime": false, 5 | "npmClient": "npm", 6 | "webDir": "www", 7 | "cordova": {}, 8 | "plugins": { 9 | "PushNotifications": { 10 | "presentationOptions": ["badge", "sound", "alert"] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_unistudents_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/capacitor.settings.gradle: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN 2 | include ':capacitor-android' 3 | project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') 4 | 5 | include ':capacitor-community-fcm' 6 | project(':capacitor-community-fcm').projectDir = new File('../node_modules/@capacitor-community/fcm/android') 7 | -------------------------------------------------------------------------------- /src/app/app.scss: -------------------------------------------------------------------------------- 1 | // App Styles 2 | // ---------------------------------------------------------------------------- 3 | // Put style rules here that you want to apply to the entire application. These 4 | // styles are for the entire app and not just one component. Additionally, this 5 | // file can hold Sass mixins, functions, and placeholder classes to be imported 6 | // and used throughout the application. -------------------------------------------------------------------------------- /src/app/pages/universities/universities.page.scss: -------------------------------------------------------------------------------- 1 | ion-card { 2 | background: #F1F1FD; 3 | box-shadow: none; 4 | } 5 | 6 | .ios ion-card { 7 | margin-left: 5px; 8 | margin-right: 5px; 9 | } 10 | 11 | .uni-name { 12 | font-size: 5.5vw; 13 | margin: 0; 14 | text-overflow: ellipsis; 15 | white-space: nowrap; 16 | } 17 | 18 | ion-card:active { 19 | transform: scale(.95); 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/zone-flags.ts", 13 | "src/polyfills.ts" 14 | ], 15 | "include": [ 16 | "src/**/*.spec.ts", 17 | "src/**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/app/modals/faq/faq-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { FaqPage } from './faq.page'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: FaqPage 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule], 16 | }) 17 | export class FaqPageRoutingModule {} 18 | -------------------------------------------------------------------------------- /src/app/login/login-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { LoginPage } from './login.page'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: LoginPage 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule], 16 | }) 17 | export class LoginPageRoutingModule {} 18 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import 'hammerjs'; 2 | import { enableProdMode } from '@angular/core'; 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | 5 | import { AppModule } from './app/app.module'; 6 | import { environment } from './environments/environment'; 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic().bootstrapModule(AppModule) 13 | .catch(err => console.log(err)); 14 | -------------------------------------------------------------------------------- /src/app/modals/about/about-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { AboutPage } from './about.page'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: AboutPage 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule], 16 | }) 17 | export class AboutPageRoutingModule {} 18 | -------------------------------------------------------------------------------- /src/app/pages/welcome/welcome-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { WelcomePage } from './welcome.page'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: WelcomePage 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule], 16 | }) 17 | export class WelcomePageRoutingModule {} 18 | -------------------------------------------------------------------------------- /src/app/tab3/tab3.page.scss: -------------------------------------------------------------------------------- 1 | .info-grid { 2 | margin: 0 16px; 3 | padding: 0; 4 | } 5 | 6 | .info-row { 7 | margin: 12.5px 0 10px; 8 | } 9 | 10 | .info-col { 11 | padding: 0; 12 | } 13 | 14 | .info-card { 15 | margin: 0; 16 | height: 100%; 17 | } 18 | 19 | .icon { 20 | display: inline-block; 21 | font-size: 25px; 22 | vertical-align: middle; 23 | color: #657BFF; 24 | margin-right: 8px; 25 | } 26 | 27 | .margin { 28 | margin-left: 10px; 29 | } 30 | -------------------------------------------------------------------------------- /src/app/modals/settings/settings-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { SettingsPage } from './settings.page'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: SettingsPage 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule], 16 | }) 17 | export class SettingsPageRoutingModule {} 18 | -------------------------------------------------------------------------------- /browserslist: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. 13 | -------------------------------------------------------------------------------- /src/app/pages/universities/universities-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { UniversitiesPage } from './universities.page'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: UniversitiesPage 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule], 16 | }) 17 | export class UniversitiesPageRoutingModule {} 18 | -------------------------------------------------------------------------------- /android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.getcapacitor.myapp; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/modals/faq/faq.page.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ModalController } from '@ionic/angular'; 3 | 4 | @Component({ 5 | selector: 'app-faq', 6 | templateUrl: './faq.page.html', 7 | styleUrls: ['./faq.page.scss'], 8 | }) 9 | export class FaqPage implements OnInit { 10 | 11 | constructor( 12 | private modalController: ModalController 13 | ) { } 14 | 15 | ngOnInit() { 16 | } 17 | 18 | async closeModal() { 19 | await this.modalController.dismiss(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Specifies intentionally untracked files to ignore when using Git 2 | # http://git-scm.com/docs/gitignore 3 | 4 | *~ 5 | *.sw[mnpcod] 6 | .tmp 7 | *.tmp 8 | *.tmp.* 9 | *.sublime-project 10 | *.sublime-workspace 11 | .DS_Store 12 | Thumbs.db 13 | UserInterfaceState.xcuserstate 14 | $RECYCLE.BIN/ 15 | 16 | *.log 17 | log.txt 18 | npm-debug.log* 19 | 20 | /.idea 21 | /.ionic 22 | /.sass-cache 23 | /.sourcemaps 24 | /.versions 25 | /.vscode 26 | /coverage 27 | /dist 28 | /node_modules 29 | /platforms 30 | /plugins 31 | /www 32 | -------------------------------------------------------------------------------- /android/app/capacitor.build.gradle: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN 2 | 3 | android { 4 | compileOptions { 5 | sourceCompatibility JavaVersion.VERSION_1_8 6 | targetCompatibility JavaVersion.VERSION_1_8 7 | } 8 | } 9 | 10 | apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" 11 | dependencies { 12 | implementation project(':capacitor-community-fcm') 13 | 14 | } 15 | 16 | 17 | if (hasProperty('postBuildExtras')) { 18 | postBuildExtras() 19 | } 20 | -------------------------------------------------------------------------------- /src/app/tabs/tabs.module.ts: -------------------------------------------------------------------------------- 1 | import { IonicModule } from '@ionic/angular'; 2 | import { NgModule } from '@angular/core'; 3 | import { CommonModule } from '@angular/common'; 4 | import { FormsModule } from '@angular/forms'; 5 | 6 | import { TabsPageRoutingModule } from './tabs-routing.module'; 7 | 8 | import { TabsPage } from './tabs.page'; 9 | 10 | @NgModule({ 11 | imports: [ 12 | IonicModule, 13 | CommonModule, 14 | FormsModule, 15 | TabsPageRoutingModule 16 | ], 17 | declarations: [TabsPage] 18 | }) 19 | export class TabsPageModule {} 20 | -------------------------------------------------------------------------------- /src/app/modals/faq/faq.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { IonicModule } from '@ionic/angular'; 6 | 7 | import { FaqPageRoutingModule } from './faq-routing.module'; 8 | 9 | import { FaqPage } from './faq.page'; 10 | 11 | @NgModule({ 12 | imports: [ 13 | CommonModule, 14 | FormsModule, 15 | IonicModule, 16 | FaqPageRoutingModule 17 | ], 18 | declarations: [FaqPage] 19 | }) 20 | export class FaqPageModule {} 21 | -------------------------------------------------------------------------------- /src/app/tab2/tab2.module.ts: -------------------------------------------------------------------------------- 1 | import { IonicModule } from '@ionic/angular'; 2 | import { RouterModule } from '@angular/router'; 3 | import { NgModule } from '@angular/core'; 4 | import { CommonModule } from '@angular/common'; 5 | import { FormsModule } from '@angular/forms'; 6 | import { Tab2Page } from './tab2.page'; 7 | 8 | @NgModule({ 9 | imports: [ 10 | IonicModule, 11 | CommonModule, 12 | FormsModule, 13 | RouterModule.forChild([{ path: '', component: Tab2Page }]) 14 | ], 15 | declarations: [Tab2Page] 16 | }) 17 | export class Tab2PageModule {} 18 | -------------------------------------------------------------------------------- /src/app/tab3/tab3.module.ts: -------------------------------------------------------------------------------- 1 | import { IonicModule } from '@ionic/angular'; 2 | import { RouterModule } from '@angular/router'; 3 | import { NgModule } from '@angular/core'; 4 | import { CommonModule } from '@angular/common'; 5 | import { FormsModule } from '@angular/forms'; 6 | import { Tab3Page } from './tab3.page'; 7 | 8 | @NgModule({ 9 | imports: [ 10 | IonicModule, 11 | CommonModule, 12 | FormsModule, 13 | RouterModule.forChild([{ path: '', component: Tab3Page }]) 14 | ], 15 | declarations: [Tab3Page] 16 | }) 17 | export class Tab3PageModule {} 18 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/unipi/students/common/StringHelper.java: -------------------------------------------------------------------------------- 1 | package com.unipi.students.common; 2 | 3 | public class StringHelper { 4 | 5 | public static String removeTones(String string) { 6 | string = string.replace("Ά", "Α"); 7 | string = string.replace("Έ", "Ε"); 8 | string = string.replace("Ή", "Η"); 9 | string = string.replace("Ί", "Ι"); 10 | string = string.replace("Ό", "Ο"); 11 | string = string.replace("Ύ", "Υ"); 12 | string = string.replace("Ώ", "Ω"); 13 | return string; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/login/login.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { IonicModule } from '@ionic/angular'; 6 | 7 | import { LoginPageRoutingModule } from './login-routing.module'; 8 | 9 | import { LoginPage } from './login.page'; 10 | 11 | @NgModule({ 12 | imports: [ 13 | CommonModule, 14 | FormsModule, 15 | IonicModule, 16 | LoginPageRoutingModule 17 | ], 18 | declarations: [LoginPage] 19 | }) 20 | export class LoginPageModule {} 21 | -------------------------------------------------------------------------------- /src/app/modals/about/about.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { IonicModule } from '@ionic/angular'; 6 | 7 | import { AboutPageRoutingModule } from './about-routing.module'; 8 | 9 | import { AboutPage } from './about.page'; 10 | 11 | @NgModule({ 12 | imports: [ 13 | CommonModule, 14 | FormsModule, 15 | IonicModule, 16 | AboutPageRoutingModule 17 | ], 18 | declarations: [AboutPage] 19 | }) 20 | export class AboutPageModule {} 21 | -------------------------------------------------------------------------------- /src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { StorageService } from './services/storage.service'; 4 | import { NetworkService } from './services/network.service'; 5 | import { HttpClientModule } from '@angular/common/http'; 6 | 7 | @NgModule({ 8 | declarations: [], 9 | imports: [ 10 | CommonModule 11 | ], 12 | exports: [ 13 | HttpClientModule 14 | ], 15 | providers: [ 16 | NetworkService, 17 | StorageService 18 | ] 19 | }) 20 | export class SharedModule { } 21 | -------------------------------------------------------------------------------- /src/app/pages/welcome/welcome.page.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { RoutingService } from '../../shared/services/routing.service'; 3 | 4 | @Component({ 5 | selector: 'app-welcome', 6 | templateUrl: './welcome.page.html', 7 | styleUrls: ['./welcome.page.scss'], 8 | }) 9 | export class WelcomePage implements OnInit { 10 | 11 | constructor( 12 | private routingService: RoutingService 13 | ) { } 14 | 15 | ngOnInit() { 16 | } 17 | 18 | ionViewWillEnter() { 19 | this.routingService.currentPage = '/welcome'; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-anydpi-v24/ic_notification.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /android/variables.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | minSdkVersion = 21 3 | compileSdkVersion = 29 4 | targetSdkVersion = 29 5 | androidxAppCompatVersion = '1.1.0' 6 | androidxCoreVersion = '1.2.0' 7 | androidxMaterialVersion = '1.1.0-rc02' 8 | androidxBrowserVersion = '1.2.0' 9 | androidxLocalbroadcastmanagerVersion = '1.0.0' 10 | firebaseMessagingVersion = '20.1.2' 11 | playServicesLocationVersion = '17.0.0' 12 | junitVersion = '4.12' 13 | androidxJunitVersion = '1.1.1' 14 | androidxEspressoCoreVersion = '3.2.0' 15 | cordovaAndroidVersion = '7.0.0' 16 | } -------------------------------------------------------------------------------- /src/app/pages/welcome/welcome.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { IonicModule } from '@ionic/angular'; 6 | 7 | import { WelcomePageRoutingModule } from './welcome-routing.module'; 8 | 9 | import { WelcomePage } from './welcome.page'; 10 | 11 | @NgModule({ 12 | imports: [ 13 | CommonModule, 14 | FormsModule, 15 | IonicModule, 16 | WelcomePageRoutingModule 17 | ], 18 | declarations: [WelcomePage] 19 | }) 20 | export class WelcomePageModule {} 21 | -------------------------------------------------------------------------------- /src/app/modals/settings/settings.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { IonicModule } from '@ionic/angular'; 6 | 7 | import { SettingsPageRoutingModule } from './settings-routing.module'; 8 | 9 | import { SettingsPage } from './settings.page'; 10 | 11 | @NgModule({ 12 | imports: [ 13 | CommonModule, 14 | FormsModule, 15 | IonicModule, 16 | SettingsPageRoutingModule 17 | ], 18 | declarations: [SettingsPage] 19 | }) 20 | export class SettingsPageModule {} 21 | -------------------------------------------------------------------------------- /src/app/tab1/tab1.module.ts: -------------------------------------------------------------------------------- 1 | import { IonicModule } from '@ionic/angular'; 2 | import { RouterModule } from '@angular/router'; 3 | import { NgModule } from '@angular/core'; 4 | import { CommonModule } from '@angular/common'; 5 | import { FormsModule } from '@angular/forms'; 6 | import { Tab1Page } from './tab1.page'; 7 | 8 | @NgModule({ 9 | imports: [ 10 | IonicModule, 11 | CommonModule, 12 | FormsModule, 13 | RouterModule.forChild([{path: '', component: Tab1Page}]), 14 | ], 15 | declarations: [ 16 | Tab1Page 17 | ], 18 | providers: [] 19 | }) 20 | export class Tab1PageModule {} 21 | -------------------------------------------------------------------------------- /android/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /src/app/pages/universities/universities.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { IonicModule } from '@ionic/angular'; 6 | 7 | import { UniversitiesPageRoutingModule } from './universities-routing.module'; 8 | 9 | import { UniversitiesPage } from './universities.page'; 10 | 11 | @NgModule({ 12 | imports: [ 13 | CommonModule, 14 | FormsModule, 15 | IonicModule, 16 | UniversitiesPageRoutingModule 17 | ], 18 | declarations: [UniversitiesPage] 19 | }) 20 | export class UniversitiesPageModule {} 21 | -------------------------------------------------------------------------------- /src/app/shared/services/api.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { EnvService } from './env.service'; 4 | 5 | @Injectable({ 6 | providedIn: 'root' 7 | }) 8 | export class ApiService { 9 | 10 | fetchedData = false; 11 | 12 | constructor( 13 | private http: HttpClient, 14 | private env: EnvService, 15 | ) { } 16 | 17 | fetchStudent(university: string, username: string, password: string) { 18 | return this.http.post(this.env.API_URL + '/api/student/' + university, { 19 | username: username, 20 | password: password 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/app/tabs/tabs.page.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { StorageService } from '../shared/services/storage.service'; 3 | 4 | @Component({ 5 | selector: 'app-tabs', 6 | templateUrl: 'tabs.page.html', 7 | styleUrls: ['tabs.page.scss'] 8 | }) 9 | export class TabsPage { 10 | 11 | private newGrades; 12 | 13 | constructor( 14 | private storageService: StorageService 15 | ) {} 16 | 17 | ngOnInit(): void { 18 | this.newGrades = this.storageService.newGrades; 19 | } 20 | 21 | newGradesExist(): boolean { 22 | this.newGrades = this.storageService.newGrades; 23 | return this.newGrades > 0; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/app/tabs/tabs.page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Αρχική 7 | 8 | 9 | 10 | 11 | {{ newGrades }} 12 | Βαθμολογία 13 | 14 | 15 | 16 | 17 | Προφίλ 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "skipLibCheck": true, 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "sourceMap": true, 8 | "declaration": false, 9 | "module": "esnext", 10 | "moduleResolution": "node", 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "importHelpers": true, 14 | "target": "es2015", 15 | "typeRoots": [ 16 | "node_modules/@types" 17 | ], 18 | "lib": [ 19 | "es2018", 20 | "dom" 21 | ] 22 | }, 23 | "angularCompilerOptions": { 24 | "fullTemplateTypeCheck": true, 25 | "strictInjectionParameters": true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/unipi/students/model/ResponseEntity.java: -------------------------------------------------------------------------------- 1 | package com.unipi.students.model; 2 | 3 | import com.unipi.students.common.HttpStatus; 4 | 5 | public class ResponseEntity { 6 | 7 | private Object object; 8 | private HttpStatus httpStatus; 9 | 10 | public ResponseEntity(HttpStatus httpStatus) { 11 | this.httpStatus = httpStatus; 12 | } 13 | 14 | public ResponseEntity(Object object, HttpStatus httpStatus) { 15 | this.object = object; 16 | this.httpStatus = httpStatus; 17 | } 18 | 19 | public Object getObject() { 20 | return object; 21 | } 22 | 23 | public HttpStatus getHttpStatus() { 24 | return httpStatus; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/offline-login/offline-login.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { Routes, RouterModule } from '@angular/router'; 5 | 6 | import { IonicModule } from '@ionic/angular'; 7 | 8 | import { OfflineLoginPage } from './offline-login.page'; 9 | 10 | const routes: Routes = [ 11 | { 12 | path: '', 13 | component: OfflineLoginPage 14 | } 15 | ]; 16 | 17 | @NgModule({ 18 | imports: [ 19 | CommonModule, 20 | FormsModule, 21 | IonicModule, 22 | RouterModule.forChild(routes) 23 | ], 24 | declarations: [OfflineLoginPage] 25 | }) 26 | export class OfflineLoginPageModule {} 27 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /src/app/shared/services/update.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { StorageService } from './storage.service'; 3 | import { StoreService } from './store.service'; 4 | 5 | @Injectable({ 6 | providedIn: 'root' 7 | }) 8 | export class UpdateService { 9 | isFirstTime: boolean; 10 | 11 | constructor( 12 | private storageService: StorageService, 13 | private storeService: StoreService 14 | ) { } 15 | 16 | checkForUpdates() { 17 | return this.storageService.getFirstTime().then(firstTime => { 18 | if (firstTime.value === null) { 19 | this.storeService.logout(); 20 | this.isFirstTime = true; 21 | } else { 22 | this.isFirstTime = false; 23 | } 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.6.2' 11 | classpath 'com.google.gms:google-services:4.3.3' 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | apply from: "variables.gradle" 19 | 20 | allprojects { 21 | repositories { 22 | google() 23 | jcenter() 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /src/app/modals/about/about.page.scss: -------------------------------------------------------------------------------- 1 | .back-btn { 2 | text-transform: none; 3 | } 4 | 5 | h5 { 6 | font-weight: 300; 7 | } 8 | 9 | ion-icon { 10 | margin: 10px; 11 | height: 25px; 12 | width: 25px; 13 | } 14 | 15 | div.top { 16 | float: left; 17 | width: 100%; 18 | height: 65%; 19 | text-align: center; 20 | display: flex; 21 | flex-direction: column; 22 | justify-content: center; 23 | align-items: center; 24 | } 25 | 26 | div.bottom { 27 | float: left; 28 | width: 100%; 29 | height: 35%; 30 | text-align: center; 31 | display: flex; 32 | flex-direction: column; 33 | justify-content: center; 34 | align-items: center; 35 | border-top-style: dashed; 36 | border-top-color: #909090; 37 | border-top-width: 2px; 38 | } 39 | 40 | div.disclaimer { 41 | margin-top: 10px; 42 | } 43 | -------------------------------------------------------------------------------- /src/app/pages/welcome/welcome.page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 |
8 |

Καλωσόρισες στο
UniStudents

9 |

Ο καλύτερος τρόπος να παρακολουθείς τη πρόοδο σου. Έτοιμος;

10 | 13 |
14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/unipi/students/common/HttpStatus.java: -------------------------------------------------------------------------------- 1 | package com.unipi.students.common; 2 | 3 | public enum HttpStatus { 4 | OK(200, "OK"), 5 | UNAUTHORIZED(401, "Unauthorized"), 6 | NOT_FOUND(404, "Not Found"), 7 | REQUEST_TIMEOUT(408, "Request Timeout"), 8 | INTERNAL_SERVER_ERROR(500, "Internal Server Error"); 9 | 10 | private final int value; 11 | private final String reasonPhrase; 12 | 13 | private HttpStatus(int value, String reasonPhrase) { 14 | this.value = value; 15 | this.reasonPhrase = reasonPhrase; 16 | } 17 | 18 | public int value() { 19 | return this.value; 20 | } 21 | 22 | public String getReasonPhrase() { 23 | return this.reasonPhrase; 24 | } 25 | 26 | public String toString() { 27 | return this.value + " " + this.name(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/offline-login/offline-login.page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
Είσαι offline
7 |
Έλεγξε τη συνδεσή σου και δοκίμασε ξανά.
8 | Συνέχεια ως {{ userAem }} 9 | Δεν βρέθηκε λογαριασμός 10 |
11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /src/app/guard/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router'; 3 | import { Observable } from 'rxjs'; 4 | import { StudentService } from '../shared/services/student.service'; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class AuthGuard implements CanActivate { 10 | 11 | constructor( 12 | private router: Router, 13 | private studentService: StudentService 14 | ) {} 15 | 16 | canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean { 17 | 18 | const currentUser = this.studentService.isLoggedIn; 19 | 20 | if (currentUser) { 21 | return true; 22 | } else { 23 | this.router.navigate(['/login']); 24 | return false; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | # Supports AndroidX 20 | android.useAndroidX=true 21 | android.enableJetifier=true -------------------------------------------------------------------------------- /android/app/src/main/java/com/unipi/students/model/Student.java: -------------------------------------------------------------------------------- 1 | package com.unipi.students.model; 2 | 3 | public class Student { 4 | 5 | private Info info; 6 | private Grades grades; 7 | 8 | public Student() { 9 | } 10 | 11 | public Student(Info info, Grades grades) { 12 | this.info = info; 13 | this.grades = grades; 14 | } 15 | 16 | public Info getInfo() { 17 | return info; 18 | } 19 | 20 | public void setInfo(Info info) { 21 | this.info = info; 22 | } 23 | 24 | public Grades getGrades() { 25 | return grades; 26 | } 27 | 28 | public void setGrades(Grades grades) { 29 | this.grades = grades; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return "Student{" + 35 | "info=" + info + 36 | ", grades=" + grades + 37 | '}'; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 17 | 18 | 19 | 22 | -------------------------------------------------------------------------------- /android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.getcapacitor.myapp; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() throws Exception { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("com.getcapacitor.app", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/app/modals/about/about.page.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ModalController } from '@ionic/angular'; 3 | import { Plugins } from '@capacitor/core'; 4 | 5 | const { Device } = Plugins; 6 | 7 | @Component({ 8 | selector: 'app-about', 9 | templateUrl: './about.page.html', 10 | styleUrls: ['./about.page.scss'], 11 | }) 12 | export class AboutPage implements OnInit { 13 | 14 | device: string; 15 | appVersion: any; 16 | 17 | constructor( 18 | private modalController: ModalController 19 | ) { } 20 | 21 | ngOnInit() { 22 | Device.getInfo().then(info => { 23 | this.appVersion = info.appVersion; 24 | if (info.platform === 'ios') { 25 | this.device = 'για iPhone'; 26 | } else if (info.platform === 'android') { 27 | this.device = 'για Android'; 28 | } 29 | }); 30 | } 31 | 32 | async closeModal() { 33 | await this.modalController.dismiss(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ionic App 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Nikos Sklavounos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/app/login/login.page.scss: -------------------------------------------------------------------------------- 1 | .item-interactive.ion-invalid { 2 | --highlight-background: var(--ion-color-danger) !important; 3 | } 4 | 5 | .item-interactive.ion-valid { 6 | --highlight-background: var(--ion-color-primary) !important; 7 | } 8 | 9 | ion-item.invalid-password { 10 | --border-color: var(--ion-color-danger); 11 | --highlight-background: var(--ion-color-danger) !important; 12 | } 13 | 14 | .passwordIcon { 15 | font-size: 22px; 16 | position: absolute; 17 | bottom: 0; 18 | right: 0; 19 | padding: 8px; 20 | margin: 0; 21 | z-index: 5; 22 | } 23 | 24 | ion-input[id="pass"] { 25 | padding-right: 25px !important; 26 | } 27 | 28 | .inner-text { 29 | position: relative; 30 | transition: all .3s; 31 | right: -10px; 32 | padding-right: 0; 33 | } 34 | 35 | .inner-text-transition { 36 | padding-right: 10px; 37 | right: 15px; 38 | transition: all .3s; 39 | } 40 | 41 | #spinner { 42 | width: 25px; 43 | height: 25px; 44 | } 45 | 46 | .spinner { 47 | position: relative; 48 | transition: opacity .15s; 49 | opacity: 0; 50 | } 51 | 52 | .spinner-transition { 53 | opacity: 1; 54 | transition: opacity .15s; 55 | } 56 | 57 | .button-disabled { 58 | opacity: 1 !important; 59 | } 60 | -------------------------------------------------------------------------------- /src/app/shared/services/theme-mode.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Plugins, StatusBarStyle } from '@capacitor/core'; 3 | import { StorageService } from './storage.service'; 4 | 5 | 6 | const { StatusBar } = Plugins; 7 | 8 | @Injectable({ 9 | providedIn: 'root' 10 | }) 11 | export class ThemeModeService { 12 | private color: string; 13 | darkMode = false; 14 | 15 | constructor( 16 | private storageService: StorageService 17 | ) { } 18 | 19 | init() { 20 | this.storageService.getThemeMode().then(mode => { 21 | if (mode === null) { 22 | this.storageService.saveThemeMode('light'); 23 | } else if (mode.value === 'dark') { 24 | this.enableDarkMode(true); 25 | } 26 | }); 27 | } 28 | 29 | enableDarkMode(shouldEnable) { 30 | document.body.classList.toggle('dark', shouldEnable); 31 | if (shouldEnable) { 32 | StatusBar.setStyle({ style: StatusBarStyle.Dark }); 33 | this.storageService.saveThemeMode('dark'); 34 | this.color = '#121212'; 35 | this.darkMode = true; 36 | } else { 37 | StatusBar.setStyle({ style: StatusBarStyle.Light }); 38 | this.storageService.saveThemeMode('light'); 39 | this.color = '#ffffff'; 40 | this.darkMode = false; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/app/tabs/tabs-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { TabsPage } from './tabs.page'; 4 | 5 | const routes: Routes = [ 6 | { 7 | path: 'tabs', 8 | component: TabsPage, 9 | children: [ 10 | { 11 | path: 'tab1', 12 | children: [ 13 | { 14 | path: '', 15 | loadChildren: '../tab1/tab1.module#Tab1PageModule' 16 | } 17 | ] 18 | }, 19 | { 20 | path: 'tab2', 21 | children: [ 22 | { 23 | path: '', 24 | loadChildren: '../tab2/tab2.module#Tab2PageModule' 25 | } 26 | ] 27 | }, 28 | { 29 | path: 'tab3', 30 | children: [ 31 | { 32 | path: '', 33 | loadChildren: '../tab3/tab3.module#Tab3PageModule' 34 | } 35 | ] 36 | }, 37 | { 38 | path: '', 39 | redirectTo: '/tabs/tab1', 40 | pathMatch: 'full' 41 | } 42 | ] 43 | }, 44 | { 45 | path: '', 46 | redirectTo: '/tabs/tab1', 47 | pathMatch: 'full' 48 | } 49 | ]; 50 | 51 | @NgModule({ 52 | imports: [RouterModule.forChild(routes)], 53 | exports: [RouterModule] 54 | }) 55 | export class TabsPageRoutingModule {} 56 | -------------------------------------------------------------------------------- /src/app/tabs/tabs.router.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { TabsPage } from './tabs.page'; 4 | 5 | const routes: Routes = [ 6 | { 7 | path: 'tabs', 8 | component: TabsPage, 9 | children: [ 10 | { 11 | path: 'tab1', 12 | children: [ 13 | { 14 | path: '', 15 | loadChildren: '../tab1/tab1.module#Tab1PageModule' 16 | } 17 | ] 18 | }, 19 | { 20 | path: 'tab2', 21 | children: [ 22 | { 23 | path: '', 24 | loadChildren: '../tab2/tab2.module#Tab2PageModule' 25 | } 26 | ] 27 | }, 28 | { 29 | path: 'tab3', 30 | children: [ 31 | { 32 | path: '', 33 | loadChildren: '../tab3/tab3.module#Tab3PageModule' 34 | } 35 | ] 36 | }, 37 | { 38 | path: '', 39 | redirectTo: '/tabs/tab1', 40 | pathMatch: 'full' 41 | } 42 | ] 43 | }, 44 | { 45 | path: '', 46 | redirectTo: '/tabs/tab1', 47 | pathMatch: 'full' 48 | } 49 | ]; 50 | 51 | @NgModule({ 52 | imports: [ 53 | RouterModule.forChild(routes) 54 | ], 55 | exports: [RouterModule] 56 | }) 57 | export class TabsPageRoutingModule {} 58 | -------------------------------------------------------------------------------- /src/app/offline-login/offline-login.page.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { StorageService } from '../shared/services/storage.service'; 4 | import { StudentService } from '../shared/services/student.service'; 5 | import { RoutingService } from '../shared/services/routing.service'; 6 | 7 | @Component({ 8 | selector: 'app-offline-login', 9 | templateUrl: './offline-login.page.html', 10 | styleUrls: ['./offline-login.page.scss'], 11 | }) 12 | export class OfflineLoginPage implements OnInit { 13 | 14 | userAem = ''; 15 | 16 | constructor( 17 | private router: Router, 18 | private storageService: StorageService, 19 | private studentService: StudentService, 20 | private routingService: RoutingService 21 | ) { } 22 | 23 | async ngOnInit() { 24 | const rememberMe = this.studentService.rememberMe; 25 | if (rememberMe === true) { 26 | const stud = await this.storageService.getStudent(); 27 | if (stud.value !== null) { 28 | const student = JSON.parse(stud.value); 29 | this.userAem = student.info.aem; 30 | } 31 | } 32 | } 33 | 34 | ionViewWillEnter() { 35 | this.routingService.currentPage = '/offline-login'; 36 | } 37 | 38 | proceedOffline() { 39 | this.studentService.isLoggedIn = true; 40 | this.router.navigate(['/app/tabs/tab1']); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/app/modals/about/about.page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Σχετικά 4 | 5 | Πίσω 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | logo 14 | 15 |
UniStudents {{ device }}
16 |
Έκδοση v{{ appVersion }}
17 | 18 |
@ 2020 UniStudents Team
19 |
All rights reserved.
20 |
21 | 22 |
23 |
Βρες μας στα Social
24 |
25 | 26 | 27 | 28 |
29 | 30 | 31 | 32 | 33 |
34 | 35 |
36 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { PreloadAllModules, RouterModule, Routes } from '@angular/router'; 3 | import { AuthGuard } from './guard/auth.guard'; 4 | 5 | const routes: Routes = [ 6 | { 7 | path: 'welcome', 8 | loadChildren: () => import('./pages/welcome/welcome.module').then( m => m.WelcomePageModule) 9 | }, 10 | { 11 | path: 'universities', 12 | loadChildren: () => import('./pages/universities/universities.module').then( m => m.UniversitiesPageModule) 13 | }, 14 | { path: 'app', loadChildren: './tabs/tabs.module#TabsPageModule', canActivate: [AuthGuard]}, 15 | { path: 'login', loadChildren: './login/login.module#LoginPageModule' }, 16 | { path: 'offline-login', loadChildren: './offline-login/offline-login.module#OfflineLoginPageModule' }, 17 | { 18 | path: 'about', 19 | loadChildren: () => import('./modals/about/about.module').then( m => m.AboutPageModule) 20 | }, 21 | { 22 | path: 'faq', 23 | loadChildren: () => import('./modals/faq/faq.module').then( m => m.FaqPageModule) 24 | }, 25 | { 26 | path: 'settings', 27 | loadChildren: () => import('./modals/settings/settings.module').then( m => m.SettingsPageModule) 28 | } 29 | ]; 30 | @NgModule({ 31 | imports: [ 32 | RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules }) 33 | ], 34 | exports: [RouterModule] 35 | }) 36 | export class AppRoutingModule {} 37 | -------------------------------------------------------------------------------- /src/app/shared/services/permission.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Plugins } from '@capacitor/core'; 3 | import { AlertController } from '@ionic/angular'; 4 | import { UpdateService } from './update.service'; 5 | 6 | const { PermissionPlugin } = Plugins; 7 | 8 | @Injectable({ 9 | providedIn: 'root' 10 | }) 11 | export class PermissionService { 12 | 13 | constructor( 14 | private alertController: AlertController, 15 | private updateService: UpdateService 16 | ) { } 17 | 18 | isXiaomi() { 19 | if (this.updateService.isFirstTime) { 20 | PermissionPlugin.isXiaomi().then(res => { 21 | if (res.isXiaomi === true) { 22 | this.presentPermissionAlert(); 23 | } 24 | }); 25 | } 26 | } 27 | 28 | async presentPermissionAlert() { 29 | const alert = await this.alertController.create({ 30 | header: 'Ωπ, Xiaomi εε;', 31 | message: 'Προκειμένου να μπορούμε να σου παρέχουμε ειδοποιήσεις νέων βαθμών σε πραγματικό χρόνο ' + 32 | 'χρειάζεται να ενεργοποιήσεις την λειτουργία "Αυτόματη έναρξη".', 33 | buttons: [ 34 | { 35 | text: 'ΟΧΙ ΤΩΡΑ', 36 | role: 'cancel' 37 | }, { 38 | text: 'ΕΝΕΡΓΟΠΟΙΗΣΗ', 39 | handler: () => { 40 | PermissionPlugin.openAutoStartPermission(); 41 | } 42 | } 43 | ], 44 | backdropDismiss: false 45 | }); 46 | 47 | await alert.present(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/app/tab1/tab1.page.scss: -------------------------------------------------------------------------------- 1 | .welcome-card img { 2 | max-height: 35vh; 3 | overflow: hidden; 4 | } 5 | 6 | .welcome-grid { 7 | height: 150px; 8 | background-color: #4c8dff; 9 | } 10 | 11 | .text-sm { 12 | font-size: 12px; 13 | margin-bottom: 0; 14 | } 15 | 16 | .text-lg { 17 | font-size: 16px; 18 | margin: 0; 19 | text-overflow: ellipsis; 20 | white-space: nowrap; 21 | overflow: hidden; 22 | } 23 | 24 | .text-xl { 25 | font-size: 22px; 26 | color: #657BFF; 27 | margin: 0; 28 | } 29 | 30 | .info-grid { 31 | margin: 0 16px; 32 | padding: 0; 33 | } 34 | 35 | .info-row { 36 | margin: 12.5px 0; 37 | } 38 | 39 | .info-col { 40 | padding: 0; 41 | } 42 | 43 | .left { 44 | padding-right: 7px; 45 | } 46 | 47 | .middle { 48 | padding-right: 3.5px; 49 | padding-left: 3.5px; 50 | } 51 | 52 | .right { 53 | padding-left: 7px; 54 | } 55 | 56 | .info-card { 57 | margin: 0; 58 | height: 100%; 59 | } 60 | 61 | .lg-info { 62 | font-size: 20px; 63 | margin-top: 0; 64 | margin-bottom: 0; 65 | } 66 | 67 | .pie-canvas { 68 | margin-bottom: 8px; 69 | } 70 | 71 | .custom-skeleton ion-skeleton-text { 72 | margin: 5px 0; 73 | } 74 | 75 | .graph-skeleton { 76 | margin: 10px 10px 0 10px; 77 | } 78 | 79 | .info-skeleton { 80 | margin: 15px 0; 81 | } 82 | 83 | .chip { 84 | padding-inline-start: 8px; 85 | padding-inline-end: 8px; 86 | box-shadow: 0 0 6px 0 rgba(0,0,0,0.3); 87 | } 88 | 89 | ion-spinner { 90 | width: 20px; 91 | height: 20px; 92 | } 93 | -------------------------------------------------------------------------------- /src/app/tab2/tab2.page.scss: -------------------------------------------------------------------------------- 1 | .text-header { 2 | font-size: 17px; 3 | font-weight: bold; 4 | text-transform: none; 5 | } 6 | 7 | .text-sm { 8 | font-size: 12px; 9 | margin: 5px 0; 10 | } 11 | 12 | .text-lg { 13 | font-size: 15px; 14 | margin: 0; 15 | text-overflow: ellipsis; 16 | white-space: nowrap; 17 | overflow: hidden; 18 | } 19 | 20 | .text-xl { 21 | font-size: 30px; 22 | text-align: center; 23 | margin-top: 14px; 24 | } 25 | 26 | @media only screen and (max-width: 320px) { 27 | .text-xl { 28 | font-size: 23px; 29 | margin-top: 18px; 30 | } 31 | } 32 | 33 | .text-error-title { 34 | color: #000000; 35 | font-size: 20px; 36 | font-weight: bold; 37 | margin-top: 30px; 38 | } 39 | 40 | .text-error-subtitle { 41 | font-size: 15px; 42 | margin-bottom: 20px; 43 | margin-top: 20px; 44 | } 45 | 46 | .notify-badge { 47 | position: absolute; 48 | right: 0; 49 | top: 0; 50 | background: #EF4141; 51 | text-align: center; 52 | border-radius: 30px; 53 | color: #ffffff; 54 | padding: 5px; 55 | font-size: 7px; 56 | } 57 | 58 | ion-list { 59 | background: none; 60 | } 61 | 62 | h2.text-header { 63 | margin-bottom: 0; 64 | } 65 | 66 | .md .hydrated { 67 | .card { 68 | margin-top: 14px; 69 | margin-bottom: 14px; 70 | } 71 | } 72 | 73 | .chip { 74 | padding-inline-start: 8px; 75 | padding-inline-end: 8px; 76 | box-shadow: 0 0 6px 0 rgba(0,0,0,0.3); 77 | } 78 | 79 | ion-spinner { 80 | width: 20px; 81 | height: 20px; 82 | } 83 | -------------------------------------------------------------------------------- /src/app/modals/settings/settings.page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Ρυθμίσεις 4 | 5 | Πίσω 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Λογαριασμός 15 | 16 | 17 | 18 | 19 | Να με θυμάσαι 20 | 21 | 22 | 23 | 24 | 25 | Ειδοποιήσεις 26 | 27 | 28 | 29 | 30 | Νέος Βαθμός BETA 31 | 32 | 33 | 34 | 35 | 36 | 37 | Ενημερώσεις UniStudents 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/unipi/students/plugin/PermissionPlugin.java: -------------------------------------------------------------------------------- 1 | package com.unipi.students.plugin; 2 | 3 | import android.content.ComponentName; 4 | import android.content.Intent; 5 | import android.util.Log; 6 | 7 | import com.getcapacitor.JSObject; 8 | import com.getcapacitor.NativePlugin; 9 | import com.getcapacitor.Plugin; 10 | import com.getcapacitor.PluginCall; 11 | import com.getcapacitor.PluginMethod; 12 | 13 | @NativePlugin() 14 | public class PermissionPlugin extends Plugin { 15 | 16 | @PluginMethod() 17 | public void isXiaomi(PluginCall call) { 18 | try { 19 | String manufacturer = android.os.Build.MANUFACTURER; 20 | JSObject ret = new JSObject(); 21 | ret.put("isXiaomi", "xiaomi".equalsIgnoreCase(manufacturer)); 22 | call.success(ret); 23 | } catch (Exception e) { 24 | e.printStackTrace(); 25 | } 26 | } 27 | 28 | @PluginMethod() 29 | public void openAutoStartPermission(PluginCall call) { 30 | try { 31 | Intent intent = new Intent(); 32 | String manufacturer = android.os.Build.MANUFACTURER; 33 | if ("xiaomi".equalsIgnoreCase(manufacturer)) { 34 | intent.setComponent(new ComponentName("com.miui.securitycenter", "com.miui.permcenter.autostart.AutoStartManagementActivity")); 35 | } 36 | this.getActivity().startActivity(intent); 37 | call.success(); 38 | } catch (Exception e) { 39 | Log.e("exc" , String.valueOf(e)); 40 | call.reject(e.getMessage()); 41 | } 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/global.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * App Global CSS 3 | * ---------------------------------------------------------------------------- 4 | * Put style rules here that you want to apply globally. These styles are for 5 | * the entire app and not just one component. Additionally, this file can be 6 | * used as an entry point to import other CSS/Sass files to be included in the 7 | * output CSS. 8 | * For more information on global stylesheets, visit the documentation: 9 | * https://ionicframework.com/docs/layout/global-stylesheets 10 | */ 11 | 12 | /* Core CSS required for Ionic components to work properly */ 13 | @import "~@ionic/angular/css/core.css"; 14 | 15 | /* Basic CSS for apps built with Ionic */ 16 | @import "~@ionic/angular/css/normalize.css"; 17 | @import "~@ionic/angular/css/structure.css"; 18 | @import "~@ionic/angular/css/typography.css"; 19 | @import '~@ionic/angular/css/display.css'; 20 | 21 | /* Optional CSS utils that can be commented out */ 22 | @import "~@ionic/angular/css/padding.css"; 23 | @import "~@ionic/angular/css/float-elements.css"; 24 | @import "~@ionic/angular/css/text-alignment.css"; 25 | @import "~@ionic/angular/css/text-transformation.css"; 26 | @import "~@ionic/angular/css/flex-utils.css"; 27 | 28 | ion-skeleton-text { 29 | border-radius: 5px !important; 30 | } 31 | 32 | .custom-toast { 33 | --border-radius: 0; 34 | --start: 0; 35 | --end: 0; 36 | --box-shadow: none; 37 | transform: translateY(-57px) !important; 38 | } 39 | 40 | .alert { 41 | z-index: 61000 !important; 42 | } 43 | 44 | .modal { 45 | z-index: 60999 !important; 46 | } 47 | 48 | .sync-component { 49 | top: calc(env(safe-area-inset-top) + 65px); 50 | left: 0; 51 | right: 0; 52 | z-index: 999; 53 | text-align: center; 54 | width: 100%; 55 | position: fixed; 56 | } 57 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/unipi/students/model/Course.java: -------------------------------------------------------------------------------- 1 | package com.unipi.students.model; 2 | 3 | public class Course { 4 | 5 | private String id; 6 | private String name; 7 | private String type; 8 | private String grade; 9 | private String examPeriod; 10 | 11 | public Course() { 12 | } 13 | 14 | public Course(String id, String name, String type, String grade, String examPeriod) { 15 | this.id = id; 16 | this.name = name; 17 | this.type = type; 18 | this.grade = grade; 19 | this.examPeriod = examPeriod; 20 | } 21 | 22 | public String getId() { 23 | return id; 24 | } 25 | 26 | public void setId(String id) { 27 | this.id = id; 28 | } 29 | 30 | public String getName() { 31 | return name; 32 | } 33 | 34 | public void setName(String name) { 35 | this.name = name; 36 | } 37 | 38 | public String getType() { 39 | return type; 40 | } 41 | 42 | public void setType(String type) { 43 | this.type = type; 44 | } 45 | 46 | public String getGrade() { 47 | return grade; 48 | } 49 | 50 | public void setGrade(String grade) { 51 | this.grade = grade; 52 | } 53 | 54 | public String getExamPeriod() { 55 | return examPeriod; 56 | } 57 | 58 | public void setExamPeriod(String examPeriod) { 59 | this.examPeriod = examPeriod; 60 | } 61 | 62 | @Override 63 | public String toString() { 64 | return "Course{" + 65 | "id='" + id + '\'' + 66 | ", name='" + name + '\'' + 67 | ", type='" + type + '\'' + 68 | ", grade=" + grade + 69 | ", examPeriod='" + examPeriod + '\'' + 70 | '}'; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/app/pages/universities/universities.page.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { UniversityService } from '../../shared/services/university.service'; 4 | import { RoutingService } from '../../shared/services/routing.service'; 5 | import { AlertController, Platform } from '@ionic/angular'; 6 | import { UpdateService } from '../../shared/services/update.service'; 7 | 8 | @Component({ 9 | selector: 'app-universities', 10 | templateUrl: './universities.page.html', 11 | styleUrls: ['./universities.page.scss'], 12 | }) 13 | export class UniversitiesPage implements OnInit { 14 | 15 | isAndroid: boolean; 16 | 17 | constructor( 18 | private router: Router, 19 | private universityService: UniversityService, 20 | private routingService: RoutingService, 21 | private platform: Platform, 22 | private updateService: UpdateService, 23 | private alertController: AlertController 24 | ) { } 25 | 26 | ngOnInit() { 27 | this.isAndroid = this.platform.is('android'); 28 | if (this.updateService.isFirstTime) { 29 | setTimeout(() => { 30 | this.presentAlert(); 31 | }, 500); 32 | } 33 | } 34 | 35 | ionViewWillEnter() { 36 | this.routingService.currentPage = '/universities'; 37 | } 38 | 39 | action(uni: string) { 40 | this.universityService.uni = uni; 41 | this.router.navigateByUrl('/login'); 42 | } 43 | 44 | async presentAlert() { 45 | const alert = await this.alertController.create({ 46 | header: 'Σημαντική Ενημέρωση', 47 | message: 'Τα ιδρύματα ΕΚΠΑ, ΠΑΔΑ και ΠΑΝΤΕΙΟ βρίσκονται σε δοκιμαστική φάση. ' + 48 | 'Σε περίπτωση που αντιμετωπίσεις κάποιο πρόβλημα παρακαλούμε να επικοινωνήσεις μαζί μας στο info@unistudents.gr', 49 | buttons: ['ΤΟ ΚΑΤΑΛΑΒΑ'], 50 | backdropDismiss: false 51 | }); 52 | 53 | await alert.present(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { RouteReuseStrategy } from '@angular/router'; 4 | 5 | import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; 6 | import { SplashScreen } from '@ionic-native/splash-screen/ngx'; 7 | import { StatusBar } from '@ionic-native/status-bar/ngx'; 8 | 9 | import { AppRoutingModule } from './app-routing.module'; 10 | import { AppComponent } from './app.component'; 11 | import { SharedModule } from './shared/shared.module'; 12 | import { IonicStorageModule } from '@ionic/storage'; 13 | import { AppMinimize } from '@ionic-native/app-minimize/ngx'; 14 | import { Network } from '@ionic-native/network/ngx'; 15 | import { AngularFireModule } from '@angular/fire'; 16 | import { environment } from '../environments/environment'; 17 | import { AngularFireAnalyticsModule, ScreenTrackingService, UserTrackingService } from '@angular/fire/analytics'; 18 | import { AboutPage } from './modals/about/about.page'; 19 | import { FaqPage } from './modals/faq/faq.page'; 20 | import { SettingsPage } from './modals/settings/settings.page'; 21 | import { FormsModule } from '@angular/forms'; 22 | 23 | @NgModule({ 24 | declarations: [AppComponent, FaqPage, SettingsPage, AboutPage], 25 | entryComponents: [FaqPage, SettingsPage, AboutPage], 26 | imports: [ 27 | BrowserModule, 28 | IonicModule.forRoot(), 29 | AppRoutingModule, 30 | IonicStorageModule.forRoot(), 31 | SharedModule, 32 | AngularFireModule.initializeApp(environment.firebase), 33 | AngularFireAnalyticsModule, 34 | FormsModule 35 | ], 36 | providers: [ 37 | StatusBar, 38 | SplashScreen, 39 | Network, 40 | AppMinimize, 41 | ScreenTrackingService, 42 | UserTrackingService, 43 | { provide: RouteReuseStrategy, useClass: IonicRouteStrategy } 44 | ], 45 | bootstrap: [AppComponent] 46 | }) 47 | export class AppModule {} 48 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/unipi/students/model/Grades.java: -------------------------------------------------------------------------------- 1 | package com.unipi.students.model; 2 | 3 | import java.util.ArrayList; 4 | 5 | public class Grades { 6 | 7 | private String totalPassedCourses; 8 | private String totalAverageGrade; 9 | private String totalEcts; 10 | private ArrayList semesters; 11 | 12 | public Grades() { 13 | this.semesters = new ArrayList<>(); 14 | } 15 | 16 | public Grades(String totalPassedCourses, String totalAverageGrade, String totalEcts) { 17 | this.totalPassedCourses = totalPassedCourses; 18 | this.totalAverageGrade = totalAverageGrade; 19 | this.totalEcts = totalEcts; 20 | this.semesters = new ArrayList<>(); 21 | } 22 | 23 | public String getTotalPassedCourses() { 24 | return totalPassedCourses; 25 | } 26 | 27 | public void setTotalPassedCourses(String totalPassedCourses) { 28 | this.totalPassedCourses = totalPassedCourses; 29 | } 30 | 31 | public String getTotalAverageGrade() { 32 | return totalAverageGrade; 33 | } 34 | 35 | public void setTotalAverageGrade(String totalAverageGrade) { 36 | this.totalAverageGrade = totalAverageGrade; 37 | } 38 | 39 | public String getTotalEcts() { 40 | return totalEcts; 41 | } 42 | 43 | public void setTotalEcts(String totalEcts) { 44 | this.totalEcts = totalEcts; 45 | } 46 | 47 | public ArrayList getSemesters() { 48 | return semesters; 49 | } 50 | 51 | public void setSemesters(ArrayList semesters) { 52 | this.semesters = semesters; 53 | } 54 | 55 | @Override 56 | public String toString() { 57 | return "Grades{" + 58 | "totalPassedCourses=" + totalPassedCourses + 59 | ", totalAverageGrade='" + totalAverageGrade + '\'' + 60 | ", totalEcts=" + totalEcts + 61 | ", semesters=" + semesters + 62 | '}'; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/unipi/students/model/Semester.java: -------------------------------------------------------------------------------- 1 | package com.unipi.students.model; 2 | 3 | import java.util.ArrayList; 4 | 5 | public class Semester { 6 | 7 | private int id; 8 | private int passedCourses; 9 | private String gradeAverage; 10 | private String ects; 11 | private ArrayList courses; 12 | 13 | public Semester() { 14 | this.courses = new ArrayList<>(); 15 | } 16 | 17 | public Semester(int id, int passedCourses, String gradeAverage, String ects) { 18 | this.id = id; 19 | this.passedCourses = passedCourses; 20 | this.gradeAverage = gradeAverage; 21 | this.ects = ects; 22 | this.courses = new ArrayList<>(); 23 | } 24 | 25 | public int getId() { 26 | return id; 27 | } 28 | 29 | public void setId(int id) { 30 | this.id = id; 31 | } 32 | 33 | public int getPassedCourses() { 34 | return passedCourses; 35 | } 36 | 37 | public void setPassedCourses(int passedCourses) { 38 | this.passedCourses = passedCourses; 39 | } 40 | 41 | public String getGradeAverage() { 42 | return gradeAverage; 43 | } 44 | 45 | public void setGradeAverage(String gradeAverage) { 46 | this.gradeAverage = gradeAverage; 47 | } 48 | 49 | public String getEcts() { 50 | return ects; 51 | } 52 | 53 | public void setEcts(String ects) { 54 | this.ects = ects; 55 | } 56 | 57 | public ArrayList getCourses() { 58 | return courses; 59 | } 60 | 61 | public void setCourses(ArrayList courses) { 62 | this.courses = courses; 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | return "Semester{" + 68 | "id=" + id + 69 | ", passedCourses=" + passedCourses + 70 | ", gradeAverage=" + gradeAverage + 71 | ", ects=" + ects + 72 | ", courses=" + courses + 73 | '}'; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /src/app/shared/services/university.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { StorageService } from './storage.service'; 3 | 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class UniversityService { 8 | 9 | private _uni: string; 10 | private _uniLogo: string; 11 | private _uniForgotMyPasswordLink: string; 12 | 13 | constructor( 14 | private storageService: StorageService 15 | ) { } 16 | 17 | async init() { 18 | const university = await this.storageService.getUniversity(); 19 | if (university.value !== null) { 20 | this.uni = university.value; 21 | } 22 | } 23 | 24 | get uni() { 25 | return this._uni; 26 | } 27 | 28 | set uni(uni: string) { 29 | this._uni = uni; 30 | this.uniLogo = uni; 31 | this.uniForgotMyPasswordLink = uni; 32 | } 33 | 34 | get uniLogo() { 35 | return this._uniLogo; 36 | } 37 | 38 | set uniLogo(uni: string) { 39 | switch (uni) { 40 | case 'UNIPI': 41 | this._uniLogo = '/assets/unipi-logo.png'; 42 | break; 43 | case 'UNIWA': 44 | this._uniLogo = '/assets/uniwa-logo.png'; 45 | break; 46 | case 'UOA': 47 | this._uniLogo = '/assets/ekpa-logo.png'; 48 | break; 49 | case 'PANTEION': 50 | this._uniLogo = '/assets/panteion-logo.png'; 51 | break; 52 | } 53 | } 54 | 55 | get uniForgotMyPasswordLink() { 56 | return this._uniForgotMyPasswordLink; 57 | } 58 | 59 | set uniForgotMyPasswordLink(uni: string) { 60 | switch (uni) { 61 | case 'UNIPI': 62 | this._uniForgotMyPasswordLink = 'https://mypassword.unipi.gr'; 63 | break; 64 | case 'UNIWA': 65 | this._uniForgotMyPasswordLink = 'https://my.uniwa.gr/recovery.php'; 66 | break; 67 | case 'UOA': 68 | this._uniForgotMyPasswordLink = 'http://www.noc.uoa.gr/ypostiri3h-xrhston/syxnes-erwtiseis.html#forgottenpassword'; 69 | break; 70 | case 'PANTEION': 71 | this._uniForgotMyPasswordLink = 'https://mypassword.panteion.gr/'; 72 | break; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/unipi/students/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.unipi.students; 2 | 3 | import android.os.Bundle; 4 | import android.util.Log; 5 | 6 | import androidx.work.Constraints; 7 | import androidx.work.ExistingPeriodicWorkPolicy; 8 | import androidx.work.NetworkType; 9 | import androidx.work.PeriodicWorkRequest; 10 | import androidx.work.WorkManager; 11 | 12 | import com.datadog.android.Datadog; 13 | import com.datadog.android.DatadogConfig; 14 | import com.getcapacitor.BridgeActivity; 15 | import com.getcapacitor.Plugin; 16 | import com.unipi.students.plugin.FCMPlugin; 17 | import com.unipi.students.plugin.PermissionPlugin; 18 | import com.unipi.students.plugin.ScrapePlugin; 19 | import com.unipi.students.worker.ScrapeDataWorker; 20 | 21 | import java.util.ArrayList; 22 | import java.util.concurrent.TimeUnit; 23 | 24 | public class MainActivity extends BridgeActivity { 25 | 26 | private WorkManager mWorkManager; 27 | 28 | @Override 29 | public void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | 32 | mWorkManager = WorkManager.getInstance(getApplication()); 33 | fetchData(); 34 | 35 | // Initializes the Bridge 36 | this.init(savedInstanceState, new ArrayList>() {{ 37 | // Additional plugins you've installed go here 38 | add(ScrapePlugin.class); 39 | add(FCMPlugin.class); 40 | add(PermissionPlugin.class); 41 | }}); 42 | } 43 | 44 | private void fetchData() { 45 | Constraints constraints = new Constraints.Builder() 46 | .setRequiredNetworkType(NetworkType.CONNECTED) 47 | .build(); 48 | 49 | PeriodicWorkRequest workRequest = 50 | new PeriodicWorkRequest.Builder(ScrapeDataWorker.class, 50, TimeUnit.MINUTES) 51 | .addTag("SCRAPE_WORKER") 52 | .setConstraints(constraints) 53 | .build(); 54 | 55 | mWorkManager.enqueueUniquePeriodicWork( 56 | "SCRAPE_WORKER", 57 | ExistingPeriodicWorkPolicy.KEEP, 58 | workRequest); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rulesDirectory": [ 4 | "codelyzer" 5 | ], 6 | "rules": { 7 | "array-type": false, 8 | "arrow-parens": false, 9 | "deprecation": { 10 | "severity": "warn" 11 | }, 12 | "import-blacklist": [ 13 | true, 14 | "rxjs/Rx" 15 | ], 16 | "interface-name": false, 17 | "max-classes-per-file": false, 18 | "max-line-length": [ 19 | true, 20 | 140 21 | ], 22 | "member-access": false, 23 | "member-ordering": [ 24 | true, 25 | { 26 | "order": [ 27 | "static-field", 28 | "instance-field", 29 | "static-method", 30 | "instance-method" 31 | ] 32 | } 33 | ], 34 | "no-consecutive-blank-lines": false, 35 | "no-console": [ 36 | true, 37 | "debug", 38 | "info", 39 | "time", 40 | "timeEnd", 41 | "trace" 42 | ], 43 | "no-empty": false, 44 | "no-inferrable-types": [ 45 | true, 46 | "ignore-params" 47 | ], 48 | "no-non-null-assertion": true, 49 | "no-redundant-jsdoc": true, 50 | "no-switch-case-fall-through": true, 51 | "no-use-before-declare": true, 52 | "no-var-requires": false, 53 | "object-literal-key-quotes": [ 54 | true, 55 | "as-needed" 56 | ], 57 | "object-literal-sort-keys": false, 58 | "ordered-imports": false, 59 | "quotemark": [ 60 | true, 61 | "single" 62 | ], 63 | "trailing-comma": false, 64 | "no-output-on-prefix": true, 65 | "no-inputs-metadata-property": true, 66 | "no-host-metadata-property": true, 67 | "no-input-rename": true, 68 | "no-output-rename": true, 69 | "use-lifecycle-interface": true, 70 | "use-pipe-transform-interface": true, 71 | "one-variable-per-declaration": false, 72 | "component-class-suffix": [true, "Page", "Component"], 73 | "directive-class-suffix": true, 74 | "directive-selector": [ 75 | true, 76 | "attribute", 77 | "app", 78 | "camelCase" 79 | ], 80 | "component-selector": [ 81 | true, 82 | "element", 83 | "app", 84 | "page", 85 | "kebab-case" 86 | ] 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { Platform } from '@ionic/angular'; 4 | import { Plugins, StatusBarStyle } from '@capacitor/core'; 5 | import { ThemeModeService } from './shared/services/theme-mode.service'; 6 | import { NetworkService } from './shared/services/network.service'; 7 | import { UpdateService } from './shared/services/update.service'; 8 | import { RoutingService } from './shared/services/routing.service'; 9 | import { NotificationService } from './shared/services/notification.service'; 10 | import { UniversityService } from './shared/services/university.service'; 11 | import { StudentService } from './shared/services/student.service'; 12 | 13 | @Component({ 14 | selector: 'app-root', 15 | templateUrl: 'app.component.html', 16 | styleUrls: ['app.component.scss'] 17 | }) 18 | export class AppComponent { 19 | constructor( 20 | private platform: Platform, 21 | private updateService: UpdateService, 22 | private routingService: RoutingService, 23 | private networkService: NetworkService, 24 | private studentService: StudentService, 25 | private themeModeService: ThemeModeService, 26 | private universityService: UniversityService, 27 | private notificationService: NotificationService 28 | ) { 29 | this.initializeApp(); 30 | } 31 | 32 | initializeApp() { 33 | const { SplashScreen, StatusBar } = Plugins; 34 | this.platform.ready().then(() => { 35 | SplashScreen.hide(); 36 | StatusBar.setStyle({ style: StatusBarStyle.Light}); 37 | 38 | // clear received notifications (for iOS) 39 | this.notificationService.removeAllDeliveredNotifications(); 40 | 41 | // check for new updates 42 | this.updateService.checkForUpdates().then(() => { 43 | 44 | // init university values 45 | this.universityService.init().then(() => { 46 | 47 | // init student data & preferences 48 | this.studentService.init().then(() => { 49 | 50 | // init network service 51 | this.networkService.init().then(() => { 52 | 53 | // init routing service 54 | this.routingService.init(); 55 | }); 56 | }); 57 | }); 58 | }); 59 | 60 | // set up theme mode 61 | this.themeModeService.init(); 62 | }); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/unipi/students/common/UserAgentGenerator.java: -------------------------------------------------------------------------------- 1 | package com.unipi.students.common; 2 | 3 | import java.util.Random; 4 | 5 | public class UserAgentGenerator { 6 | 7 | private static String[] userAgents = { 8 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0", 9 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:80.0) Gecko/20100101 Firefox/80.0", 10 | "Mozilla/5.0 (X11; Linux i686; rv:80.0) Gecko/20100101 Firefox/80.0", 11 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36", 12 | "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36", 13 | "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36", 14 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36", 15 | "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36", 16 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 OPR/70.0.3728.133", 17 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 OPR/70.0.3728.133", 18 | "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 OPR/70.0.3728.133", 19 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Edg/84.0.522.63", 20 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Edg/84.0.522.44", 21 | "Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko", 22 | "Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko", 23 | "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko" 24 | }; 25 | 26 | public static String generate() { 27 | Random random = new Random(); 28 | int randomIndex = random.nextInt(userAgents.length); 29 | return userAgents[randomIndex]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/app/shared/services/toast.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { AnimationController, Platform, ToastController } from '@ionic/angular'; 3 | 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class ToastService { 8 | 9 | constructor( 10 | private toastController: ToastController, 11 | private animationCtrl: AnimationController, 12 | private platform: Platform 13 | ) { } 14 | 15 | async presentSimple(msg: string, dur = 2000) { 16 | const toast = await this.toastController.create({ 17 | message: msg, 18 | duration: dur, 19 | mode: 'ios' 20 | }); 21 | await toast.present(); 22 | } 23 | 24 | async present(msg: string, dur = 2000) { 25 | 26 | if (this.platform.is('ios')) { 27 | this.presentSimple(msg, dur); 28 | return; 29 | } 30 | 31 | const leave = (baseEl: any) => { 32 | const bottom = `calc(-0px - var(--ion-safe-area-bottom, 0px))`; 33 | const animation = this.animationCtrl.create() 34 | // tslint:disable-next-line:no-non-null-assertion 35 | .addElement(baseEl.querySelector('.toast-wrapper')!) 36 | .fromTo('transform', `translateY(${bottom})`, 'translateY(100%'); 37 | 38 | return this.animationCtrl.create() 39 | .addElement(baseEl) 40 | .easing('cubic-bezier(.36,.66,.04,1)') 41 | .duration(300) 42 | .addAnimation(animation); 43 | }; 44 | 45 | const enter = (baseEl: any) => { 46 | const bottom = `calc(-0px - var(--ion-safe-area-bottom, 0px))`; 47 | const animation = this.animationCtrl.create() 48 | // tslint:disable-next-line:no-non-null-assertion 49 | .addElement(baseEl.querySelector('.toast-wrapper')!) 50 | .fromTo('transform', 'translateY(100%)', `translateY(${bottom})`); 51 | 52 | return this.animationCtrl.create() 53 | .addElement(baseEl) 54 | .easing('cubic-bezier(.36,.66,.04,1)') 55 | .duration(300) 56 | .addAnimation(animation); 57 | }; 58 | 59 | const toast = await this.toastController.create({ 60 | message: msg, 61 | duration: dur, 62 | cssClass: 'custom-toast', 63 | mode: 'ios', 64 | leaveAnimation: ((baseEl, opts) => leave(baseEl)), 65 | enterAnimation: ((baseEl, opts) => (enter(baseEl))) 66 | }); 67 | await toast.present(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/unipi/students/model/Info.java: -------------------------------------------------------------------------------- 1 | package com.unipi.students.model; 2 | 3 | public class Info { 4 | 5 | private String aem; 6 | private String firstName; 7 | private String lastName; 8 | private String department; 9 | private String semester; 10 | private String registrationYear; 11 | 12 | public Info() { 13 | } 14 | 15 | public Info(String aem, String firstName, String lastName, String department, String semester, String registrationYear) { 16 | this.aem = aem; 17 | this.firstName = firstName; 18 | this.lastName = lastName; 19 | this.department = department; 20 | this.semester = semester; 21 | this.registrationYear = registrationYear; 22 | } 23 | 24 | public String getAem() { 25 | return aem; 26 | } 27 | 28 | public void setAem(String aem) { 29 | this.aem = aem; 30 | } 31 | 32 | public String getFirstName() { 33 | return firstName; 34 | } 35 | 36 | public void setFirstName(String firstName) { 37 | this.firstName = firstName; 38 | } 39 | 40 | public String getLastName() { 41 | return lastName; 42 | } 43 | 44 | public void setLastName(String lastName) { 45 | this.lastName = lastName; 46 | } 47 | 48 | public String getDepartment() { 49 | return department; 50 | } 51 | 52 | public void setDepartment(String department) { 53 | this.department = department; 54 | } 55 | 56 | public String getSemester() { 57 | return semester; 58 | } 59 | 60 | public void setSemester(String semester) { 61 | this.semester = semester; 62 | } 63 | 64 | public String getRegistrationYear() { 65 | return registrationYear; 66 | } 67 | 68 | public void setRegistrationYear(String registrationYear) { 69 | this.registrationYear = registrationYear; 70 | } 71 | 72 | @Override 73 | public String toString() { 74 | return "Info{" + 75 | "aem='" + aem + '\'' + 76 | ", firstName='" + firstName + '\'' + 77 | ", lastName='" + lastName + '\'' + 78 | ", department='" + department + '\'' + 79 | ", semester='" + semester + '\'' + 80 | ", registrationYear='" + registrationYear + '\'' + 81 | '}'; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "0.0.1", 4 | "author": "Ionic Framework", 5 | "homepage": "https://ionicframework.com/", 6 | "scripts": { 7 | "ng": "ng", 8 | "start": "ng serve", 9 | "build": "ng build", 10 | "test": "ng test", 11 | "lint": "ng lint", 12 | "e2e": "ng e2e" 13 | }, 14 | "private": true, 15 | "dependencies": { 16 | "@angular/animations": "~8.2.14", 17 | "@angular/cdk": "~8.2.3", 18 | "@angular/common": "~8.2.14", 19 | "@angular/core": "~8.2.14", 20 | "@angular/fire": "^6.0.0", 21 | "@angular/forms": "~8.2.14", 22 | "@angular/material": "^8.2.3", 23 | "@angular/platform-browser": "~8.2.14", 24 | "@angular/platform-browser-dynamic": "~8.2.14", 25 | "@angular/router": "~8.2.14", 26 | "@capacitor-community/fcm": "^1.0.8", 27 | "@capacitor/android": "^2.0.2", 28 | "@capacitor/core": "2.0.0", 29 | "@capacitor/ios": "^2.0.2", 30 | "@ionic-native/app-minimize": "^5.24.0", 31 | "@ionic-native/core": "^5.24.0", 32 | "@ionic-native/network": "^5.24.0", 33 | "@ionic-native/splash-screen": "^5.24.0", 34 | "@ionic-native/status-bar": "^5.24.0", 35 | "@ionic/angular": "^5.1.1", 36 | "@ionic/storage": "^2.2.0", 37 | "chart.js": "^2.9.3", 38 | "core-js": "^2.5.4", 39 | "crypto-js": "^4.0.0", 40 | "firebase": "^7.14.2", 41 | "hammerjs": "^2.0.8", 42 | "rxjs": "~6.5.1", 43 | "tslib": "^1.9.0", 44 | "zone.js": "~0.9.1" 45 | }, 46 | "devDependencies": { 47 | "@angular-devkit/build-angular": "~0.803.20", 48 | "@angular/cli": "~8.3.23", 49 | "@angular/compiler": "~8.2.14", 50 | "@angular/compiler-cli": "~8.2.14", 51 | "@angular/language-service": "~8.2.14", 52 | "@capacitor/cli": "2.0.0", 53 | "@ionic/angular-toolkit": "^2.1.1", 54 | "@ionic/lab": "3.1.3", 55 | "@types/jasmine": "~3.3.8", 56 | "@types/jasminewd2": "~2.0.3", 57 | "@types/node": "~8.9.4", 58 | "codelyzer": "^5.0.0", 59 | "jasmine-core": "~3.4.0", 60 | "jasmine-spec-reporter": "~4.2.1", 61 | "karma": "~4.1.0", 62 | "karma-chrome-launcher": "~2.2.0", 63 | "karma-coverage-istanbul-reporter": "~2.0.1", 64 | "karma-jasmine": "~2.0.1", 65 | "karma-jasmine-html-reporter": "^1.4.0", 66 | "protractor": "^5.4.4", 67 | "ts-node": "~7.0.0", 68 | "tslint": "~5.15.0", 69 | "typescript": "~3.4.3" 70 | }, 71 | "description": "An Ionic project" 72 | } 73 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | defaultConfig { 6 | applicationId "com.unipi.students" 7 | minSdkVersion rootProject.ext.minSdkVersion 8 | targetSdkVersion rootProject.ext.targetSdkVersion 9 | versionCode 100116 10 | versionName "1.1.16" 11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | repositories { 22 | flatDir{ 23 | dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs' 24 | } 25 | maven { url 'https://dl.bintray.com/datadog/datadog-maven' } 26 | } 27 | 28 | dependencies { 29 | implementation fileTree(include: ['*.jar'], dir: 'libs') 30 | implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" 31 | implementation project(':capacitor-android') 32 | testImplementation "junit:junit:$junitVersion" 33 | androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" 34 | androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" 35 | implementation project(':capacitor-cordova-android-plugins') 36 | implementation 'org.jsoup:jsoup:1.13.1' 37 | implementation 'org.apache.commons:commons-lang3:3.6' 38 | implementation 'commons-codec:commons-codec:1.10' 39 | implementation 'com.fasterxml.jackson.core:jackson-core:2.11.0' 40 | implementation 'com.fasterxml.jackson.core:jackson-annotations:2.11.0' 41 | implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.0' 42 | 43 | def work_version = "2.3.4" 44 | 45 | // (Java only) 46 | implementation "androidx.work:work-runtime:$work_version" 47 | 48 | implementation 'com.google.firebase:firebase-messaging:20.1.7' 49 | 50 | implementation 'com.datadoghq:dd-sdk-android:1.4.3' 51 | } 52 | 53 | apply from: 'capacitor.build.gradle' 54 | 55 | try { 56 | def servicesJSON = file('google-services.json') 57 | if (servicesJSON.text) { 58 | apply plugin: 'com.google.gms.google-services' 59 | } 60 | } catch(Exception e) { 61 | logger.warn("google-services.json not found, google-services plugin not applied. Push Notifications won't work") 62 | } -------------------------------------------------------------------------------- /src/app/tab2/tab2.page.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Grades } from '../shared/models/grades.model'; 3 | import { Platform } from '@ionic/angular'; 4 | import { StoreService } from '../shared/services/store.service'; 5 | import { Observable } from 'rxjs'; 6 | import { Student } from '../shared/models/student.model'; 7 | import { RoutingService } from '../shared/services/routing.service'; 8 | import { AngularFireAnalytics } from '@angular/fire/analytics'; 9 | import { StorageService } from '../shared/services/storage.service'; 10 | import { ApiService } from '../shared/services/api.service'; 11 | import { NetworkService } from '../shared/services/network.service'; 12 | 13 | @Component({ 14 | selector: 'app-tab2', 15 | templateUrl: 'tab2.page.html', 16 | styleUrls: ['tab2.page.scss'] 17 | }) 18 | export class Tab2Page { 19 | 20 | public grades: Grades = null; 21 | private studentObservable: Observable; 22 | 23 | constructor( 24 | private storeService: StoreService, 25 | private storageService: StorageService, 26 | private apiService: ApiService, 27 | private platform: Platform, 28 | private routingService: RoutingService, 29 | private networkService: NetworkService, 30 | private angularFireAnalytics: AngularFireAnalytics 31 | ) {} 32 | 33 | ngOnInit(): void { 34 | this.studentObservable = this.storeService.students; 35 | this.studentObservable.subscribe(res => { 36 | if (res.length !== 0) { 37 | this.grades = res[0].grades; 38 | } 39 | }); 40 | } 41 | 42 | ionViewWillEnter() { 43 | this.routingService.currentPage = '/app/tabs/tab2'; 44 | } 45 | 46 | refreshGrades(event) { 47 | this.angularFireAnalytics.logEvent('refresh_grades', {screen: 'tab2'}); 48 | if (this.networkService.networkStatus.connected !== true) { 49 | event.target.complete(); 50 | return; 51 | } 52 | setTimeout(() => { 53 | this.storeService.fetchStudent(event); 54 | }, 500); 55 | } 56 | 57 | getGradeColor(gradeString: string) { 58 | if (gradeString.includes('-')) { 59 | return '#657BFF'; 60 | } 61 | 62 | const grade = Number(gradeString.replace(',', '.')); 63 | 64 | if (grade >= 5) { 65 | return '#657BFF'; 66 | } else { 67 | return '#f25454'; 68 | } 69 | } 70 | 71 | getCourseLength(courseLength) { 72 | if (courseLength === 0) { 73 | return 11; 74 | } else if (courseLength >= 3) { 75 | return 9; 76 | } else { 77 | return 10; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/app/shared/services/routing.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Platform } from '@ionic/angular'; 3 | import { Router } from '@angular/router'; 4 | import { Plugins } from '@capacitor/core'; 5 | import { StudentService } from './student.service'; 6 | import { NetworkService } from './network.service'; 7 | import { UpdateService } from './update.service'; 8 | 9 | const { App } = Plugins; 10 | 11 | @Injectable({ 12 | providedIn: 'root' 13 | }) 14 | export class RoutingService { 15 | 16 | currentPage: string; 17 | isAnyModalPageOpened = false; 18 | isLoadingFlag = false; 19 | 20 | constructor( 21 | private router: Router, 22 | private platform: Platform, 23 | private networkService: NetworkService, 24 | private studentService: StudentService, 25 | private updateService: UpdateService 26 | ) { } 27 | 28 | init() { 29 | if (this.networkService.networkStatus.connected === true) { 30 | const rememberMe = this.studentService.rememberMe; 31 | if (rememberMe === null && this.updateService.isFirstTime) { 32 | this.router.navigateByUrl('/welcome'); 33 | } else if (rememberMe === null && !this.updateService.isFirstTime) { 34 | this.router.navigateByUrl('/universities'); 35 | } else if (rememberMe === false) { 36 | this.router.navigateByUrl('/login'); 37 | } else if (rememberMe === true) { 38 | this.router.navigate(['/app/tabs/tab1']); 39 | } 40 | } else if (this.networkService.networkStatus.connected === false) { 41 | this.router.navigateByUrl('/offline-login'); 42 | } 43 | 44 | this.platform.resume.subscribe(() => { 45 | this.router.navigate([this.currentPage]); 46 | }); 47 | 48 | this.platform.backButton.subscribe(async () => { 49 | if ((this.router.isActive('/welcome', true) && this.router.url === '/welcome') || 50 | (this.router.isActive('/universities', true) && this.router.url === '/universities') || 51 | ((this.router.isActive('/offline-login', true) && this.router.url === '/offline-login')) || 52 | ((this.router.isActive('/app/tabs/tab1', true) && this.router.url === '/app/tabs/tab1')) || 53 | ((this.router.isActive('/app/tabs/tab2', true) && this.router.url === '/app/tabs/tab2')) || 54 | ((this.router.isActive('/app/tabs/tab3', true) && this.router.url === '/app/tabs/tab3') && !this.isAnyModalPageOpened)) { 55 | App.exitApp(); 56 | } else if ((this.router.isActive('/login', true) && this.router.url === '/login') && !this.isLoadingFlag) { 57 | this.router.navigate(['/universities']); 58 | } 59 | }); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/app/login/login.page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |
19 |
20 | 21 | 22 |

Σύνδεση

23 |
24 |
25 | 26 | 27 | 28 | Όνομα Χρήστη 29 | 30 | 31 | 32 | Κωδικός Πρόσβασης 33 | 34 | 35 | 36 | 37 | 41 | 42 |
43 | 44 | 45 |

Ξέχασες τον κωδικό σου;

46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/unipi/students/service/StorageService.java: -------------------------------------------------------------------------------- 1 | package com.unipi.students.service; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | 7 | import com.unipi.students.model.Course; 8 | import com.unipi.students.model.Grades; 9 | import com.unipi.students.model.Semester; 10 | 11 | import java.util.ArrayList; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | public class StorageService { 17 | 18 | private static final String PREFS_NAME = "CapacitorStorage"; 19 | private SharedPreferences prefs; 20 | private SharedPreferences.Editor editor; 21 | 22 | public StorageService(Context context) { 23 | prefs = context.getSharedPreferences(PREFS_NAME, Activity.MODE_PRIVATE); 24 | editor = prefs.edit(); 25 | } 26 | 27 | public String getUniversity() { 28 | return prefs.getString("university", null); 29 | } 30 | 31 | public String getUsername() { 32 | return prefs.getString("username", null); 33 | } 34 | 35 | public String getPassword() { 36 | return prefs.getString("password", null); 37 | } 38 | 39 | public void removePassword() { 40 | editor.remove("password"); 41 | editor.apply(); 42 | } 43 | 44 | public String getStudent() { 45 | return prefs.getString("student", null); 46 | } 47 | 48 | public String getRememberMe() { 49 | return prefs.getString("remember_me", null); 50 | } 51 | 52 | public void removeRememberMe() { 53 | editor.remove("remember_me"); 54 | editor.apply(); 55 | } 56 | 57 | public Map compareGrades(Grades oldGrades, Grades newGrades) { 58 | Map newCourses = new HashMap<>(); 59 | List oldCourses = new ArrayList<>(); 60 | 61 | for (Semester semester : oldGrades.getSemesters()) 62 | oldCourses.addAll(semester.getCourses()); 63 | 64 | for (Semester semester : newGrades.getSemesters()) { 65 | for (Course course : semester.getCourses()) { 66 | int i = 0; 67 | while (oldCourses.size() > 0) { 68 | if (course.getId().equals(oldCourses.get(i).getId())) { 69 | if (!course.getExamPeriod().equals(oldCourses.get(i).getExamPeriod())) { 70 | newCourses.put(course, semester.getId()); 71 | } 72 | oldCourses.remove(i); 73 | break; 74 | } 75 | else { 76 | i++; 77 | } 78 | } 79 | } 80 | } 81 | return newCourses; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/app/pages/universities/universities.page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Ίδρυμα 4 | 5 | 6 | 7 | 8 | 9 | Ίδρυμα 10 | 11 | 12 | 13 | 14 | 15 |

Ίδρυμα

16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |

Εθνικό Καποδιστριακό
Πανεπιστήμιο

24 |
25 |
26 |
27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |

Πανεπιστήμιο
Δυτικής Αττικής

36 |
37 |
38 |
39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |

Πανεπιστήμιο
Πειραιώς

48 |
49 |
50 |
51 |
52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |

Πάντειο
Πανεπιστήμιο

60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/unipi/students/plugin/FCMPlugin.java: -------------------------------------------------------------------------------- 1 | package com.unipi.students.plugin; 2 | 3 | import com.getcapacitor.JSObject; 4 | import com.getcapacitor.NativePlugin; 5 | import com.getcapacitor.Plugin; 6 | import com.getcapacitor.PluginCall; 7 | import com.getcapacitor.PluginMethod; 8 | import com.google.firebase.iid.FirebaseInstanceId; 9 | import com.google.firebase.messaging.FirebaseMessaging; 10 | 11 | import java.io.IOException; 12 | 13 | @NativePlugin() 14 | public class FCMPlugin extends Plugin { 15 | 16 | @PluginMethod() 17 | public void subscribeTo(final PluginCall call) { 18 | final String topicName = call.getString("topic"); 19 | 20 | FirebaseMessaging 21 | .getInstance() 22 | .subscribeToTopic(topicName) 23 | .addOnSuccessListener(aVoid -> { 24 | JSObject ret = new JSObject(); 25 | ret.put("message", "Subscribed to topic " + topicName); 26 | call.success(ret); 27 | }) 28 | .addOnFailureListener(e -> call.error("Cant subscribe to topic" + topicName, e)); 29 | 30 | } 31 | 32 | @PluginMethod() 33 | public void unsubscribeFrom(final PluginCall call) { 34 | final String topicName = call.getString("topic"); 35 | 36 | FirebaseMessaging 37 | .getInstance() 38 | .unsubscribeFromTopic(topicName) 39 | .addOnSuccessListener(aVoid -> { 40 | JSObject ret = new JSObject(); 41 | ret.put("message", "Unsubscribed from topic " + topicName); 42 | call.success(ret); 43 | }) 44 | .addOnFailureListener(e -> call.error("Cant unsubscribe from topic" + topicName, e)); 45 | 46 | } 47 | 48 | @PluginMethod() 49 | public void deleteInstance(final PluginCall call) { 50 | Runnable r = () -> { 51 | try { 52 | FirebaseInstanceId.getInstance().deleteInstanceId(); 53 | call.success(); 54 | } catch (IOException e) { 55 | e.printStackTrace(); 56 | call.error("Cant delete Firebase Instance ID", e); 57 | } 58 | }; 59 | new Thread(r).start(); 60 | } 61 | 62 | @PluginMethod() 63 | public void getToken(final PluginCall call) { 64 | FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(getActivity(), instanceIdResult -> { 65 | JSObject data = new JSObject(); 66 | data.put("token", instanceIdResult.getToken()); 67 | call.success(data); 68 | }); 69 | FirebaseInstanceId.getInstance().getInstanceId().addOnFailureListener(e -> call.error("Failed to get instance FirebaseID", e)); 70 | } 71 | } -------------------------------------------------------------------------------- /src/app/shared/services/network.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { NetworkStatus, Plugins } from '@capacitor/core'; 3 | import { Router } from '@angular/router'; 4 | import { ToastService } from './toast.service'; 5 | import { StudentService } from './student.service'; 6 | 7 | const { Network } = Plugins; 8 | 9 | @Injectable({ 10 | providedIn: 'root' 11 | }) 12 | export class NetworkService { 13 | 14 | private _networkStatus: NetworkStatus; 15 | 16 | constructor( 17 | private router: Router, 18 | private toastService: ToastService, 19 | private studentService: StudentService 20 | ) {} 21 | 22 | async init() { 23 | this.networkStatus = await Network.getStatus(); 24 | return Network.addListener('networkStatusChange', (status) => { 25 | if (status.connected !== this.networkStatus.connected) { 26 | this.networkStatus = status; 27 | if (status.connected === false) { 28 | if ((this.router.isActive('/login', true) && this.router.url === '/login') || 29 | ((this.router.isActive('/universities', true) && this.router.url === '/universities')) || 30 | ((this.router.isActive('/welcome', true) && this.router.url === '/welcome'))) { 31 | this.toastService.presentSimple('Είσαι offline!'); 32 | } else { 33 | this.toastService.present('Είσαι offline!'); 34 | } 35 | } else if (status.connected === true) { 36 | if (this.router.isActive('/offline-login', true) && this.router.url === '/offline-login') { 37 | const rememberMe = this.studentService.rememberMe; 38 | if (rememberMe === null) { 39 | this.router.navigateByUrl('/universities'); 40 | this.toastService.presentSimple('Είσαι πάλι online!'); 41 | } else if (rememberMe === false) { 42 | this.router.navigateByUrl('/login'); 43 | this.toastService.presentSimple('Είσαι πάλι online!'); 44 | } else if (rememberMe === true) { 45 | this.router.navigate(['/app/tabs/tab1']); 46 | this.toastService.present('Είσαι πάλι online!'); 47 | } 48 | } else if ((this.router.isActive('/universities', true) && this.router.url === '/universities') || 49 | (this.router.isActive('/login', true) && this.router.url === '/login') || 50 | (this.router.isActive('/welcome', true) && this.router.url === '/welcome')) { 51 | this.toastService.presentSimple('Είσαι πάλι online!'); 52 | } else { 53 | this.toastService.present('Είσαι πάλι online!'); 54 | } 55 | } 56 | } 57 | }); 58 | } 59 | 60 | get networkStatus(): NetworkStatus { 61 | return this._networkStatus; 62 | } 63 | 64 | set networkStatus(value: NetworkStatus) { 65 | this._networkStatus = value; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /android/app/src/main/res/raw/panteion.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIH5jCCBc6gAwIBAgIRALDLE7y89ulzDZXNzYhpJ3owDQYJKoZIhvcNAQEMBQAw 3 | RDELMAkGA1UEBhMCTkwxGTAXBgNVBAoTEEdFQU5UIFZlcmVuaWdpbmcxGjAYBgNV 4 | BAMTEUdFQU5UIE9WIFJTQSBDQSA0MB4XDTIwMDcyMTAwMDAwMFoXDTIyMDcyMTIz 5 | NTk1OVowgdwxCzAJBgNVBAYTAkdSMQ4wDAYDVQQREwUxNzY3MTEPMA0GA1UECBMG 6 | QXR0aWtpMQ8wDQYDVQQHEwZBdGhlbnMxLDAqBgNVBAkTI1N5bmdyb3UgQXYuIDEz 7 | NiBLYWxsaXRoZWEsIDE3NjcxIEdSMTswOQYDVQQKDDJQYW50ZWlvbiBVbml2ZXJz 8 | aXR5IG9mIFBvbGl0aWNhbCAmIFNvY2lhbCBTY2llbmNlczEVMBMGA1UECxMMRm9p 9 | dGl0b2xvZ2lvMRkwFwYDVQQDExBmb2l0LnBhbnRlaW9uLmdyMIIBIjANBgkqhkiG 10 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsVhXJZ1fFnYkKXSVT9t14ke9dWhRjISBmEqS 11 | bc/xagtKl/t4CdqkO/EJqSr7Eae8DA7zs1lMCoz6MtbEHTiAFwZGqCeYBiYbpZKz 12 | /sYERt8E9/lxt1DVyPmyI7QGsf3298+yai0yXc47AOkBhG2L/ovTPR/8I2JoFHUP 13 | Zc9cVPESFKLTALKUVlD+SrZbwDG12w0itF/LuX47KEl2IgK3kyX17P01/MeGsvEC 14 | b0oIK+x8Tvx5zi3+2CnCpatVM/LIwMMD44QyIvg6rncWmePI8a8+W1DKTrkDIkya 15 | +C7WV+VYsZFCgDuaupHSVdy/65NjRHRlrb/5ziYR9CjxZ1bMgQIDAQABo4IDODCC 16 | AzQwHwYDVR0jBBgwFoAUbx01SRBsMvpZoJ68iugflb5xegwwHQYDVR0OBBYEFAuL 17 | ZnMHQDiRwrmMkA6aq46+9hqgMA4GA1UdDwEB/wQEAwIFoDAMBgNVHRMBAf8EAjAA 18 | MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjBJBgNVHSAEQjBAMDQGCysG 19 | AQQBsjEBAgJPMCUwIwYIKwYBBQUHAgEWF2h0dHBzOi8vc2VjdGlnby5jb20vQ1BT 20 | MAgGBmeBDAECAjA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vR0VBTlQuY3JsLnNl 21 | Y3RpZ28uY29tL0dFQU5UT1ZSU0FDQTQuY3JsMHUGCCsGAQUFBwEBBGkwZzA6Bggr 22 | BgEFBQcwAoYuaHR0cDovL0dFQU5ULmNydC5zZWN0aWdvLmNvbS9HRUFOVE9WUlNB 23 | Q0E0LmNydDApBggrBgEFBQcwAYYdaHR0cDovL0dFQU5ULm9jc3Auc2VjdGlnby5j 24 | b20wMQYDVR0RBCowKIIQZm9pdC5wYW50ZWlvbi5ncoIUd3d3LmZvaXQucGFudGVp 25 | b24uZ3IwggF9BgorBgEEAdZ5AgQCBIIBbQSCAWkBZwB2AEalVet1+pEgMLWiiWn0 26 | 830RLEF0vv1JuIWr8vxw/m1HAAABc3FinicAAAQDAEcwRQIhAL3ppDEX3sV9v/Wt 27 | BWjDSS8xLBE766uMdDHRMjlZQOQdAiA69dKDpTIJhPi4AlUMkCH/C07NaywS11vt 28 | 1LBcvmnnuwB1AN+lXqtogk8fbK3uuF9OPlrqzaISpGpejjsSwCBEXCpzAAABc3Fi 29 | nk8AAAQDAEYwRAIgdY9WFX5HGjry5ZJWi7MFES1mkL6OiwD+mDf3orpsjcQCIE0v 30 | i0XE4rvLIWnfAo1pYTY9zv98ys8jCeyOe4r5mhR0AHYAb1N2rDHwMRnYmQCkURX/ 31 | dxUcEdkCwQApBo2yCJo32RMAAAFzcWKeIAAABAMARzBFAiAD04k8K1ydgESaQZ/b 32 | eiFRnkAqwoZQlB7AQWuWg8LcKQIhAMax6/I3XSFD8dsUmPY+Ob/mF6vpGOG3BdyQ 33 | o7xBRKdGMA0GCSqGSIb3DQEBDAUAA4ICAQBtJx/ZSNGR/7gjFT6bh/NAHhIyvppJ 34 | /H8Yl1T7Cb68NXSqVMoq0M6iwdrxFaKXSdBvh//13wGFxXRfFb5xXRmr0/IHo3XL 35 | vAmEv52yJL9PhDtt/XBQxulaeCKoo0qqbLfC6fsjxwBurfhovBeXHxS0HFJDrO+e 36 | Tq8WYRbsiS7dtWlFGdAjPJ6ukXnfrkd9n7wByMIDbZpfgHKIOFu3cXWJAUAvLRQc 37 | pWp8gVlvtXrcxTtRVDkLSTVhS+8LQ3q80EwVcQCm7Pj8ZtI/oZfJr6GFAcjcqMfU 38 | gWOm18awGvffe1vBspM0lkswlvNNWEl699ccEpdeq5rcNNbMmOZt+9HH33PDbUq/ 39 | zyJCaJoDaf6UwKTHDE5aEUaAg0LKUThX6KJ9ZS7Mg1WsywlQVvahxnQa8ar2LR26 40 | /yAQx4tjKdnm4wpx3dTsZOF6HGdQGesqUr+bsvKlL0QvVOCA79zM4KcEhRRuGtS0 41 | pbWepY1YUGUFHxbhz42yrUY3PaIsrgH90aDaE7FihNx5daRzjmv/le/7SV5ZVptj 42 | u2wa0wQc1rJLiTl0gyjHASKsedJuAWGSd6ec+mIB4TSI47nELiksSG2ktN0trp1s 43 | H+DZzy4Lr14BBETjljuTHkmyElCGIF2zkwSW+rqlKRlXljcgvUg3VdZydvao1IOC 44 | DvpWWdgpToRK+g== 45 | -----END CERTIFICATE----- 46 | -------------------------------------------------------------------------------- /src/app/modals/faq/faq.page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | FAQ 4 | 5 | Πίσω 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Συχνές Ερωτήσεις

14 |
15 | 16 |
Που αποθηκεύονται οι κωδικοί μου και τα στοιχεία μου;
17 |

Επιλέγοντας τη λειτουργία “Να με θυμάσαι” αποθηκεύουμε τους κωδικούς σου και τα στοιχεία σου τοπικά στο κινητό σου, πάντα κρυπτογραφημένα, προσφέροντάς σου τη μέγιστη ασφάλεια!

18 | 19 |
Έχασα τους κωδικούς μου, που θα χρειαστεί να απευθυνθώ;
20 |

Τα περισσότερα ιδρύματα παρέχουν μια αυτοματοποιημένη υπηρεσία αλλαγής κωδικού σε περίπτωση που τον έχεις ξεχάσει. Διαφορετικά θα πρέπει να απευθυνθείς στη γραμματεία του τμήματός σου.

21 | 22 |
“Κάτι πήγε λάθος, δοκίμασε ξανά αργότερα!” τι σημαίνει;
23 |

Αν δεις αυτό το μήνυμα κατά τη σύνδεσή σου σημαίνει πως δεν καταφέραμε να σε συνδέσουμε με τη πλατφόρμα του ιδρύματός σου. Αυτό μπορεί να συμβαίνει για πολλούς και διαφορετικούς λόγους. Μερικοί από αυτούς είναι οι παρακάτω:

24 | 25 |
    26 |
  1. Η πλατφόρμα του ιδρύματός σου δεν λειτουργεί προσωρινά
  2. 27 |
  3. Η σύνδεσή σου διακόπηκε λόγω απώλειας σήματος
  4. 28 |
  5. Ο ακαδημαϊκός σου λογαριασμός δεν είναι ενεργός
  6. 29 |
30 | 31 |

Αν δεν ισχύει τίποτα από τα παραπάνω πολύ πιθανόν να υπάρχει κάποιο πρόβλημα από τη μεριά μας. Σε αυτή τη περίπτωση μπορείς να μας στείλεις ένα μήνυμα να το κοιτάξουμε παρέα.

32 | 33 |
Η εφαρμογή είναι η επίσημα υποστηριζόμενη από το ίδρυμά μου;
34 |

Η εφαρμογή UniStudents δεν έχει καμία σχέση με τα ιδρύματα που υποστηρίζει και αποτελεί μια ανεξάρτητη υλοποίηση από φοιτητές για φοιτητές.

35 | 36 |
Βγήκε νέος βαθμός και δε μου ήρθε ειδοποίηση. Γιατί;
37 |

Να σε ενημερώσουμε πως η λειτουργία αυτή είναι νέα προσθήκη του UniStudents και για την ώρα τρέχει σε δοκιμαστικό επίπεδο. Ελπίζουμε να μην αντιμετωπίσεις κάποιο πρόβλημα, αλλά αν κάτι πάει λάθος βεβαιώσου πως ισχύουν τα παρακάτω:

38 | 39 |
    40 |
  1. Έχεις ενεργοποιήσει τη λειτουργία “Να με θυμάσαι”
  2. 41 |
  3. Έχεις ενεργοποιήσει τις ειδοποιήσεις “Νέος Βαθμός”
  4. 42 |
  5. Αν διαθέτεις Xiaomi συσκευή, έχεις δώσει άδεια να λαμβάνεις ειδοποιήσεις για αυτή την εφαρμογή
  6. 43 |
44 | 45 |

Αν έχεις ενεργοποιημένα τα παραπάνω αλλά δεν έλαβες ειδοποίηση στείλε μας ένα μήνυμα να το κοιτάξουμε παρέα.

46 | 47 |
Πως υπολογίζεται ο μέσος όρος μου;
48 |

Στις περιπτώσεις που ο μέσος όρος σου υπολογίζεται από τη πλατφόρμα του ιδρύματός σου τον παίρνουμε έτοιμο. Ωστόσο στις περιπτώσεις που δεν υπολογίζεται από την επίσημη πλατφόρμα του ιδρύματός σου, τον υπολογίζουμε εμείς λαμβάνοντας πως όλα τα μαθήματα προσμετρώνται στο Μ.Ο σου.

49 | 50 |
51 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags.ts'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | import './zone-flags'; 56 | 57 | /*************************************************************************************************** 58 | * Zone JS is required by default for Angular itself. 59 | */ 60 | 61 | import 'zone.js/dist/zone'; // Included with Angular CLI. 62 | 63 | 64 | /*************************************************************************************************** 65 | * APPLICATION IMPORTS 66 | */ 67 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 14 | 15 | 18 | 21 | 22 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 50 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/app/shared/services/student.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { StorageService } from './storage.service'; 3 | import { CryptoService } from './crypto.service'; 4 | 5 | @Injectable({ 6 | providedIn: 'root' 7 | }) 8 | export class StudentService { 9 | 10 | private _username: string; 11 | private _password: string; 12 | private _rememberMe: boolean; 13 | private _isLoggedIn: boolean; 14 | private _notificationsForNewGrades: boolean; 15 | private _notificationsForGeneralUpdates: boolean; 16 | 17 | constructor( 18 | private storageService: StorageService, 19 | private cryptoService: CryptoService 20 | ) { } 21 | 22 | async init() { 23 | const username = await this.storageService.getUsername(); 24 | this.username = username.value; 25 | 26 | const password = await this.storageService.getPassword(); 27 | if (password.value !== null) { 28 | this.password = this.cryptoService.decrypt(password.value); 29 | } 30 | 31 | const rememberMe = await this.storageService.getRememberMe(); 32 | if (rememberMe.value === null) { 33 | this.rememberMe = null; 34 | this.isLoggedIn = false; 35 | this.notificationsForNewGrades = false; 36 | } else if (rememberMe.value === 'false') { 37 | this.rememberMe = false; 38 | this.isLoggedIn = false; 39 | this.notificationsForNewGrades = false; 40 | } else if (rememberMe.value === 'true') { 41 | this.rememberMe = true; 42 | this.isLoggedIn = true; 43 | const notificationsForNewGrades = await this.storageService.getNewGradeNotification(); 44 | if (notificationsForNewGrades.value === 'true') { 45 | this.notificationsForNewGrades = true; 46 | } else if (notificationsForNewGrades.value === 'false') { 47 | this.notificationsForNewGrades = false; 48 | } 49 | } 50 | 51 | const notificationsForGeneralUpdates = await this.storageService.getUpdatesNotification(); 52 | if (notificationsForGeneralUpdates.value === null) { 53 | this.notificationsForGeneralUpdates = true; 54 | } else if (notificationsForGeneralUpdates.value === 'true') { 55 | this.notificationsForGeneralUpdates = true; 56 | } else if (notificationsForGeneralUpdates.value === 'false') { 57 | this.notificationsForGeneralUpdates = false; 58 | } 59 | } 60 | 61 | get username(): string { 62 | return this._username; 63 | } 64 | 65 | set username(value: string) { 66 | this._username = value; 67 | } 68 | 69 | get password(): string { 70 | return this._password; 71 | } 72 | 73 | set password(value: string) { 74 | this._password = value; 75 | } 76 | 77 | get isLoggedIn(): boolean { 78 | return this._isLoggedIn; 79 | } 80 | 81 | set isLoggedIn(value: boolean) { 82 | this._isLoggedIn = value; 83 | } 84 | 85 | get rememberMe(): boolean { 86 | return this._rememberMe; 87 | } 88 | 89 | set rememberMe(value: boolean) { 90 | this._rememberMe = value; 91 | } 92 | 93 | get notificationsForNewGrades(): boolean { 94 | return this._notificationsForNewGrades; 95 | } 96 | 97 | set notificationsForNewGrades(value: boolean) { 98 | this._notificationsForNewGrades = value; 99 | } 100 | 101 | get notificationsForGeneralUpdates(): boolean { 102 | return this._notificationsForGeneralUpdates; 103 | } 104 | 105 | set notificationsForGeneralUpdates(value: boolean) { 106 | this._notificationsForGeneralUpdates = value; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Feature Image](https://user-images.githubusercontent.com/25327910/92993140-3cf55800-f4f8-11ea-8074-cbe75bdade00.png) 2 | 3 | # UniStudents 4 | 5 | Παρακολούθησε την ακαδημαϊκή σου πορεία πιο εύκολα από ποτέ! Με real time notifications και super clean interface το UniStudents θα γίνει το αγαπημένο σου φοιτητικό app! 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | ### Το UniStudents θα γίνει το αγαπημένο σου φοιτητικό app! 14 | 15 | Σαν φοιτητές και εμείς, καταλαβαίνουμε απόλυτα πόσο αγχώδες μπορούν να γίνουν τα άπειρα refresh που κάνεις τη μέρα, για να μάθεις αν βγήκε επιτέλους νέος βαθμός. 16 | Με το UniStudents όλα αυτά είναι παρελθόν αφού θα λαμβάνεις σε πραγματικό χρόνο ειδοποιήσεις κάθε φορά που ανακοινώνεται νέος βαθμός! 17 | 18 | Και τι να πούμε για μερικά φοιτητολόγια που θυμίζουν συστήματα εικοσαετίας και θέλουν μπόλικη υπομονή και αρκετά zooms για να καταφέρεις να δεις τους βαθμούς σου. 19 | Πλέον θα μπορείς, εύκολα και γρήγορα, να παρακολουθείς τους βαθμούς σου μέσα από ένα απλό και όμορφο interface συμβατό με κάθε συσκευή! 20 | 21 | ## Χαρακτηριστικά 22 | - Real time Notification για ανακοίνωση νέων βαθμών 23 | - Άμεση και ασφαλής σύνδεση 24 | - Πρόσβαση στη βαθμολογία σου ακόμα και εάν είσαι offline 25 | - Εύχρηστο και ευπαρουσίαστο interface 26 | - Χρήσιμα charts για τη παρακολούθηση της προόδου σου 27 | - Ασφαλής αποθήκευση των στοιχείων σου μόνο στη συσκευή σου 28 | 29 | ## Ιδρύματα που υποστηρίζονται 30 | 31 | - [x] Α.Σ.ΠΑΙ.Τ.Ε 32 | - [x] Αριστοτέλειο Πανεπιστήμιο Θεσσαλονίκης 33 | - [x] Γεωπονικό Πανεπιστήμιο Αθηνών 34 | - [x] Διεθνές Πανεπιστήμιο Αθηνών 35 | - [x] Δημοκρίτειο Πανεπιστήμιο Θράκης 36 | - [x] Εθνικό Μετσόβιο Πολυτεχνείο 37 | - [x] Εθνικό Καποδιστριακό Πανεπιστήμιο 38 | - [x] Ελληνικό Μεσογειακό Πανεπιστήμιο 39 | - [x] Ιόνιο Πανεπιστήμιο 40 | - [x] Οικονομικό Πανεπιστήμιο Αθηνών 41 | - [x] Πανεπιστήμιο Αιγαίου 42 | - [x] Πανεπιστήμιο Δυτικής Αττικής 43 | - [x] Πανεπιστήμιο Δυτικής Μακεδονίας 44 | - [x] Πανεπιστήμιο Ιωαννίνων 45 | - [x] Πανεπιστήμιο Κρήτης 46 | - [x] Πανεπιστήμιο Πατρών 47 | - [x] Πανεπιστήμιο Πειραιώς 48 | - [x] Πανεπιστήμιο Πελοποννήσου 49 | - [x] Πάντειο Πανεπιστήμιο 50 | - [x] Πολυτεχνίο Κρήτης 51 | - [x] Πρώην ΤΕΙ Δυτικής Ελλάδας 52 | - [x] Χαροκόπειο Πανεπιστήμιο 53 | 54 | 55 | ## DISCLAIMER 56 | Η εφαρμογή δεν αποτελεί μέρος των ιδρυμάτων που υποστηρίζει. Πρόκειται για μια ανεπίσημη υλοποίηση της οποίας στόχος είναι, να προσφέρει στο φοιτητή μία καλύτερη εμπειρία στη παρακολούθηση των βαθμών του. Ο χρήστης που επιλέγει την χρήση αυτής της εφαρμογής το κάνει καθαρά με δική του ευθύνη, έχοντας λάβει υπόψη τα παραπάνω. **Η εφαρμογή δεν αποθηκεύει πουθενά τους κωδικούς και τα στοιχεία του χρήστη, εκτός από την συσκευή του, χρησιμοποιώντας υψίστης ασφαλείας κρυπτογράφηση.** Το UniStudents αποτελεί λογισμικό ανοιχτού κώδικα που αναπτύσσεται εξ ολοκλήρου από φοιτητές. Βρες το κώδικα του API στο link: https://github.com/UniStudents/unistudents-api. Κάνε join στο [Discord Server](https://discord.gg/XR5g6DCpgT) μας και έλα να συζητήσουμε για Open Source και το μέλλον του UniStudents. 57 | 58 | ## License & Copyright 59 | 60 | @ Nikos Sklavounos. Licensed under the [MIT License](LICENSE). 61 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/unipi/students/plugin/ScrapePlugin.java: -------------------------------------------------------------------------------- 1 | package com.unipi.students.plugin; 2 | 3 | import android.os.AsyncTask; 4 | 5 | import com.datadog.android.log.Logger; 6 | import com.fasterxml.jackson.core.JsonProcessingException; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | import com.getcapacitor.JSObject; 9 | import com.getcapacitor.NativePlugin; 10 | import com.getcapacitor.Plugin; 11 | import com.getcapacitor.PluginCall; 12 | import com.getcapacitor.PluginMethod; 13 | import com.unipi.students.model.ResponseEntity; 14 | import com.unipi.students.model.Student; 15 | import com.unipi.students.service.ScrapeService; 16 | 17 | import java.util.HashMap; 18 | import java.util.concurrent.ExecutionException; 19 | 20 | @NativePlugin() 21 | public class ScrapePlugin extends Plugin { 22 | 23 | private Logger logger = new Logger.Builder() 24 | .setNetworkInfoEnabled(true) 25 | .setServiceName("com.unipi.students.plugin.ScrapePlugin") 26 | .setLogcatLogsEnabled(true) 27 | .setDatadogLogsEnabled(true) 28 | .setLoggerName("ScrapePlugin") 29 | .build(); 30 | 31 | @PluginMethod() 32 | public void getStudent(PluginCall call) { 33 | String university = call.getString("university"); 34 | String username = call.getString("username"); 35 | String password = call.getString("password"); 36 | 37 | logger.i("[" + university + "]: Scraping task started"); 38 | long startTime = System.currentTimeMillis(); 39 | 40 | ResponseEntity response = null; 41 | Student student = null; 42 | String sStudent = null; 43 | try { 44 | response = new ScrapeTask().execute(university, username, password).get(); 45 | 46 | if (response.getObject() != null) { 47 | ObjectMapper objectMapper = new ObjectMapper(); 48 | student = (Student) response.getObject(); 49 | sStudent = objectMapper.writeValueAsString(student); 50 | } else { 51 | long endTime = System.currentTimeMillis(); 52 | logger.i("[" + university + "]: Scraping task finished with status code " + response.getHttpStatus().value() + ", after " + (endTime - startTime) + " ms"); 53 | call.reject(response.getHttpStatus().getReasonPhrase(), String.valueOf(response.getHttpStatus().value())); 54 | return; 55 | } 56 | } catch (ExecutionException | InterruptedException | JsonProcessingException e) { 57 | long endTime = System.currentTimeMillis(); 58 | logger.e("[" + university + "]: Scraping task finished with status code 500, after " + (endTime - startTime) + " ms"); 59 | call.reject("Internal Server Error", "500"); 60 | return; 61 | } 62 | 63 | long endTime = System.currentTimeMillis(); 64 | Student finalStudent = student; 65 | logger.i("[" + university + "]: Scraping task finished with status code 200, after " + (endTime - startTime) + " ms",null, 66 | new HashMap() {{ 67 | put("department", finalStudent.getInfo().getDepartment()); 68 | put("semester", finalStudent.getInfo().getSemester()); 69 | }}); 70 | JSObject ret = new JSObject(); 71 | ret.put("student", sStudent); 72 | call.success(ret); 73 | } 74 | 75 | private class ScrapeTask extends AsyncTask { 76 | @Override 77 | protected ResponseEntity doInBackground(String... strings) { 78 | return new ScrapeService().getStudent(strings[0], strings[1], strings[2], getContext()); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/app/tab3/tab3.page.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { AlertController, ModalController } from '@ionic/angular'; 4 | import { ThemeModeService } from '../shared/services/theme-mode.service'; 5 | import { StoreService } from '../shared/services/store.service'; 6 | import { RoutingService } from '../shared/services/routing.service'; 7 | import { AboutPage } from '../modals/about/about.page'; 8 | import { FaqPage } from '../modals/faq/faq.page'; 9 | import { SettingsPage } from '../modals/settings/settings.page'; 10 | import { AngularFireAnalytics } from '@angular/fire/analytics'; 11 | import { Info } from '../shared/models/info.model'; 12 | import { Observable } from 'rxjs'; 13 | import { Student } from '../shared/models/student.model'; 14 | 15 | @Component({ 16 | selector: 'app-tab3', 17 | templateUrl: 'tab3.page.html', 18 | styleUrls: ['tab3.page.scss'] 19 | }) 20 | export class Tab3Page { 21 | 22 | public info: Info = null; 23 | private studentObservable: Observable; 24 | darkMode = false; 25 | 26 | constructor( 27 | public alertController: AlertController, 28 | private router: Router, 29 | private themeMode: ThemeModeService, 30 | private storeService: StoreService, 31 | private routingService: RoutingService, 32 | private modalController: ModalController, 33 | private angularFireAnalytics: AngularFireAnalytics 34 | ) { } 35 | 36 | ngOnInit(): void { 37 | this.studentObservable = this.storeService.students; 38 | this.studentObservable.subscribe(res => { 39 | if (res.length !== 0) { 40 | this.info = res[0].info; 41 | } 42 | }); 43 | this.darkMode = this.themeMode.darkMode; 44 | } 45 | 46 | ionViewWillEnter() { 47 | this.routingService.currentPage = '/app/tabs/tab3'; 48 | } 49 | 50 | async logout() { 51 | this.angularFireAnalytics.logEvent('logout', {screen: 'tab3'}); 52 | await this.storeService.logout(); 53 | this.router.navigate(['/universities']); 54 | } 55 | 56 | async logoutAlert() { 57 | const alert = await this.alertController.create({ 58 | header: 'Αποσύνδεση', 59 | message: 'Τα δεδομένα σου θα διαγραφούν απο τη συσκευή!', 60 | buttons: [ 61 | { 62 | text: 'ΑΚΥΡΟ', 63 | role: 'cancel', 64 | handler: () => { 65 | alert.dismiss(); 66 | } 67 | }, { 68 | text: 'ΝΑΙ', 69 | handler: () => { 70 | this.logout(); 71 | } 72 | } 73 | ] 74 | }); 75 | 76 | await alert.present(); 77 | } 78 | 79 | toggleThemeMode() { 80 | this.themeMode.enableDarkMode(this.darkMode); 81 | this.angularFireAnalytics.setUserProperties({DarkMode: this.darkMode}); 82 | } 83 | 84 | async openFaqModal() { 85 | this.routingService.isAnyModalPageOpened = true; 86 | const modal = await this.modalController.create({ 87 | component: FaqPage, 88 | cssClass: 'modal', 89 | swipeToClose: true 90 | }); 91 | modal.onDidDismiss().then(() => this.routingService.isAnyModalPageOpened = false); 92 | return await modal.present(); 93 | } 94 | 95 | async openSettingsModal() { 96 | this.routingService.isAnyModalPageOpened = true; 97 | const modal = await this.modalController.create({ 98 | component: SettingsPage, 99 | cssClass: 'modal', 100 | swipeToClose: true 101 | }); 102 | modal.onDidDismiss().then(() => this.routingService.isAnyModalPageOpened = false); 103 | return await modal.present(); 104 | } 105 | 106 | async openAboutModal() { 107 | this.routingService.isAnyModalPageOpened = true; 108 | const modal = await this.modalController.create({ 109 | component: AboutPage, 110 | cssClass: 'modal', 111 | swipeToClose: true 112 | }); 113 | modal.onDidDismiss().then(() => this.routingService.isAnyModalPageOpened = false); 114 | return await modal.present(); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/app/tab3/tab3.page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Προφίλ 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {{ info?.aem }} 20 | 21 | 22 | 23 | {{ info?.firstName }} {{ info?.lastName }} 24 | 25 | 26 | 27 | {{ info?.department }} 28 | 29 | 30 | 31 | {{ info?.registrationYear }} 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | Dark Mode 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | Ρυθμίσεις 69 | 70 | 71 | 72 | 73 | 74 | FAQ 75 | 76 | 77 | 78 | 79 | 80 | Σχετικά 81 | 82 | 83 | 84 | 85 | 86 | Αποσύνδεση 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/app/tab1/tab1.page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Αρχική 5 | 6 | 7 | 8 | 9 |
10 | 11 | Συγχρονισμός 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |

Συνδέθηκες ως

25 |

{{ student?.info.firstName }} {{ student?.info.lastName }}

26 |
27 | 28 | 29 | 30 | 31 |
32 |
33 |
34 |
35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |

{{ student?.grades.totalPassedCourses }}

51 |

Περασμένα μαθήματα

52 |
53 | 54 | 55 | 56 | 57 |
58 |
59 |
60 |
61 | 62 | 63 | 64 | 65 | 66 |

{{ student?.grades.totalAverageGrade }}

67 |

Μέσος Όρος

68 |
69 | 70 | 71 | 72 | 73 |
74 |
75 |
76 |
77 | 78 | 79 | 80 | 81 | 82 |

{{ student?.grades.totalEcts }}

83 |

-

84 |

ECTs

85 |
86 | 87 | 88 | 89 | 90 |
91 |
92 |
93 |
94 |
95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 |
104 |
105 | -------------------------------------------------------------------------------- /src/app/shared/services/notification.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Plugins, PushNotification, PushNotificationActionPerformed, PushNotificationToken } from '@capacitor/core'; 3 | import { AlertController } from '@ionic/angular'; 4 | import { AngularFireAnalytics } from '@angular/fire/analytics'; 5 | import { ApiService } from './api.service'; 6 | import { Course } from '../models/course.model'; 7 | 8 | const { PushNotifications, FCMPlugin } = Plugins; 9 | 10 | @Injectable({ 11 | providedIn: 'root' 12 | }) 13 | export class NotificationService { 14 | 15 | subscribedTopics: string[] = []; 16 | 17 | constructor( 18 | private apiService: ApiService, 19 | public alertController: AlertController, 20 | private angularFireAnalytics: AngularFireAnalytics 21 | ) { } 22 | 23 | async initNotificationService() { 24 | // Request permission to use push notifications 25 | // iOS will prompt user and return if they granted permission or not 26 | // Android will just grant without prompting 27 | PushNotifications.requestPermission().then(result => { 28 | if (result.granted) { 29 | // Register with Apple / Google to receive push via APNS/FCM 30 | PushNotifications.register(); 31 | } 32 | }); 33 | 34 | // On success, we should be able to receive notifications 35 | PushNotifications.addListener('registration', 36 | (token: PushNotificationToken) => { 37 | console.log('Push registration success, token: ' + token.value); 38 | } 39 | ); 40 | 41 | // Some issue with our setup and push will not work 42 | PushNotifications.addListener('registrationError', 43 | (error: any) => { 44 | console.log('Error on registration: ' + JSON.stringify(error)); 45 | } 46 | ); 47 | 48 | // Show us the notification payload if the app is open on our device 49 | PushNotifications.addListener('pushNotificationReceived', 50 | (notification: PushNotification) => { 51 | this.alert(notification.title, notification.body); 52 | } 53 | ); 54 | 55 | // Method called when tapping on a notification 56 | PushNotifications.addListener('pushNotificationActionPerformed', 57 | (notification: PushNotificationActionPerformed) => { 58 | this.angularFireAnalytics.logEvent('open_notification', 59 | {title: notification.notification.title, body: notification.notification.body}); 60 | } 61 | ); 62 | } 63 | 64 | subscribeToTopics() { 65 | this.subscribedTopics.forEach(topic => { 66 | this.subscribeToTopic(topic); 67 | }); 68 | } 69 | 70 | subscribeToTopic(fTopic: string) { 71 | fTopic = encodeURIComponent(fTopic); 72 | FCMPlugin.subscribeTo({ topic: fTopic }).then(() => { 73 | }, err => { 74 | console.error(err); 75 | }); 76 | } 77 | 78 | unsubscribeFromTopic(fTopic: string) { 79 | this.removeTopic(fTopic); 80 | fTopic = encodeURIComponent(fTopic); 81 | FCMPlugin.unsubscribeFrom({ topic: fTopic }).then(() => { 82 | }, error => { 83 | console.error(error); 84 | }); 85 | } 86 | 87 | unsubscribeFromAllTopics() { 88 | this.removeAllTopics(); 89 | PushNotifications.removeAllListeners(); 90 | FCMPlugin.deleteInstance(); 91 | } 92 | 93 | notifyNewGrade(university: string, course: Course, semester: number, department: string) { 94 | this.unsubscribeFromTopic(university + '.' + course.id); 95 | setTimeout(() => { 96 | this.apiService.notifyForNewCourse(); 97 | }, 3000); 98 | } 99 | 100 | addTopic(topic: string) { 101 | if (!this.subscribedTopics.includes(topic)) { 102 | this.subscribedTopics.push(topic); 103 | } 104 | } 105 | 106 | removeTopic(topic: string) { 107 | const index: number = this.subscribedTopics.indexOf(topic); 108 | if (index !== -1) { 109 | this.subscribedTopics.splice(index, 1); 110 | } 111 | } 112 | 113 | removeAllTopics() { 114 | this.subscribedTopics = []; 115 | } 116 | 117 | async alert(title: string, body: string) { 118 | const alert = await this.alertController.create({ 119 | header: title, 120 | message: body, 121 | buttons: [ 122 | { 123 | text: 'OKAY', 124 | role: 'cancel', 125 | handler: () => { 126 | alert.dismiss(); 127 | } 128 | } 129 | ] 130 | }); 131 | await alert.present(); 132 | } 133 | 134 | removeAllDeliveredNotifications() { 135 | PushNotifications.removeAllDeliveredNotifications(); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/app/modals/settings/settings.page.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { AlertController, ModalController } from '@ionic/angular'; 3 | import { StorageService } from '../../shared/services/storage.service'; 4 | import { CryptoService } from '../../shared/services/crypto.service'; 5 | import { NotificationService } from '../../shared/services/notification.service'; 6 | import { AngularFireAnalytics } from '@angular/fire/analytics'; 7 | import { StudentService } from '../../shared/services/student.service'; 8 | 9 | @Component({ 10 | selector: 'app-settings', 11 | templateUrl: './settings.page.html', 12 | styleUrls: ['./settings.page.scss'], 13 | }) 14 | export class SettingsPage implements OnInit { 15 | 16 | rememberMe = false; 17 | newGrade = false; 18 | appUpdates = false; 19 | 20 | onInit: boolean[] = [false, false, false]; 21 | 22 | constructor( 23 | public alertController: AlertController, 24 | private modalController: ModalController, 25 | private storageService: StorageService, 26 | private cryptoService: CryptoService, 27 | private notificationService: NotificationService, 28 | private studentService: StudentService, 29 | private angularFireAnalytics: AngularFireAnalytics 30 | ) { } 31 | 32 | ngOnInit() { 33 | if (this.studentService.rememberMe === true) { 34 | this.onInit[0] = true; 35 | this.rememberMe = true; 36 | if (this.studentService.notificationsForNewGrades === true) { 37 | this.onInit[1] = true; 38 | this.newGrade = true; 39 | } 40 | } 41 | 42 | if (this.studentService.notificationsForGeneralUpdates === true) { 43 | this.onInit[2] = true; 44 | this.appUpdates = true; 45 | } 46 | } 47 | 48 | async closeModal() { 49 | await this.modalController.dismiss(); 50 | } 51 | 52 | toggleRememberMe() { 53 | if (this.onInit[0]) { 54 | this.onInit[0] = false; 55 | return; 56 | } 57 | 58 | if (this.rememberMe === true) { 59 | this.storageService.saveRememberMe('true').then(() => { 60 | // encrypt, save & store password locally 61 | const password = this.cryptoService.encrypt(this.studentService.password); 62 | this.storageService.savePassword(password).then(() => { 63 | this.studentService.rememberMe = true; 64 | }); 65 | }); 66 | } else if (this.rememberMe === false) { 67 | if (this.newGrade === true) { 68 | this.newGrade = false; 69 | this.alert(); 70 | } 71 | this.storageService.saveRememberMe('false').then(() => { 72 | this.storageService.removePassword().then(() => { 73 | this.studentService.rememberMe = false; 74 | this.studentService.notificationsForNewGrades = false; 75 | }); 76 | }); 77 | } 78 | this.angularFireAnalytics.logEvent('toggle_remember_me_notification', {active: this.rememberMe}); 79 | } 80 | 81 | toggleNewGrade() { 82 | if (this.onInit[1]) { 83 | this.onInit[1] = false; 84 | return; 85 | } 86 | 87 | if (this.newGrade === true) { 88 | this.storageService.saveNewGradeNotification('true').then(() => { 89 | // sub to all topics 90 | this.studentService.notificationsForNewGrades = true; 91 | this.notificationService.subscribeToTopics(); 92 | }); 93 | } else { 94 | this.storageService.saveNewGradeNotification('false').then(() => { 95 | // unsub from all topics 96 | this.studentService.notificationsForNewGrades = false; 97 | this.notificationService.subscribedTopics.forEach(topic => { 98 | this.notificationService.unsubscribeFromTopic(topic); 99 | }); 100 | }); 101 | } 102 | this.angularFireAnalytics.logEvent('toggle_new_grade_notification', {active: this.newGrade}); 103 | } 104 | 105 | toggleAppUpdates() { 106 | if (this.onInit[2]) { 107 | this.onInit[2] = false; 108 | return; 109 | } 110 | 111 | if (this.appUpdates === true) { 112 | this.storageService.saveUpdatesNotification('true').then(() => { 113 | // sub to "UniStudents" topic 114 | this.notificationService.subscribeToTopic('unistudents'); 115 | this.studentService.notificationsForGeneralUpdates = true; 116 | }); 117 | } else { 118 | this.storageService.saveUpdatesNotification('false').then(() => { 119 | // unsub from UniStudents topic 120 | this.notificationService.unsubscribeFromTopic('unistudents'); 121 | this.studentService.notificationsForGeneralUpdates = false; 122 | }); 123 | } 124 | this.angularFireAnalytics.logEvent('toggle_updates_notification', {active: this.appUpdates}); 125 | } 126 | 127 | async alert() { 128 | const alert = await this.alertController.create({ 129 | header: 'Quick reminder', 130 | cssClass: 'alert', 131 | message: 'Απενεργοποιώντας τη λειτουργία αυτή δεν θα μπορείς να λαμβάνεις ειδοποιήσεις για νέους βαθμούς', 132 | buttons: [ 133 | { 134 | text: 'OKAY', 135 | role: 'cancel', 136 | handler: () => { 137 | alert.dismiss(); 138 | } 139 | } 140 | ] 141 | }); 142 | 143 | await alert.present(); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/app/tab2/tab2.page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Βαθμολογία 5 | 6 | 7 | 8 | 9 |
10 | 11 | Συγχρονισμός 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 |

Εξάμηνο {{ semester.id }}

26 |
27 | 28 | 29 | 30 | 31 | 32 |
{{ course.id }}
33 |
34 | 35 |

{{ course.name }}

36 |
37 | 38 |
{{ course.type + ' | ' + course.examPeriod }}
39 |
{{ course.examPeriod }}
40 |
41 |
42 | 43 | 44 |

{{ course.grade }}

45 |
46 |
47 |
48 |
49 |
50 |
51 | 52 |
53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 |
138 |
139 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | Graduation -------------------------------------------------------------------------------- /android/app/src/main/java/com/unipi/students/worker/ScrapeDataWorker.java: -------------------------------------------------------------------------------- 1 | package com.unipi.students.worker; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.work.Worker; 8 | import androidx.work.WorkerParameters; 9 | 10 | import com.fasterxml.jackson.core.JsonProcessingException; 11 | import com.fasterxml.jackson.databind.ObjectMapper; 12 | import com.unipi.students.common.HttpStatus; 13 | import com.unipi.students.model.Course; 14 | import com.unipi.students.model.ResponseEntity; 15 | import com.unipi.students.model.Student; 16 | import com.unipi.students.service.ApiService; 17 | import com.unipi.students.service.CryptoService; 18 | import com.unipi.students.service.ScrapeService; 19 | import com.unipi.students.service.StorageService; 20 | 21 | import java.util.Calendar; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | public class ScrapeDataWorker extends Worker { 26 | private static final String TAG = ScrapeDataWorker.class.getSimpleName(); 27 | private StorageService storageService; 28 | private CryptoService cryptoService; 29 | private ApiService apiService; 30 | 31 | public ScrapeDataWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { 32 | super(context, workerParams); 33 | storageService = new StorageService(context); 34 | cryptoService = new CryptoService(); 35 | apiService = new ApiService(); 36 | } 37 | 38 | @NonNull 39 | @Override 40 | public Result doWork() { 41 | Log.d(TAG, "doWork: [START NEW WORK]"); 42 | 43 | // check if scraping is available based on some constrains 44 | if (!scrapeIsAvailable()) { 45 | return Result.success(); 46 | } 47 | 48 | try { 49 | // get stored data: uni, username, pass 50 | String university = storageService.getUniversity(); 51 | String username = storageService.getUsername(); 52 | String password = storageService.getPassword(); 53 | 54 | // decrypt password... 55 | password = cryptoService.decrypt(password); 56 | Log.d(TAG, "doWork: " + university + " | " + username); 57 | 58 | // scrape data, get newStudent 59 | ResponseEntity responseEntity = new ScrapeService().getStudent(university, username, password, getApplicationContext()); 60 | 61 | if (responseEntity.getObject() == null) { 62 | if (responseEntity.getHttpStatus() == HttpStatus.UNAUTHORIZED) { 63 | storageService.removePassword(); 64 | storageService.removeRememberMe(); 65 | return Result.failure(); 66 | } 67 | return Result.failure(); 68 | } 69 | 70 | Student newStudent = (Student) responseEntity.getObject(); 71 | Student storedStudent = getStoredStudent(); 72 | 73 | // compare newStudent with storedStudent 74 | Map newCourses; 75 | if (storedStudent == null) { return Result.failure(); } 76 | if (storedStudent.getInfo().getAem().equals(newStudent.getInfo().getAem())) { 77 | newCourses = storageService.compareGrades(storedStudent.getGrades(), newStudent.getGrades()); 78 | 79 | // if newGrades found, notify server 80 | for (Map.Entry entry : newCourses.entrySet()) { 81 | apiService.notifyForNewGrade(entry.getKey(), university, storedStudent.getInfo().getDepartment(), entry.getValue()); 82 | } 83 | } 84 | else { return Result.failure(); } 85 | 86 | Log.d(TAG, "doWork: [COMPLETE WORK]"); 87 | return Result.success(); 88 | } catch (Exception e) { 89 | e.printStackTrace(); 90 | return Result.failure(); 91 | } 92 | } 93 | 94 | @Override 95 | public void onStopped() { 96 | super.onStopped(); 97 | Log.i(TAG, "OnStopped called for this worker"); 98 | } 99 | 100 | private boolean scrapeIsAvailable() { 101 | // get current day &time 102 | Calendar c = Calendar.getInstance(); 103 | int dayOfDay = c.get(Calendar.DAY_OF_WEEK); 104 | int timeOfDay = c.get(Calendar.HOUR_OF_DAY); 105 | 106 | // scraping is available MON to FRI 107 | if (dayOfDay == Calendar.SATURDAY || dayOfDay == Calendar.SUNDAY) { 108 | Log.d(TAG, "scrapeIsAvailable: NOT AVAILABLE " + timeOfDay + ", " + dayOfDay); 109 | return false; 110 | } 111 | 112 | // scraping is available 8:00 to 17:00 113 | if ((timeOfDay <= 7) || (timeOfDay >= 18)) { 114 | Log.d(TAG, "scrapeIsAvailable: NOT AVAILABLE " + timeOfDay + ", " + dayOfDay); 115 | return false; 116 | } 117 | 118 | // scraping is available only on "remember_me" mode 119 | String rememberMe = storageService.getRememberMe(); 120 | if (rememberMe == null) { 121 | Log.d(TAG, "scrapeIsAvailable: NOT AVAILABLE " + timeOfDay + ", " + dayOfDay + ", " + rememberMe); 122 | return false; 123 | } 124 | else if (!rememberMe.equals("true")) { 125 | Log.d(TAG, "scrapeIsAvailable: NOT AVAILABLE " + timeOfDay + ", " + dayOfDay + ", " + rememberMe); 126 | return false; 127 | } 128 | 129 | Log.d(TAG, "scrapeIsAvailable: AVAILABLE " + timeOfDay + ", " + dayOfDay + ", " + rememberMe); 130 | return true; 131 | } 132 | 133 | private Student convertToStudent(String jsonStudent) { 134 | ObjectMapper mapper = new ObjectMapper(); 135 | Student student; 136 | try { 137 | student = mapper.readValue(jsonStudent, Student.class); 138 | return student; 139 | } catch (JsonProcessingException e) { 140 | e.printStackTrace(); 141 | return null; 142 | } 143 | } 144 | 145 | private Student getStoredStudent() { 146 | String studentJson = storageService.getStudent(); 147 | if (studentJson != null) { 148 | return convertToStudent(studentJson); 149 | } 150 | return null; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "defaultProject": "app", 5 | "newProjectRoot": "projects", 6 | "projects": { 7 | "app": { 8 | "root": "", 9 | "sourceRoot": "src", 10 | "projectType": "application", 11 | "prefix": "app", 12 | "schematics": {}, 13 | "architect": { 14 | "build": { 15 | "builder": "@angular-devkit/build-angular:browser", 16 | "options": { 17 | "outputPath": "www", 18 | "index": "src/index.html", 19 | "main": "src/main.ts", 20 | "polyfills": "src/polyfills.ts", 21 | "tsConfig": "tsconfig.app.json", 22 | "assets": [ 23 | { 24 | "glob": "**/*", 25 | "input": "src/assets", 26 | "output": "assets" 27 | }, 28 | { 29 | "glob": "**/*.svg", 30 | "input": "node_modules/ionicons/dist/ionicons/svg", 31 | "output": "./svg" 32 | } 33 | ], 34 | "styles": [ 35 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 36 | { 37 | "input": "src/theme/variables.scss" 38 | }, 39 | { 40 | "input": "src/global.scss" 41 | } 42 | ], 43 | "scripts": [] 44 | }, 45 | "configurations": { 46 | "production": { 47 | "fileReplacements": [ 48 | { 49 | "replace": "src/environments/environment.ts", 50 | "with": "src/environments/environment.prod.ts" 51 | } 52 | ], 53 | "optimization": true, 54 | "outputHashing": "all", 55 | "sourceMap": false, 56 | "extractCss": true, 57 | "namedChunks": false, 58 | "aot": true, 59 | "extractLicenses": true, 60 | "vendorChunk": false, 61 | "buildOptimizer": true, 62 | "budgets": [ 63 | { 64 | "type": "initial", 65 | "maximumWarning": "2mb", 66 | "maximumError": "5mb" 67 | } 68 | ] 69 | }, 70 | "ci": { 71 | "progress": false 72 | } 73 | } 74 | }, 75 | "serve": { 76 | "builder": "@angular-devkit/build-angular:dev-server", 77 | "options": { 78 | "browserTarget": "app:build" 79 | }, 80 | "configurations": { 81 | "production": { 82 | "browserTarget": "app:build:production" 83 | }, 84 | "ci": { 85 | "progress": false 86 | } 87 | } 88 | }, 89 | "extract-i18n": { 90 | "builder": "@angular-devkit/build-angular:extract-i18n", 91 | "options": { 92 | "browserTarget": "app:build" 93 | } 94 | }, 95 | "test": { 96 | "builder": "@angular-devkit/build-angular:karma", 97 | "options": { 98 | "main": "src/test.ts", 99 | "polyfills": "src/polyfills.ts", 100 | "tsConfig": "tsconfig.spec.json", 101 | "karmaConfig": "karma.conf.js", 102 | "styles": [ 103 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css" 104 | ], 105 | "scripts": [], 106 | "assets": [ 107 | { 108 | "glob": "favicon.ico", 109 | "input": "src/", 110 | "output": "/" 111 | }, 112 | { 113 | "glob": "**/*", 114 | "input": "src/assets", 115 | "output": "/assets" 116 | } 117 | ] 118 | }, 119 | "configurations": { 120 | "ci": { 121 | "progress": false, 122 | "watch": false 123 | } 124 | } 125 | }, 126 | "lint": { 127 | "builder": "@angular-devkit/build-angular:tslint", 128 | "options": { 129 | "tsConfig": [ 130 | "tsconfig.app.json", 131 | "tsconfig.spec.json", 132 | "e2e/tsconfig.json" 133 | ], 134 | "exclude": [ 135 | "**/node_modules/**" 136 | ] 137 | } 138 | }, 139 | "e2e": { 140 | "builder": "@angular-devkit/build-angular:protractor", 141 | "options": { 142 | "protractorConfig": "e2e/protractor.conf.js", 143 | "devServerTarget": "app:serve" 144 | }, 145 | "configurations": { 146 | "production": { 147 | "devServerTarget": "app:serve:production" 148 | }, 149 | "ci": { 150 | "devServerTarget": "app:serve:ci" 151 | } 152 | } 153 | }, 154 | "ionic-cordova-build": { 155 | "builder": "@ionic/angular-toolkit:cordova-build", 156 | "options": { 157 | "browserTarget": "app:build" 158 | }, 159 | "configurations": { 160 | "production": { 161 | "browserTarget": "app:build:production" 162 | } 163 | } 164 | }, 165 | "ionic-cordova-serve": { 166 | "builder": "@ionic/angular-toolkit:cordova-serve", 167 | "options": { 168 | "cordovaBuildTarget": "app:ionic-cordova-build", 169 | "devServerTarget": "app:serve" 170 | }, 171 | "configurations": { 172 | "production": { 173 | "cordovaBuildTarget": "app:ionic-cordova-build:production", 174 | "devServerTarget": "app:serve:production" 175 | } 176 | } 177 | } 178 | } 179 | } 180 | }, 181 | "cli": { 182 | "defaultCollection": "@ionic/angular-toolkit" 183 | }, 184 | "schematics": { 185 | "@ionic/angular-toolkit:component": { 186 | "styleext": "scss" 187 | }, 188 | "@ionic/angular-toolkit:page": { 189 | "styleext": "scss" 190 | } 191 | } 192 | } -------------------------------------------------------------------------------- /src/app/shared/services/storage.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Student } from '../models/student.model'; 3 | import { Grades } from '../models/grades.model'; 4 | import { ToastService } from './toast.service'; 5 | import { Plugins } from '@capacitor/core'; 6 | import { NotificationService } from './notification.service'; 7 | import { Course } from '../models/course.model'; 8 | 9 | const { Storage } = Plugins; 10 | 11 | @Injectable({ 12 | providedIn: 'root' 13 | }) 14 | export class StorageService { 15 | 16 | public newGrades = 0; 17 | public newGradesList: Array = []; 18 | private STUDENT_KEY = 'student'; 19 | private USERNAME_KEY = 'username'; 20 | private PASSWORD_KEY = 'password'; 21 | private UNIVERSITY_KEY = 'university'; 22 | private THEME_MODE_KEY = 'theme_mode'; 23 | private REMEMBER_ME_KEY = 'remember_me'; 24 | private NEW_GRADE_NOTIFICATION = 'new_grade_notification'; 25 | private UPDATES_NOTIFICATION = 'updates_notification'; 26 | private APP_VERSION_KEY = 'app_version'; 27 | private IS_FIRST_TIME = 'is_first_time'; 28 | 29 | constructor( 30 | private toastService: ToastService, 31 | private notificationService: NotificationService 32 | ) { } 33 | 34 | saveStudent(student: Student) { 35 | return Storage.set({ key: this.STUDENT_KEY, value: JSON.stringify({ 36 | info: student.info, 37 | grades: student.grades 38 | }) }); 39 | } 40 | 41 | saveGrades(grades: Grades) { 42 | this.getStudent().then((student) => { 43 | const stud: Student = JSON.parse(student.value); 44 | stud.grades = grades; 45 | return Storage.set({ key: this.STUDENT_KEY, value: JSON.stringify({ 46 | info: stud.info, 47 | grades: stud.grades 48 | }) }); 49 | }); 50 | } 51 | 52 | getStudent() { 53 | return Storage.get({ key: this.STUDENT_KEY }); 54 | } 55 | 56 | removeStudent() { 57 | return Storage.remove({ key: this.STUDENT_KEY }); 58 | } 59 | 60 | async compareGrades(newGrades: Grades, oldGrades: Grades, university: string, department: string) { 61 | this.newGrades = 0; 62 | this.newGradesList = []; 63 | 64 | const oldCourses: Array = []; 65 | oldGrades.semesters.forEach(semester => { 66 | semester.courses.forEach(course => { 67 | oldCourses.push(course); 68 | }); 69 | }); 70 | 71 | for (const semester of newGrades.semesters) { 72 | for (const course of semester.courses) { 73 | let i = 0; 74 | while (oldCourses.length > 0) { 75 | if (course.id === oldCourses[i].id) { 76 | if (course.examPeriod !== oldCourses[i].examPeriod) { 77 | this.newGrades++; 78 | this.newGradesList.push(course.id); 79 | 80 | if (course.grade !== '-') { 81 | if (Number(course.grade) >= 5) { 82 | await this.notificationService.unsubscribeFromTopic(university + '.' + course.id); 83 | } 84 | } 85 | 86 | this.notificationService.notifyNewGrade(university, course, semester.id, department); 87 | } 88 | oldCourses.splice(i, 1); 89 | break; 90 | } else { 91 | i++; 92 | } 93 | } 94 | } 95 | } 96 | 97 | setTimeout(() => { 98 | this.printNewGradesMsg(); 99 | }, 500); 100 | } 101 | 102 | printNewGradesMsg() { 103 | if (this.newGrades > 1) { 104 | this.toastService.present('Έχεις ' + this.newGrades + ' νέους βαθμούς!'); 105 | } else if (this.newGrades === 1) { 106 | this.toastService.present('Έχεις 1 νέο βαθμό!'); 107 | } else { 108 | this.toastService.present('Δεν έχεις νέους βαθμούς!'); 109 | } 110 | } 111 | 112 | saveUsername(username: string) { 113 | return Storage.set({ key: this.USERNAME_KEY, value: username }); 114 | } 115 | 116 | getUsername() { 117 | return Storage.get({ key: this.USERNAME_KEY }); 118 | } 119 | 120 | removeUsername() { 121 | return Storage.remove({ key: this.USERNAME_KEY }); 122 | } 123 | 124 | savePassword(password: string) { 125 | return Storage.set({ key: this.PASSWORD_KEY, value: password }); 126 | } 127 | 128 | getPassword() { 129 | return Storage.get({ key: this.PASSWORD_KEY }); 130 | } 131 | 132 | removePassword() { 133 | return Storage.remove({ key: this.PASSWORD_KEY }); 134 | } 135 | 136 | saveUniversity(university: string) { 137 | return Storage.set({ key: this.UNIVERSITY_KEY, value: university }); 138 | } 139 | 140 | getUniversity() { 141 | return Storage.get({ key: this.UNIVERSITY_KEY }); 142 | } 143 | 144 | saveThemeMode(mode: string) { 145 | return Storage.set({ key: this.THEME_MODE_KEY, value: mode }); 146 | } 147 | 148 | getThemeMode() { 149 | return Storage.get({ key: this.THEME_MODE_KEY }); 150 | } 151 | 152 | saveRememberMe(rememberMe: string) { 153 | return Storage.set({ key: this.REMEMBER_ME_KEY, value: rememberMe }); 154 | } 155 | 156 | getRememberMe() { 157 | return Storage.get({ key: this.REMEMBER_ME_KEY }); 158 | } 159 | 160 | removeRememberMe() { 161 | return Storage.remove({ key: this.REMEMBER_ME_KEY }); 162 | } 163 | 164 | getNewGradeNotification() { 165 | return Storage.get({ key: this.NEW_GRADE_NOTIFICATION }); 166 | } 167 | 168 | saveNewGradeNotification(newGradeNotification: string) { 169 | return Storage.set({ key: this.NEW_GRADE_NOTIFICATION, value: newGradeNotification }); 170 | } 171 | 172 | getUpdatesNotification() { 173 | return Storage.get({ key: this.UPDATES_NOTIFICATION }); 174 | } 175 | 176 | saveUpdatesNotification(updatesNotification: string) { 177 | return Storage.set({ key: this.UPDATES_NOTIFICATION, value: updatesNotification }); 178 | } 179 | 180 | saveAppVersion(appVersion: string) { 181 | return Storage.set({ key: this.APP_VERSION_KEY, value: appVersion }); 182 | } 183 | 184 | getAppVersion() { 185 | return Storage.get({ key: this.APP_VERSION_KEY }); 186 | } 187 | 188 | saveFirstTime(isFirstTime: string) { 189 | return Storage.set({ key: this.IS_FIRST_TIME, value: isFirstTime }); 190 | } 191 | 192 | getFirstTime() { 193 | return Storage.get({ key: this.IS_FIRST_TIME }); 194 | } 195 | 196 | clear() { 197 | return Storage.clear(); 198 | } 199 | } 200 | --------------------------------------------------------------------------------