diff --git a/filcnaplo/.gitignore b/filcnaplo/.gitignore new file mode 100644 index 0000000..99287a2 --- /dev/null +++ b/filcnaplo/.gitignore @@ -0,0 +1,47 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +build.sh + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/filcnaplo/.metadata b/filcnaplo/.metadata new file mode 100644 index 0000000..bbc00a9 --- /dev/null +++ b/filcnaplo/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 06e2fd63574bad2edafbe4653104ed76871ee0b1 + channel: beta + +project_type: app diff --git a/filcnaplo/README.md b/filcnaplo/README.md new file mode 100644 index 0000000..6464f55 --- /dev/null +++ b/filcnaplo/README.md @@ -0,0 +1,3 @@ +# filcnaplo + +Main lib \ No newline at end of file diff --git a/filcnaplo/android/.gitignore b/filcnaplo/android/.gitignore new file mode 100644 index 0000000..0a741cb --- /dev/null +++ b/filcnaplo/android/.gitignore @@ -0,0 +1,11 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties diff --git a/filcnaplo/android/app/build.gradle b/filcnaplo/android/app/build.gradle new file mode 100644 index 0000000..e9e4799 --- /dev/null +++ b/filcnaplo/android/app/build.gradle @@ -0,0 +1,83 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + throw new GradleException("Undefined VersionCode") +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + throw new GradleException("Undefined VersionName") +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file("$System.env.ANDROID_SIGNING") +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} else { + keystoreProperties.load(new FileInputStream(rootProject.file("signing/signing.properties"))) + +} + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + applicationId "hu.filc.naplo" + minSdkVersion 21 + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + signingConfigs { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile file(keystoreProperties['storeFile']) + storePassword keystoreProperties['storePassword'] + } + } + + buildTypes { + release { + signingConfig signingConfigs.release + shrinkResources false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + androidTestImplementation 'androidx.test:runner:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' +} diff --git a/filcnaplo/android/app/proguard-rules.pro b/filcnaplo/android/app/proguard-rules.pro new file mode 100644 index 0000000..0940b69 --- /dev/null +++ b/filcnaplo/android/app/proguard-rules.pro @@ -0,0 +1,5 @@ +-keep class io.flutter.plugin.editing.** { *; } +-keep class androidx.lifecycle.DefaultLifecycleObserver +-keep class com.pauldemarco.flutter_blue.** { *; } +-keep class com.mr.flutter.plugin.filepicker.** { *; } +-keep class com.shockwave.** \ No newline at end of file diff --git a/filcnaplo/android/app/src/debug/AndroidManifest.xml b/filcnaplo/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..f4f2d81 --- /dev/null +++ b/filcnaplo/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/filcnaplo/android/app/src/main/AndroidManifest.xml b/filcnaplo/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a3e6205 --- /dev/null +++ b/filcnaplo/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/filcnaplo/android/app/src/main/kotlin/hu/filc/naplo/MainActivity.kt b/filcnaplo/android/app/src/main/kotlin/hu/filc/naplo/MainActivity.kt new file mode 100644 index 0000000..b63cca1 --- /dev/null +++ b/filcnaplo/android/app/src/main/kotlin/hu/filc/naplo/MainActivity.kt @@ -0,0 +1,6 @@ +package hu.filc.naplo + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/filcnaplo/android/app/src/main/res/drawable-hdpi/ic_stat_splash_logo.png b/filcnaplo/android/app/src/main/res/drawable-hdpi/ic_stat_splash_logo.png new file mode 100644 index 0000000..f180cf9 Binary files /dev/null and b/filcnaplo/android/app/src/main/res/drawable-hdpi/ic_stat_splash_logo.png differ diff --git a/filcnaplo/android/app/src/main/res/drawable-mdpi/ic_stat_splash_logo.png b/filcnaplo/android/app/src/main/res/drawable-mdpi/ic_stat_splash_logo.png new file mode 100644 index 0000000..b6b25d1 Binary files /dev/null and b/filcnaplo/android/app/src/main/res/drawable-mdpi/ic_stat_splash_logo.png differ diff --git a/filcnaplo/android/app/src/main/res/drawable-v21/launch_background.xml b/filcnaplo/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..86aa265 --- /dev/null +++ b/filcnaplo/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/filcnaplo/android/app/src/main/res/drawable-v21/launch_gradient_background.xml b/filcnaplo/android/app/src/main/res/drawable-v21/launch_gradient_background.xml new file mode 100644 index 0000000..67e425a --- /dev/null +++ b/filcnaplo/android/app/src/main/res/drawable-v21/launch_gradient_background.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/filcnaplo/android/app/src/main/res/drawable-xhdpi/ic_stat_splash_logo.png b/filcnaplo/android/app/src/main/res/drawable-xhdpi/ic_stat_splash_logo.png new file mode 100644 index 0000000..7c96a2f Binary files /dev/null and b/filcnaplo/android/app/src/main/res/drawable-xhdpi/ic_stat_splash_logo.png differ diff --git a/filcnaplo/android/app/src/main/res/drawable-xxhdpi/ic_stat_splash_logo.png b/filcnaplo/android/app/src/main/res/drawable-xxhdpi/ic_stat_splash_logo.png new file mode 100644 index 0000000..b8b19e7 Binary files /dev/null and b/filcnaplo/android/app/src/main/res/drawable-xxhdpi/ic_stat_splash_logo.png differ diff --git a/filcnaplo/android/app/src/main/res/drawable-xxxhdpi/ic_stat_splash_logo.png b/filcnaplo/android/app/src/main/res/drawable-xxxhdpi/ic_stat_splash_logo.png new file mode 100644 index 0000000..dd12562 Binary files /dev/null and b/filcnaplo/android/app/src/main/res/drawable-xxxhdpi/ic_stat_splash_logo.png differ diff --git a/filcnaplo/android/app/src/main/res/drawable/launch_background.xml b/filcnaplo/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..86aa265 --- /dev/null +++ b/filcnaplo/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/filcnaplo/android/app/src/main/res/drawable/launch_gradient_background.xml b/filcnaplo/android/app/src/main/res/drawable/launch_gradient_background.xml new file mode 100644 index 0000000..67e425a --- /dev/null +++ b/filcnaplo/android/app/src/main/res/drawable/launch_gradient_background.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/filcnaplo/android/app/src/main/res/ic_launcher-web.png b/filcnaplo/android/app/src/main/res/ic_launcher-web.png new file mode 100644 index 0000000..90d1850 Binary files /dev/null and b/filcnaplo/android/app/src/main/res/ic_launcher-web.png differ diff --git a/filcnaplo/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/filcnaplo/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..fcd3a1e --- /dev/null +++ b/filcnaplo/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/filcnaplo/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/filcnaplo/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..e734cde --- /dev/null +++ b/filcnaplo/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/filcnaplo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/filcnaplo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..1d098b9 Binary files /dev/null and b/filcnaplo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/filcnaplo/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png b/filcnaplo/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png new file mode 100644 index 0000000..1d064b2 Binary files /dev/null and b/filcnaplo/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png differ diff --git a/filcnaplo/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/filcnaplo/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..f4bdc92 Binary files /dev/null and b/filcnaplo/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/filcnaplo/android/app/src/main/res/mipmap-hdpi/ic_splash.png b/filcnaplo/android/app/src/main/res/mipmap-hdpi/ic_splash.png new file mode 100644 index 0000000..08e9ceb Binary files /dev/null and b/filcnaplo/android/app/src/main/res/mipmap-hdpi/ic_splash.png differ diff --git a/filcnaplo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/filcnaplo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..343f52b Binary files /dev/null and b/filcnaplo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/filcnaplo/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png b/filcnaplo/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png new file mode 100644 index 0000000..7a61178 Binary files /dev/null and b/filcnaplo/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png differ diff --git a/filcnaplo/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/filcnaplo/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..51eb608 Binary files /dev/null and b/filcnaplo/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/filcnaplo/android/app/src/main/res/mipmap-mdpi/ic_splash.png b/filcnaplo/android/app/src/main/res/mipmap-mdpi/ic_splash.png new file mode 100644 index 0000000..f1bec95 Binary files /dev/null and b/filcnaplo/android/app/src/main/res/mipmap-mdpi/ic_splash.png differ diff --git a/filcnaplo/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/filcnaplo/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..ae678c1 Binary files /dev/null and b/filcnaplo/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/filcnaplo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/filcnaplo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png new file mode 100644 index 0000000..4317a89 Binary files /dev/null and b/filcnaplo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png differ diff --git a/filcnaplo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/filcnaplo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..902f8dd Binary files /dev/null and b/filcnaplo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/filcnaplo/android/app/src/main/res/mipmap-xhdpi/ic_splash.png b/filcnaplo/android/app/src/main/res/mipmap-xhdpi/ic_splash.png new file mode 100644 index 0000000..5ce1c6a Binary files /dev/null and b/filcnaplo/android/app/src/main/res/mipmap-xhdpi/ic_splash.png differ diff --git a/filcnaplo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/filcnaplo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..7cce925 Binary files /dev/null and b/filcnaplo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/filcnaplo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/filcnaplo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png new file mode 100644 index 0000000..42bac25 Binary files /dev/null and b/filcnaplo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png differ diff --git a/filcnaplo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/filcnaplo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..fe9b6c5 Binary files /dev/null and b/filcnaplo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/filcnaplo/android/app/src/main/res/mipmap-xxhdpi/ic_splash.png b/filcnaplo/android/app/src/main/res/mipmap-xxhdpi/ic_splash.png new file mode 100644 index 0000000..982dc3d Binary files /dev/null and b/filcnaplo/android/app/src/main/res/mipmap-xxhdpi/ic_splash.png differ diff --git a/filcnaplo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/filcnaplo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..2b510a9 Binary files /dev/null and b/filcnaplo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/filcnaplo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/filcnaplo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png new file mode 100644 index 0000000..0dae433 Binary files /dev/null and b/filcnaplo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png differ diff --git a/filcnaplo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/filcnaplo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..a9eb4bf Binary files /dev/null and b/filcnaplo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/filcnaplo/android/app/src/main/res/mipmap-xxxhdpi/ic_splash.png b/filcnaplo/android/app/src/main/res/mipmap-xxxhdpi/ic_splash.png new file mode 100644 index 0000000..b3def8c Binary files /dev/null and b/filcnaplo/android/app/src/main/res/mipmap-xxxhdpi/ic_splash.png differ diff --git a/filcnaplo/android/app/src/main/res/playstore-icon.png b/filcnaplo/android/app/src/main/res/playstore-icon.png new file mode 100644 index 0000000..90d1850 Binary files /dev/null and b/filcnaplo/android/app/src/main/res/playstore-icon.png differ diff --git a/filcnaplo/android/app/src/main/res/values-night/styles.xml b/filcnaplo/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..4de7564 --- /dev/null +++ b/filcnaplo/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/filcnaplo/android/app/src/main/res/values/colors.xml b/filcnaplo/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..4366078 --- /dev/null +++ b/filcnaplo/android/app/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #1F5B50 + \ No newline at end of file diff --git a/filcnaplo/android/app/src/main/res/values/styles.xml b/filcnaplo/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..c8a5e60 --- /dev/null +++ b/filcnaplo/android/app/src/main/res/values/styles.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/filcnaplo/android/app/src/profile/AndroidManifest.xml b/filcnaplo/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..f4f2d81 --- /dev/null +++ b/filcnaplo/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/filcnaplo/android/build.gradle b/filcnaplo/android/build.gradle new file mode 100644 index 0000000..dc156cb --- /dev/null +++ b/filcnaplo/android/build.gradle @@ -0,0 +1,53 @@ +buildscript { + ext.kotlin_version = '1.3.50' + + ext { + compileSdkVersion = 30 + targetSdkVersion = 30 + appCompatVersion = "1.1.0" + } + + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.6.3' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +// allprojects { +// repositories { +// google() +// jcenter() +// maven { +// // [required] background_fetch +// url "${project(':background_fetch').projectDir}/libs" +// } +// } +// } + +subprojects { + afterEvaluate {project -> + if (project.plugins.hasPlugin('android') || project.plugins.hasPlugin('android-library')) { + android { + compileSdkVersion 30 + buildToolsVersion '30.0.3' + } + } + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/filcnaplo/android/gradle.properties b/filcnaplo/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/filcnaplo/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/filcnaplo/android/gradle/wrapper/gradle-wrapper.properties b/filcnaplo/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..90f271d --- /dev/null +++ b/filcnaplo/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip diff --git a/filcnaplo/android/settings.gradle b/filcnaplo/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/filcnaplo/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/filcnaplo/android/settings_aar.gradle b/filcnaplo/android/settings_aar.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/filcnaplo/android/settings_aar.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/filcnaplo/android/signing/signing.keystore b/filcnaplo/android/signing/signing.keystore new file mode 100644 index 0000000..07de482 Binary files /dev/null and b/filcnaplo/android/signing/signing.keystore differ diff --git a/filcnaplo/android/signing/signing.properties b/filcnaplo/android/signing/signing.properties new file mode 100644 index 0000000..0bda00d --- /dev/null +++ b/filcnaplo/android/signing/signing.properties @@ -0,0 +1,4 @@ +keyAlias=test +keyPassword=test123 +storeFile=../signing/signing.keystore +storePassword=test123 \ No newline at end of file diff --git a/filcnaplo/assets/fonts/FilcIcons.ttf b/filcnaplo/assets/fonts/FilcIcons.ttf new file mode 100644 index 0000000..d1e782b Binary files /dev/null and b/filcnaplo/assets/fonts/FilcIcons.ttf differ diff --git a/filcnaplo/assets/fonts/Montserrat/Montserrat-Black.ttf b/filcnaplo/assets/fonts/Montserrat/Montserrat-Black.ttf new file mode 100644 index 0000000..437b115 Binary files /dev/null and b/filcnaplo/assets/fonts/Montserrat/Montserrat-Black.ttf differ diff --git a/filcnaplo/assets/fonts/Montserrat/Montserrat-BlackItalic.ttf b/filcnaplo/assets/fonts/Montserrat/Montserrat-BlackItalic.ttf new file mode 100644 index 0000000..5234835 Binary files /dev/null and b/filcnaplo/assets/fonts/Montserrat/Montserrat-BlackItalic.ttf differ diff --git a/filcnaplo/assets/fonts/Montserrat/Montserrat-Bold.ttf b/filcnaplo/assets/fonts/Montserrat/Montserrat-Bold.ttf new file mode 100644 index 0000000..221819b Binary files /dev/null and b/filcnaplo/assets/fonts/Montserrat/Montserrat-Bold.ttf differ diff --git a/filcnaplo/assets/fonts/Montserrat/Montserrat-BoldItalic.ttf b/filcnaplo/assets/fonts/Montserrat/Montserrat-BoldItalic.ttf new file mode 100644 index 0000000..9ae2bd2 Binary files /dev/null and b/filcnaplo/assets/fonts/Montserrat/Montserrat-BoldItalic.ttf differ diff --git a/filcnaplo/assets/fonts/Montserrat/Montserrat-ExtraBold.ttf b/filcnaplo/assets/fonts/Montserrat/Montserrat-ExtraBold.ttf new file mode 100644 index 0000000..80ea806 Binary files /dev/null and b/filcnaplo/assets/fonts/Montserrat/Montserrat-ExtraBold.ttf differ diff --git a/filcnaplo/assets/fonts/Montserrat/Montserrat-ExtraBoldItalic.ttf b/filcnaplo/assets/fonts/Montserrat/Montserrat-ExtraBoldItalic.ttf new file mode 100644 index 0000000..6c961e1 Binary files /dev/null and b/filcnaplo/assets/fonts/Montserrat/Montserrat-ExtraBoldItalic.ttf differ diff --git a/filcnaplo/assets/fonts/Montserrat/Montserrat-ExtraLight.ttf b/filcnaplo/assets/fonts/Montserrat/Montserrat-ExtraLight.ttf new file mode 100644 index 0000000..ca0bbb6 Binary files /dev/null and b/filcnaplo/assets/fonts/Montserrat/Montserrat-ExtraLight.ttf differ diff --git a/filcnaplo/assets/fonts/Montserrat/Montserrat-ExtraLightItalic.ttf b/filcnaplo/assets/fonts/Montserrat/Montserrat-ExtraLightItalic.ttf new file mode 100644 index 0000000..f3c1559 Binary files /dev/null and b/filcnaplo/assets/fonts/Montserrat/Montserrat-ExtraLightItalic.ttf differ diff --git a/filcnaplo/assets/fonts/Montserrat/Montserrat-Italic.ttf b/filcnaplo/assets/fonts/Montserrat/Montserrat-Italic.ttf new file mode 100644 index 0000000..eb4232a Binary files /dev/null and b/filcnaplo/assets/fonts/Montserrat/Montserrat-Italic.ttf differ diff --git a/filcnaplo/assets/fonts/Montserrat/Montserrat-Light.ttf b/filcnaplo/assets/fonts/Montserrat/Montserrat-Light.ttf new file mode 100644 index 0000000..990857d Binary files /dev/null and b/filcnaplo/assets/fonts/Montserrat/Montserrat-Light.ttf differ diff --git a/filcnaplo/assets/fonts/Montserrat/Montserrat-LightItalic.ttf b/filcnaplo/assets/fonts/Montserrat/Montserrat-LightItalic.ttf new file mode 100644 index 0000000..2096040 Binary files /dev/null and b/filcnaplo/assets/fonts/Montserrat/Montserrat-LightItalic.ttf differ diff --git a/filcnaplo/assets/fonts/Montserrat/Montserrat-Medium.ttf b/filcnaplo/assets/fonts/Montserrat/Montserrat-Medium.ttf new file mode 100644 index 0000000..6e079f6 Binary files /dev/null and b/filcnaplo/assets/fonts/Montserrat/Montserrat-Medium.ttf differ diff --git a/filcnaplo/assets/fonts/Montserrat/Montserrat-MediumItalic.ttf b/filcnaplo/assets/fonts/Montserrat/Montserrat-MediumItalic.ttf new file mode 100644 index 0000000..0dc3ac9 Binary files /dev/null and b/filcnaplo/assets/fonts/Montserrat/Montserrat-MediumItalic.ttf differ diff --git a/filcnaplo/assets/fonts/Montserrat/Montserrat-Regular.ttf b/filcnaplo/assets/fonts/Montserrat/Montserrat-Regular.ttf new file mode 100644 index 0000000..8d443d5 Binary files /dev/null and b/filcnaplo/assets/fonts/Montserrat/Montserrat-Regular.ttf differ diff --git a/filcnaplo/assets/fonts/Montserrat/Montserrat-SemiBold.ttf b/filcnaplo/assets/fonts/Montserrat/Montserrat-SemiBold.ttf new file mode 100644 index 0000000..f8a43f2 Binary files /dev/null and b/filcnaplo/assets/fonts/Montserrat/Montserrat-SemiBold.ttf differ diff --git a/filcnaplo/assets/fonts/Montserrat/Montserrat-SemiBoldItalic.ttf b/filcnaplo/assets/fonts/Montserrat/Montserrat-SemiBoldItalic.ttf new file mode 100644 index 0000000..336c56e Binary files /dev/null and b/filcnaplo/assets/fonts/Montserrat/Montserrat-SemiBoldItalic.ttf differ diff --git a/filcnaplo/assets/fonts/Montserrat/Montserrat-Thin.ttf b/filcnaplo/assets/fonts/Montserrat/Montserrat-Thin.ttf new file mode 100644 index 0000000..b985875 Binary files /dev/null and b/filcnaplo/assets/fonts/Montserrat/Montserrat-Thin.ttf differ diff --git a/filcnaplo/assets/fonts/Montserrat/Montserrat-ThinItalic.ttf b/filcnaplo/assets/fonts/Montserrat/Montserrat-ThinItalic.ttf new file mode 100644 index 0000000..e488998 Binary files /dev/null and b/filcnaplo/assets/fonts/Montserrat/Montserrat-ThinItalic.ttf differ diff --git a/filcnaplo/assets/fonts/Montserrat/OFL.txt b/filcnaplo/assets/fonts/Montserrat/OFL.txt new file mode 100644 index 0000000..7881887 --- /dev/null +++ b/filcnaplo/assets/fonts/Montserrat/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2011 The Montserrat Project Authors (https://github.com/JulietaUla/Montserrat) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/filcnaplo/assets/icons/home.svg b/filcnaplo/assets/icons/home.svg new file mode 100644 index 0000000..a536f40 --- /dev/null +++ b/filcnaplo/assets/icons/home.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/filcnaplo/assets/icons/ic_launcher.png b/filcnaplo/assets/icons/ic_launcher.png new file mode 100644 index 0000000..90d1850 Binary files /dev/null and b/filcnaplo/assets/icons/ic_launcher.png differ diff --git a/filcnaplo/assets/icons/ic_launcher_foreground.png b/filcnaplo/assets/icons/ic_launcher_foreground.png new file mode 100644 index 0000000..6d46232 Binary files /dev/null and b/filcnaplo/assets/icons/ic_launcher_foreground.png differ diff --git a/filcnaplo/assets/icons/ic_splash.png b/filcnaplo/assets/icons/ic_splash.png new file mode 100644 index 0000000..b0e4edc Binary files /dev/null and b/filcnaplo/assets/icons/ic_splash.png differ diff --git a/filcnaplo/assets/icons/linux.svg b/filcnaplo/assets/icons/linux.svg new file mode 100644 index 0000000..38430b8 --- /dev/null +++ b/filcnaplo/assets/icons/linux.svg @@ -0,0 +1,3 @@ + + + diff --git a/filcnaplo/ios/.gitignore b/filcnaplo/ios/.gitignore new file mode 100644 index 0000000..151026b --- /dev/null +++ b/filcnaplo/ios/.gitignore @@ -0,0 +1,33 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/filcnaplo/ios/Flutter/AppFrameworkInfo.plist b/filcnaplo/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9367d48 --- /dev/null +++ b/filcnaplo/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 8.0 + + diff --git a/filcnaplo/ios/Flutter/Debug.xcconfig b/filcnaplo/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/filcnaplo/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/filcnaplo/ios/Flutter/Release.xcconfig b/filcnaplo/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/filcnaplo/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/filcnaplo/ios/Runner.xcodeproj/project.pbxproj b/filcnaplo/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..e52440a --- /dev/null +++ b/filcnaplo/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,471 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = hu.filc.naplo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = hu.filc.naplo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = hu.filc.naplo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} \ No newline at end of file diff --git a/filcnaplo/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/filcnaplo/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/filcnaplo/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/filcnaplo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/filcnaplo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/filcnaplo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/filcnaplo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/filcnaplo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/filcnaplo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/filcnaplo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/filcnaplo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..a28140c --- /dev/null +++ b/filcnaplo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/filcnaplo/ios/Runner.xcworkspace/contents.xcworkspacedata b/filcnaplo/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/filcnaplo/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/filcnaplo/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/filcnaplo/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/filcnaplo/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/filcnaplo/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/filcnaplo/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/filcnaplo/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/filcnaplo/ios/Runner/AppDelegate.swift b/filcnaplo/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/filcnaplo/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..1387cc5 Binary files /dev/null and b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..36fb482 Binary files /dev/null and b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..4b1b2db Binary files /dev/null and b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..df3c09a Binary files /dev/null and b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..a2d0fe8 Binary files /dev/null and b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..d2da491 Binary files /dev/null and b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..86ab6d4 Binary files /dev/null and b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..4b1b2db Binary files /dev/null and b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..c887371 Binary files /dev/null and b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..15c9e31 Binary files /dev/null and b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..15c9e31 Binary files /dev/null and b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..3df9265 Binary files /dev/null and b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..8c87a6e Binary files /dev/null and b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..3f46226 Binary files /dev/null and b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..2b597a2 Binary files /dev/null and b/filcnaplo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/filcnaplo/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json b/filcnaplo/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json new file mode 100644 index 0000000..9f447e1 --- /dev/null +++ b/filcnaplo/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "background.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/filcnaplo/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png b/filcnaplo/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png new file mode 100644 index 0000000..1c45b72 Binary files /dev/null and b/filcnaplo/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png differ diff --git a/filcnaplo/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/filcnaplo/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..00cabce --- /dev/null +++ b/filcnaplo/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "LaunchImage.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "LaunchImage@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "LaunchImage@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/filcnaplo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/filcnaplo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..0d957ec Binary files /dev/null and b/filcnaplo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/filcnaplo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/filcnaplo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..979bd98 Binary files /dev/null and b/filcnaplo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/filcnaplo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/filcnaplo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..d43818f Binary files /dev/null and b/filcnaplo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/filcnaplo/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/filcnaplo/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/filcnaplo/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/filcnaplo/ios/Runner/Base.lproj/LaunchScreen.storyboard b/filcnaplo/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..0430c33 --- /dev/null +++ b/filcnaplo/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/filcnaplo/ios/Runner/Base.lproj/Main.storyboard b/filcnaplo/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/filcnaplo/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/filcnaplo/ios/Runner/Info.plist b/filcnaplo/ios/Runner/Info.plist new file mode 100644 index 0000000..a3010e0 --- /dev/null +++ b/filcnaplo/ios/Runner/Info.plist @@ -0,0 +1,52 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + filcnaplo + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + UIStatusBarHidden + + LSApplicationQueriesSchemes + + https + http + + + \ No newline at end of file diff --git a/filcnaplo/ios/Runner/Runner-Bridging-Header.h b/filcnaplo/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/filcnaplo/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/filcnaplo/lib/api/client.dart b/filcnaplo/lib/api/client.dart new file mode 100644 index 0000000..b4b19d0 --- /dev/null +++ b/filcnaplo/lib/api/client.dart @@ -0,0 +1,85 @@ +import 'dart:convert'; + +import 'package:filcnaplo/models/config.dart'; +import 'package:filcnaplo/models/news.dart'; +import 'package:filcnaplo/models/release.dart'; +import 'package:filcnaplo_kreta_api/models/school.dart'; +import 'package:http/http.dart' as http; + +class FilcAPI { + static const SCHOOL_LIST = "https://filcnaplo.hu/v2/school_list.json"; + static const CONFIG = "https://filcnaplo.hu/v2/config.json"; + static const NEWS = "https://filcnaplo.hu/v2/news.json"; + static const REPO = "filc/naplo"; + static const RELEASES = "https://api.github.com/repos/$REPO/releases"; + + static Future?> getSchools() async { + try { + http.Response res = await http.get(Uri.parse(SCHOOL_LIST)); + + if (res.statusCode == 200) { + return (jsonDecode(res.body) as List).cast().map((json) => School.fromJson(json)).toList(); + } else { + throw "HTTP ${res.statusCode}: ${res.body}"; + } + } catch (error) { + print("ERROR: FilcAPI.getSchools: $error"); + } + } + + static Future getConfig() async { + try { + http.Response res = await http.get(Uri.parse(CONFIG)); + + if (res.statusCode == 200) { + return Config.fromJson(jsonDecode(res.body)); + } else { + throw "HTTP ${res.statusCode}: ${res.body}"; + } + } catch (error) { + print("ERROR: FilcAPI.getConfig: $error"); + } + } + + static Future?> getNews() async { + try { + http.Response res = await http.get(Uri.parse(NEWS)); + + if (res.statusCode == 200) { + return (jsonDecode(res.body) as List).cast().map((e) => News.fromJson(e)).toList(); + } else { + throw "HTTP ${res.statusCode}: ${res.body}"; + } + } catch (error) { + print("ERROR: FilcAPI.getNews: $error"); + } + } + + static Future?> getReleases() async { + try { + http.Response res = await http.get(Uri.parse(RELEASES)); + + if (res.statusCode == 200) { + return (jsonDecode(res.body) as List).cast().map((e) => Release.fromJson(e)).toList(); + } else { + throw "HTTP ${res.statusCode}: ${res.body}"; + } + } catch (error) { + print("ERROR: FilcAPI.getReleases: $error"); + } + } + + static Future downloadRelease(Release release) { + if (release.downloads.length > 0) { + try { + var client = http.Client(); + var request = http.Request('GET', Uri.parse(release.downloads.first)); + return client.send(request); + } catch (error) { + print("ERROR: FilcAPI.downloadRelease: $error"); + } + } + + return Future.value(null); + } +} diff --git a/filcnaplo/lib/api/login.dart b/filcnaplo/lib/api/login.dart new file mode 100644 index 0000000..8d515b1 --- /dev/null +++ b/filcnaplo/lib/api/login.dart @@ -0,0 +1,110 @@ +import 'package:filcnaplo_kreta_api/providers/absence_provider.dart'; +import 'package:filcnaplo_kreta_api/providers/event_provider.dart'; +import 'package:filcnaplo_kreta_api/providers/exam_provider.dart'; +import 'package:filcnaplo_kreta_api/providers/grade_provider.dart'; +import 'package:filcnaplo_kreta_api/providers/homework_provider.dart'; +import 'package:filcnaplo_kreta_api/providers/message_provider.dart'; +import 'package:filcnaplo_kreta_api/providers/note_provider.dart'; +import 'package:filcnaplo_kreta_api/providers/timetable_provider.dart'; +import 'package:filcnaplo/api/providers/user_provider.dart'; +import 'package:filcnaplo/api/providers/database_provider.dart'; +import 'package:filcnaplo/models/settings.dart'; +import 'package:filcnaplo/models/user.dart'; +import 'package:filcnaplo/utils/jwt.dart'; +import 'package:filcnaplo_kreta_api/client/api.dart'; +import 'package:filcnaplo_kreta_api/client/client.dart'; +import 'package:filcnaplo_kreta_api/models/message.dart'; +import 'package:filcnaplo_kreta_api/models/student.dart'; +import 'package:filcnaplo_kreta_api/models/week.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:filcnaplo/api/nonce.dart'; + +enum LoginState { normal, inProgress, missingFields, invalidGrant, success, failed } + +Nonce getNonce(BuildContext context, String nonce, String username, String instituteCode) { + Nonce nonceEncoder = Nonce(key: [53, 75, 109, 112, 109, 103, 100, 53, 102, 74], nonce: nonce); + nonceEncoder.encode(username.toLowerCase() + instituteCode.toLowerCase() + nonce); + + return nonceEncoder; +} + +Future loginApi({ + required String username, + required String password, + required String instituteCode, + required BuildContext context, + void Function(User)? onLogin, + void Function()? onSuccess, +}) async { + Provider.of(context, listen: false).userAgent = Provider.of(context, listen: false).config.userAgent; + + Map headers = { + "content-type": "application/x-www-form-urlencoded", + }; + + String nonceStr = await Provider.of(context, listen: false).getAPI(KretaAPI.nonce, json: false); + + Nonce nonce = getNonce(context, nonceStr, username, instituteCode); + headers.addAll(nonce.header()); + + Map? res = await Provider.of(context, listen: false).postAPI(KretaAPI.login, + headers: headers, + body: User.loginBody( + username: username, + password: password, + instituteCode: instituteCode, + )); + if (res != null) { + if (res.containsKey("error")) { + if (res["error"] == "invalid_grant") { + return LoginState.invalidGrant; + } + } else { + if (res.containsKey("access_token")) { + try { + Provider.of(context, listen: false).accessToken = res["access_token"]; + Map? studentJson = await Provider.of(context, listen: false).getAPI(KretaAPI.student(instituteCode)); + var user = User( + username: username, + password: password, + instituteCode: instituteCode, + name: JwtUtils.getNameFromJWT(res["access_token"]) ?? "?", + student: Student.fromJson(studentJson!), + ); + + if (onLogin != null) onLogin(user); + + // Store User in the database + await Provider.of(context, listen: false).store.storeUser(user); + Provider.of(context, listen: false).addUser(user); + Provider.of(context, listen: false).setUser(user.id); + + // Get user data + try { + await Provider.of(context, listen: false).fetch(); + await Provider.of(context, listen: false).fetch(week: Week.current()); + await Provider.of(context, listen: false).fetch(); + await Provider.of(context, listen: false).fetch(); + await Provider.of(context, listen: false).fetch(type: MessageType.inbox); + await Provider.of(context, listen: false).fetch(); + await Provider.of(context, listen: false).fetch(); + await Provider.of(context, listen: false).fetch(); + } catch (error) { + print("WARNING: failed to fetch user data: $error"); + } + + if (onSuccess != null) onSuccess(); + + return LoginState.success; + } catch (error) { + print("ERROR: loginApi: $error"); + // maybe check debug mode + // ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("ERROR: $error"))); + return LoginState.failed; + } + } + } + } + return LoginState.failed; +} diff --git a/filcnaplo/lib/api/nonce.dart b/filcnaplo/lib/api/nonce.dart new file mode 100644 index 0000000..505124c --- /dev/null +++ b/filcnaplo/lib/api/nonce.dart @@ -0,0 +1,25 @@ +import 'dart:convert'; +import 'package:crypto/crypto.dart'; + +class Nonce { + String nonce; + List key; + String? encoded; + + Nonce({required this.nonce, required this.key}); + + Future encode(String message) async { + List messageBytes = utf8.encode(message); + Hmac hmac = Hmac(sha512, key); + Digest digest = hmac.convert(messageBytes); + encoded = base64.encode(digest.bytes); + } + + Map header() { + return { + "X-Authorizationpolicy-Nonce": nonce, + "X-Authorizationpolicy-Key": encoded ?? "", + "X-Authorizationpolicy-Version": "v1", + }; + } +} diff --git a/filcnaplo/lib/api/providers/database_provider.dart b/filcnaplo/lib/api/providers/database_provider.dart new file mode 100644 index 0000000..4f4d4ca --- /dev/null +++ b/filcnaplo/lib/api/providers/database_provider.dart @@ -0,0 +1,20 @@ +import 'package:filcnaplo/database/query.dart'; +import 'package:filcnaplo/database/store.dart'; +import 'package:sqflite/sqflite.dart'; + +class DatabaseProvider { + // late Database _database; + late DatabaseQuery query; + late UserDatabaseQuery userQuery; + late DatabaseStore store; + late UserDatabaseStore userStore; + + Future init() async { + var db = await openDatabase("app.db"); + // _database = db; + query = DatabaseQuery(db: db); + store = DatabaseStore(db: db); + userQuery = UserDatabaseQuery(db: db); + userStore = UserDatabaseStore(db: db); + } +} diff --git a/filcnaplo/lib/api/providers/news_provider.dart b/filcnaplo/lib/api/providers/news_provider.dart new file mode 100644 index 0000000..312df1a --- /dev/null +++ b/filcnaplo/lib/api/providers/news_provider.dart @@ -0,0 +1,82 @@ +import 'dart:math'; + +import 'package:filcnaplo/api/client.dart'; +import 'package:filcnaplo/models/news.dart'; +import 'package:filcnaplo/models/settings.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class NewsProvider extends ChangeNotifier { + // Private + late List _news; + late int _state; + late int _fresh; + bool show = false; + late BuildContext _context; + + // Public + List get news => _news; + int get state => _fresh - 1; + + NewsProvider({ + List initialNews = const [], + required BuildContext context, + }) { + _news = List.castFrom(initialNews); + _context = context; + } + + Future restore() async { + // Load news state from the database + var state_ = Provider.of(_context, listen: false).newsState; + + if (state_ == -1) { + var news_ = await FilcAPI.getNews(); + if (news_ != null) { + state_ = news_.length; + _news = news_; + } + } + + _state = state_; + Provider.of(_context, listen: false).update(_context, newsState: _state); + } + + Future fetch() async { + var news_ = await FilcAPI.getNews(); + if (news_ == null) return; + + _news = news_; + _fresh = news_.length - _state; + + if (_fresh < 0) { + _state = news_.length; + Provider.of(_context, listen: false).update(_context, newsState: _state); + } + + _fresh = max(_fresh, 0); + + if (_fresh > 0) { + show = true; + notifyListeners(); + } + } + + void lock() => show = false; + + void release() { + if (_fresh == 0) return; + + _fresh--; + _state++; + + Provider.of(_context, listen: false).update(_context, newsState: _state); + + if (_fresh > 0) + show = true; + else + show = false; + + notifyListeners(); + } +} diff --git a/filcnaplo/lib/api/providers/update_provider.dart b/filcnaplo/lib/api/providers/update_provider.dart new file mode 100644 index 0000000..a225fd1 --- /dev/null +++ b/filcnaplo/lib/api/providers/update_provider.dart @@ -0,0 +1,41 @@ +import 'dart:io'; + +import 'package:filcnaplo/api/client.dart'; +import 'package:filcnaplo/models/release.dart'; +import 'package:flutter/material.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +class UpdateProvider extends ChangeNotifier { + // Private + late List _releases; + late BuildContext _context; + bool _available = false; + bool get available => _available && _releases.length > 0; + PackageInfo? _packageInfo; + + // Public + List get releases => _releases; + + UpdateProvider({ + List initialReleases = const [], + required BuildContext context, + }) { + _releases = List.castFrom(initialReleases); + _context = context; + PackageInfo.fromPlatform().then((value) => _packageInfo = value); + } + + Future fetch() async { + if (!Platform.isAndroid) return; + + _releases = await FilcAPI.getReleases() ?? []; + _releases.sort((a, b) => -a.version.compareTo(b.version)); + + // Check for new releases + if (_releases.length > 0) { + print("INFO: New update: ${releases.first.version}"); + _available = _packageInfo != null && _releases.first.version.compareTo(Version.fromString(_packageInfo?.version ?? "")) == 1; + notifyListeners(); + } + } +} diff --git a/filcnaplo/lib/api/providers/user_provider.dart b/filcnaplo/lib/api/providers/user_provider.dart new file mode 100644 index 0000000..2cd4069 --- /dev/null +++ b/filcnaplo/lib/api/providers/user_provider.dart @@ -0,0 +1,41 @@ +import 'package:filcnaplo/models/user.dart'; +import 'package:filcnaplo_kreta_api/models/student.dart'; +import 'package:flutter/foundation.dart'; + +class UserProvider with ChangeNotifier { + Map _users = {}; + String? _selectedUserId; + User? get user => _users[_selectedUserId]; + + // _user properties + String? get instituteCode => user?.instituteCode; + String? get id => user?.id; + String? get name => user?.name; + String? get username => user?.username; + String? get password => user?.password; + Student? get student => user?.student; + + void setUser(String userId) { + _selectedUserId = userId; + notifyListeners(); + } + + void addUser(User user) { + _users[user.id] = user; + print("DEBUG: Added User: ${user.id} ${user.name}"); + } + + void removeUser(String userId) { + _users.removeWhere((key, value) => key == userId); + if (_users.isNotEmpty) _selectedUserId = _users.keys.first; + notifyListeners(); + } + + User getUser(String userId) { + return _users[userId]!; + } + + List getUsers() { + return _users.values.toList(); + } +} diff --git a/filcnaplo/lib/app.dart b/filcnaplo/lib/app.dart new file mode 100644 index 0000000..066ec59 --- /dev/null +++ b/filcnaplo/lib/app.dart @@ -0,0 +1,120 @@ +import 'package:filcnaplo/api/client.dart'; +import 'package:filcnaplo/api/providers/news_provider.dart'; +import 'package:filcnaplo/api/providers/database_provider.dart'; +import 'package:filcnaplo/models/config.dart'; +import 'package:filcnaplo/theme.dart'; +import 'package:filcnaplo_kreta_api/client/client.dart'; +import 'package:filcnaplo_mobile_ui/common/system_chrome.dart'; +import 'package:filcnaplo_mobile_ui/screens/login/login_route.dart'; +import 'package:filcnaplo_mobile_ui/screens/login/login_screen.dart'; +import 'package:filcnaplo_mobile_ui/screens/navigation/navigation_screen.dart'; +import 'package:filcnaplo_mobile_ui/screens/settings/settings_route.dart'; +import 'package:filcnaplo_mobile_ui/screens/settings/settings_screen.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:i18n_extension/i18n_widget.dart'; +import 'package:provider/provider.dart'; + +// Providers +import 'package:filcnaplo/models/settings.dart'; +import 'package:filcnaplo_kreta_api/providers/absence_provider.dart'; +import 'package:filcnaplo_kreta_api/providers/event_provider.dart'; +import 'package:filcnaplo_kreta_api/providers/exam_provider.dart'; +import 'package:filcnaplo_kreta_api/providers/grade_provider.dart'; +import 'package:filcnaplo_kreta_api/providers/homework_provider.dart'; +import 'package:filcnaplo_kreta_api/providers/message_provider.dart'; +import 'package:filcnaplo_kreta_api/providers/note_provider.dart'; +import 'package:filcnaplo_kreta_api/providers/timetable_provider.dart'; +import 'package:filcnaplo/api/providers/user_provider.dart'; +import 'package:filcnaplo/api/providers/update_provider.dart'; +import 'package:filcnaplo_mobile_ui/pages/grades/calculator/grade_calculator_provider.dart'; + +class App extends StatelessWidget { + final SettingsProvider settings; + final UserProvider user; + final DatabaseProvider database; + + App({Key? key, required this.database, required this.settings, required this.user}) : super(key: key) { + if (user.getUsers().length > 0) user.setUser(user.getUsers().first.id); + } + + @override + Widget build(BuildContext context) { + setSystemChrome(context); + + WidgetsBinding.instance?.addPostFrameCallback((_) { + FilcAPI.getConfig().then((Config? config) { + settings.update(context, database: database, config: config ?? Config.fromJson({})); + }); + }); + + return I18n( + initialLocale: Locale(settings.language, settings.language), + child: MultiProvider( + providers: [ + ChangeNotifierProvider(create: (_) => settings), + ChangeNotifierProvider(create: (_) => user), + Provider(create: (context) => KretaClient(context: context, userAgent: settings.config.userAgent)), + Provider(create: (context) => database), + ChangeNotifierProvider(create: (context) => ThemeModeObserver(initialTheme: settings.theme)), + ChangeNotifierProvider(create: (context) => NewsProvider(context: context)), + ChangeNotifierProvider(create: (context) => UpdateProvider(context: context)), + + // User data providers + ChangeNotifierProvider(create: (context) => GradeProvider(context: context)), + ChangeNotifierProvider(create: (context) => TimetableProvider(context: context)), + ChangeNotifierProvider(create: (context) => ExamProvider(context: context)), + ChangeNotifierProvider(create: (context) => HomeworkProvider(context: context)), + ChangeNotifierProvider(create: (context) => MessageProvider(context: context)), + ChangeNotifierProvider(create: (context) => NoteProvider(context: context)), + ChangeNotifierProvider(create: (context) => EventProvider(context: context)), + ChangeNotifierProvider(create: (context) => AbsenceProvider(context: context)), + + ChangeNotifierProvider(create: (context) => GradeCalculatorProvider(context)), + ], + child: Consumer( + builder: (context, themeMode, child) => MaterialApp( + title: "Filc Napló", + debugShowCheckedModeBanner: false, + theme: AppTheme.lightTheme(context), + darkTheme: AppTheme.darkTheme(context), + themeMode: themeMode.themeMode, + localizationsDelegates: [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: [ + const Locale('en'), + const Locale('hu'), + const Locale('de'), + ], + onGenerateRoute: (settings) => rootNavigator(settings), + initialRoute: user.getUsers().length > 0 ? "navigation" : "login"), + ), + ), + ); + } + + Route? rootNavigator(RouteSettings route) { + // if platform == android || platform == ios + switch (route.name) { + case "login_back": + return CupertinoPageRoute(builder: (context) => LoginScreen(back: true)); + case "login": + return _rootRoute(LoginScreen()); + case "navigation": + return _rootRoute(Navigation()); + case "login_to_navigation": + return loginRoute(Navigation()); + case "settings": + return settingsRoute(SettingsScreen()); + } + // else if platform == windows || ... + } + + Route _rootRoute(Widget widget) { + return PageRouteBuilder(pageBuilder: (context, _, __) => widget); + } +} diff --git a/filcnaplo/lib/database/init.dart b/filcnaplo/lib/database/init.dart new file mode 100644 index 0000000..ab84bbe --- /dev/null +++ b/filcnaplo/lib/database/init.dart @@ -0,0 +1,72 @@ +import 'package:filcnaplo/database/struct.dart'; +import 'package:filcnaplo/models/settings.dart'; +import 'package:sqflite/sqflite.dart'; + +Future initDB() async { + // await deleteDatabase('app.db'); // for debugging + var db = await openDatabase('app.db'); + + var settingsDB = await createSettingsTable(db); + + // Create table Users + await db.execute("CREATE TABLE IF NOT EXISTS users (id TEXT NOT NULL, name TEXT, username TEXT, password TEXT, institute_code TEXT, student TEXT)"); + await db.execute("CREATE TABLE IF NOT EXISTS user_data (" + "id TEXT NOT NULL, grades TEXT, timetable TEXT, exams TEXT, homework TEXT, messages TEXT, notes TEXT, events TEXT, absences TEXT)"); + + if ((await db.rawQuery("SELECT COUNT(*) FROM settings"))[0].values.first == 0) { + // Set default values for table Settings + await db.insert("settings", SettingsProvider.defaultSettings().toMap()); + } + + await migrateDB(db, settingsDB.struct.keys); + + return db; +} + +Future createSettingsTable(Database db) async { + var settingsDB = DatabaseStruct({ + "language": String, "start_page": int, "rounding": int, "theme": int, "accent_color": int, "news": int, "news_state": int, "developer_mode": int, + "update_channel": int, "config": String, // general + "grade_color1": int, "grade_color2": int, "grade_color3": int, "grade_color4": int, "grade_color5": int, // grade colors + "vibrate": int, "vibration_strength": int, "ab_weeks": int, "swap_ab_weeks": int, + "notifications": int, "notifications_bitfield": int, "notification_poll_interval": int, // notifications + }); + + // Create table Settings + await db.execute("CREATE TABLE IF NOT EXISTS settings ($settingsDB)"); + + return settingsDB; +} + +Future migrateDB(Database db, Iterable keys) async { + var settings = (await db.query("settings"))[0]; + + bool migrationRequired = keys.any((key) => !settings.containsKey(key) || settings[key] == null); + + if (migrationRequired) { + var defaultSettings = SettingsProvider.defaultSettings(); + var settingsCopy = Map.from(settings); + + // Delete settings + await db.execute("drop table settings"); + + // Fill missing columns + keys.forEach((key) { + if (!keys.contains(key)) { + print("debug: dropping $key"); + settingsCopy.remove(key); + } + + if (!settings.containsKey(key) || settings[key] == null) { + print("DEBUG: migrating $key"); + settingsCopy[key] = defaultSettings.toMap()[key]; + } + }); + + // Recreate settings + await createSettingsTable(db); + await db.insert("settings", settingsCopy); + + print("INFO: Database migrated"); + } +} diff --git a/filcnaplo/lib/database/query.dart b/filcnaplo/lib/database/query.dart new file mode 100644 index 0000000..db42c1a --- /dev/null +++ b/filcnaplo/lib/database/query.dart @@ -0,0 +1,114 @@ +import 'dart:convert'; +import 'package:filcnaplo/models/user.dart'; +import 'package:sqflite/sqflite.dart'; + +// Models +import 'package:filcnaplo/models/settings.dart'; +import 'package:filcnaplo/api/providers/user_provider.dart'; +import 'package:filcnaplo_kreta_api/models/grade.dart'; +import 'package:filcnaplo_kreta_api/models/lesson.dart'; +import 'package:filcnaplo_kreta_api/models/exam.dart'; +import 'package:filcnaplo_kreta_api/models/homework.dart'; +import 'package:filcnaplo_kreta_api/models/message.dart'; +import 'package:filcnaplo_kreta_api/models/note.dart'; +import 'package:filcnaplo_kreta_api/models/event.dart'; +import 'package:filcnaplo_kreta_api/models/absence.dart'; + +class DatabaseQuery { + DatabaseQuery({required this.db}); + + final Database db; + + Future getSettings() async { + Map settingsMap = (await db.query("settings")).elementAt(0); + SettingsProvider settings = SettingsProvider.fromMap(settingsMap); + return settings; + } + + Future getUsers() async { + var userProvider = UserProvider(); + List usersMap = await db.query("users"); + usersMap.forEach((user) { + userProvider.addUser(User.fromMap(user)); + }); + return userProvider; + } +} + +class UserDatabaseQuery { + UserDatabaseQuery({required this.db}); + + final Database db; + + Future> getGrades({required String userId}) async { + List userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]); + if (userData.length == 0) return []; + String? gradesJson = userData.elementAt(0)["grades"] as String?; + if (gradesJson == null) return []; + List grades = (jsonDecode(gradesJson) as List).map((e) => Grade.fromJson(e)).toList(); + return grades; + } + + Future> getLessons({required String userId}) async { + List userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]); + if (userData.length == 0) return []; + String? lessonsJson = userData.elementAt(0)["timetable"] as String?; + if (lessonsJson == null) return []; + List lessons = (jsonDecode(lessonsJson) as List).map((e) => Lesson.fromJson(e)).toList(); + return lessons; + } + + Future> getExams({required String userId}) async { + List userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]); + if (userData.length == 0) return []; + String? examsJson = userData.elementAt(0)["exams"] as String?; + if (examsJson == null) return []; + List exams = (jsonDecode(examsJson) as List).map((e) => Exam.fromJson(e)).toList(); + return exams; + } + + Future> getHomework({required String userId}) async { + List userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]); + if (userData.length == 0) return []; + String? homeworkJson = userData.elementAt(0)["homework"] as String?; + if (homeworkJson == null) return []; + List homework = (jsonDecode(homeworkJson) as List).map((e) => Homework.fromJson(e)).toList(); + return homework; + } + + Future> getMessages({required String userId}) async { + List userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]); + if (userData.length == 0) return []; + String? messagesJson = userData.elementAt(0)["messages"] as String?; + if (messagesJson == null) return []; + List messages = (jsonDecode(messagesJson) as List).map((e) => Message.fromJson(e)).toList(); + return messages; + } + + Future> getNotes({required String userId}) async { + List userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]); + if (userData.length == 0) return []; + String? notesJson = userData.elementAt(0)["notes"] as String?; + if (notesJson == null) return []; + List notes = (jsonDecode(notesJson) as List).map((e) => Note.fromJson(e)).toList(); + return notes; + } + + Future> getEvents({required String userId}) async { + List userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]); + if (userData.length == 0) return []; + String? eventsJson = userData.elementAt(0)["events"] as String?; + if (eventsJson == null) return []; + List events = (jsonDecode(eventsJson) as List).map((e) => Event.fromJson(e)).toList(); + return events; + } + + Future> getAbsences({required String userId}) async { + List userData = await db.query("user_data", where: "id = ?", whereArgs: [userId]); + if (userData.length == 0) return []; + String? absebcesJson = userData.elementAt(0)["absences"] as String?; + if (absebcesJson == null) return []; + List absebces = (jsonDecode(absebcesJson) as List).map((e) => Absence.fromJson(e)).toList(); + return absebces; + } +} diff --git a/filcnaplo/lib/database/store.dart b/filcnaplo/lib/database/store.dart new file mode 100644 index 0000000..b5f64de --- /dev/null +++ b/filcnaplo/lib/database/store.dart @@ -0,0 +1,85 @@ +import 'dart:convert'; +import 'package:sqflite/sqflite.dart'; + +// Models +import 'package:filcnaplo/models/settings.dart'; +import 'package:filcnaplo/models/user.dart'; +import 'package:filcnaplo_kreta_api/models/grade.dart'; +import 'package:filcnaplo_kreta_api/models/lesson.dart'; +import 'package:filcnaplo_kreta_api/models/exam.dart'; +import 'package:filcnaplo_kreta_api/models/homework.dart'; +import 'package:filcnaplo_kreta_api/models/message.dart'; +import 'package:filcnaplo_kreta_api/models/note.dart'; +import 'package:filcnaplo_kreta_api/models/event.dart'; +import 'package:filcnaplo_kreta_api/models/absence.dart'; + +class DatabaseStore { + DatabaseStore({required this.db}); + + final Database db; + + Future storeSettings(SettingsProvider settings) async { + await db.update("settings", settings.toMap()); + } + + Future storeUser(User user) async { + List userRes = await db.query("users", where: "id = ?", whereArgs: [user.id]); + if (userRes.length > 0) { + await db.update("users", user.toMap(), where: "id = ?", whereArgs: [user.id]); + } else { + await db.insert("users", user.toMap()); + await db.insert("user_data", {"id": user.id}); + } + } + + Future removeUser(String userId) async { + await db.delete("users", where: "id = ?", whereArgs: [userId]); + await db.delete("user_data", where: "id = ?", whereArgs: [userId]); + } +} + +class UserDatabaseStore { + UserDatabaseStore({required this.db}); + + final Database db; + + Future storeGrades(List grades, {required String userId}) async { + String gradesJson = jsonEncode(grades.map((e) => e.json).toList()); + await db.update("user_data", {"grades": gradesJson}, where: "id = ?", whereArgs: [userId]); + } + + Future storeLessons(List lessons, {required String userId}) async { + String lessonsJson = jsonEncode(lessons.map((e) => e.json).toList()); + await db.update("user_data", {"timetable": lessonsJson}, where: "id = ?", whereArgs: [userId]); + } + + Future storeExams(List exams, {required String userId}) async { + String examsJson = jsonEncode(exams.map((e) => e.json).toList()); + await db.update("user_data", {"exams": examsJson}, where: "id = ?", whereArgs: [userId]); + } + + Future storeHomework(List homework, {required String userId}) async { + String homeworkJson = jsonEncode(homework.map((e) => e.json).toList()); + await db.update("user_data", {"homework": homeworkJson}, where: "id = ?", whereArgs: [userId]); + } + + Future storeMessages(List messages, {required String userId}) async { + String messagesJson = jsonEncode(messages.map((e) => e.json).toList()); + await db.update("user_data", {"messages": messagesJson}, where: "id = ?", whereArgs: [userId]); + } + + Future storeNotes(List notes, {required String userId}) async { + String notesJson = jsonEncode(notes.map((e) => e.json).toList()); + await db.update("user_data", {"notes": notesJson}, where: "id = ?", whereArgs: [userId]); + } + + Future storeEvents(List events, {required String userId}) async { + String eventsJson = jsonEncode(events.map((e) => e.json).toList()); + await db.update("user_data", {"events": eventsJson}, where: "id = ?", whereArgs: [userId]); + } + + Future storeAbsences(List absences, {required String userId}) async { + String absencesJson = jsonEncode(absences.map((e) => e.json).toList()); + await db.update("user_data", {"absences": absencesJson}, where: "id = ?", whereArgs: [userId]); + } +} diff --git a/filcnaplo/lib/database/struct.dart b/filcnaplo/lib/database/struct.dart new file mode 100644 index 0000000..50b3355 --- /dev/null +++ b/filcnaplo/lib/database/struct.dart @@ -0,0 +1,29 @@ +class DatabaseStruct { + final Map struct; + + DatabaseStruct(this.struct); + + String _toDBfield(String name, dynamic type) { + String typeName = ""; + + switch (type.runtimeType) { + case int: + typeName = "integer"; + break; + case String: + typeName = "text"; + break; + } + + return "${name} ${typeName.toUpperCase()}"; + } + + @override + String toString() { + List columns = []; + struct.forEach((key, value) { + columns.add(_toDBfield(key, value)); + }); + return columns.join(","); + } +} diff --git a/filcnaplo/lib/helpers/attachment_helper.dart b/filcnaplo/lib/helpers/attachment_helper.dart new file mode 100644 index 0000000..76edc29 --- /dev/null +++ b/filcnaplo/lib/helpers/attachment_helper.dart @@ -0,0 +1,30 @@ +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:filcnaplo/helpers/storage_helper.dart'; +import 'package:filcnaplo_kreta_api/client/client.dart'; +import 'package:filcnaplo_kreta_api/models/attachment.dart'; +import 'package:flutter/widgets.dart'; +import 'package:open_file/open_file.dart'; +import 'package:provider/provider.dart'; + +extension AttachmentHelper on Attachment { + Future download(BuildContext context, {bool overwrite = false}) async { + String downloads = await StorageHelper.downloadsPath(); + + if (!overwrite && await File("$downloads/$name").exists()) return "$downloads/$name"; + + Uint8List data = await Provider.of(context, listen: false).getAPI(downloadUrl, rawResponse: true); + if (!await StorageHelper.write("$downloads/$name", data)) return ""; + + return "$downloads/$name"; + } + + Future open(BuildContext context) async { + String downloads = await StorageHelper.downloadsPath(); + + if (!await File("$downloads/$name").exists()) await download(context); + var result = await OpenFile.open("$downloads/$name"); + return result.type == ResultType.done; + } +} diff --git a/filcnaplo/lib/helpers/average_helper.dart b/filcnaplo/lib/helpers/average_helper.dart new file mode 100644 index 0000000..3b43cc3 --- /dev/null +++ b/filcnaplo/lib/helpers/average_helper.dart @@ -0,0 +1,25 @@ +import 'package:filcnaplo_kreta_api/models/grade.dart'; + +class AverageHelper { + static double averageEvals(List grades, {bool finalAvg = false}) { + double average = 0.0; + + List ignoreInFinal = ["5,SzorgalomErtek", "4,MagatartasErtek"]; + + if (finalAvg) + grades.removeWhere((e) => + (e.value.value == 0) || + (ignoreInFinal.contains(e.gradeType?.id))); + + grades.forEach((e) { + average += e.value.value * ((finalAvg ? 100 : e.value.weight) / 100); + }); + + average = average / + grades + .map((e) => (finalAvg ? 100 : e.value.weight) / 100) + .fold(0.0, (a, b) => a + b); + + return average.isNaN ? 0.0 : average; + } +} \ No newline at end of file diff --git a/filcnaplo/lib/helpers/share_helper.dart b/filcnaplo/lib/helpers/share_helper.dart new file mode 100644 index 0000000..5242980 --- /dev/null +++ b/filcnaplo/lib/helpers/share_helper.dart @@ -0,0 +1,14 @@ +import 'package:filcnaplo/helpers/attachment_helper.dart'; +import 'package:filcnaplo_kreta_api/models/attachment.dart'; +import 'package:flutter/widgets.dart'; +import 'package:share_plus/share_plus.dart'; + +class ShareHelper { + static Future shareText(String text, {String? subject}) => Share.share(text, subject: subject); + static Future shareFile(String path, {String? text, String? subject}) => Share.shareFiles([path], text: text, subject: subject); + + static Future shareAttachment(Attachment attachment, {required BuildContext context}) async { + String path = await attachment.download(context); + await shareFile(path); + } +} diff --git a/filcnaplo/lib/helpers/storage_helper.dart b/filcnaplo/lib/helpers/storage_helper.dart new file mode 100644 index 0000000..1f43f4e --- /dev/null +++ b/filcnaplo/lib/helpers/storage_helper.dart @@ -0,0 +1,36 @@ +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:path_provider/path_provider.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class StorageHelper { + static Future write(String path, Uint8List data) async { + try { + if (await Permission.storage.request().isGranted) { + await File(path).writeAsBytes(data); + return true; + } else { + if (await Permission.storage.isPermanentlyDenied) { + openAppSettings(); + } + return false; + } + } catch (error) { + print("ERROR: StorageHelper.write: $error"); + return false; + } + } + + static Future downloadsPath() async { + String downloads; + + if (Platform.isAndroid) { + downloads = "/storage/self/primary/Download"; + } else { + downloads = (await getTemporaryDirectory()).path; + } + + return downloads; + } +} diff --git a/filcnaplo/lib/helpers/subject_icon.dart b/filcnaplo/lib/helpers/subject_icon.dart new file mode 100644 index 0000000..67fa90a --- /dev/null +++ b/filcnaplo/lib/helpers/subject_icon.dart @@ -0,0 +1,46 @@ +import 'package:filcnaplo/icons/filc_icons.dart'; +import 'package:filcnaplo/utils/format.dart'; +import 'package:filcnaplo_kreta_api/models/subject.dart'; +import 'package:flutter/material.dart'; + +class SubjectIcon { + static IconData? lookup({Subject? subject, String? subjectName}) { + assert(!(subject == null && subjectName == null)); + + String name = subject?.name.toLowerCase().specialChars() ?? subjectName ?? ""; + String category = subject?.category.description.toLowerCase().specialChars() ?? ""; + + // TODO: check for categories + if (RegExp("mate(k|matika)").hasMatch(name) || category == "matematika") return Icons.calculate_outlined; + if (RegExp("magyar nyelv|nyelvtan").hasMatch(name)) return Icons.spellcheck_outlined; + if (RegExp("irodalom").hasMatch(name)) return Icons.menu_book_outlined; + if (RegExp("rajz|muvtori|muveszet|kultura").hasMatch(name)) return Icons.palette_outlined; + if (RegExp("tor(i|tenelem)").hasMatch(name)) return Icons.hourglass_empty_outlined; + if (RegExp("foldrajz").hasMatch(name)) return Icons.public_outlined; + if (RegExp("fizika").hasMatch(name)) return Icons.emoji_objects_outlined; + if (RegExp("^enek|zene|szolfezs|zongora|korus").hasMatch(name)) return Icons.music_note_outlined; + if (RegExp("^tes(i|tneveles)|sport").hasMatch(name)) return Icons.sports_soccer_outlined; + if (RegExp("kemia").hasMatch(name)) return Icons.science_outlined; + if (RegExp("biologia").hasMatch(name)) return Icons.pets_outlined; + if (RegExp("kornyezet|termeszet(tudomany|ismeret)|hon( es nep)?ismeret").hasMatch(name)) return Icons.eco_outlined; + if (RegExp("(hit|erkolcs)tan|vallas|etika").hasMatch(name)) return Icons.favorite_border_outlined; + if (RegExp("penzugy").hasMatch(name)) return Icons.savings_outlined; + if (RegExp("informatika|szoftver|iroda").hasMatch(name)) return Icons.computer_outlined; + if (RegExp("prog").hasMatch(name)) return Icons.code_outlined; + if (RegExp("halozat").hasMatch(name)) return Icons.wifi_tethering_outlined; + if (RegExp("szinhaz").hasMatch(name)) return Icons.theater_comedy_outlined; + if (RegExp("film|media").hasMatch(name)) return Icons.theaters_outlined; + if (RegExp("elektro(tech)?nika").hasMatch(name)) return Icons.electrical_services_outlined; + if (RegExp("gepesz|mernok|ipar").hasMatch(name)) return Icons.precision_manufacturing_outlined; + if (RegExp("technika").hasMatch(name)) return Icons.build_outlined; + if (RegExp("tanc").hasMatch(name)) return Icons.speaker_outlined; + if (RegExp("filozofia").hasMatch(name)) return Icons.psychology_outlined; + if (RegExp("osztaly(fonoki|kozosseg)").hasMatch(name)) return Icons.groups_outlined; + if (RegExp("gazdasag").hasMatch(name)) return Icons.account_balance_outlined; + if (RegExp("szorgalom").hasMatch(name)) return Icons.verified_outlined; + if (RegExp("magatartas").hasMatch(name)) return Icons.emoji_people_outlined; + if (RegExp("angol|nemet|francia|olasz|orosz|spanyol|latin|kinai|nyelv").hasMatch(name)) return Icons.translate_outlined; + if (RegExp("linux").hasMatch(name)) return FilcIcons.linux; + return Icons.widgets_outlined; + } +} diff --git a/filcnaplo/lib/helpers/update_helper.dart b/filcnaplo/lib/helpers/update_helper.dart new file mode 100644 index 0000000..5ea501f --- /dev/null +++ b/filcnaplo/lib/helpers/update_helper.dart @@ -0,0 +1,65 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:filcnaplo/api/client.dart'; +import 'package:filcnaplo/helpers/storage_helper.dart'; +import 'package:filcnaplo/models/release.dart'; +import 'package:open_file/open_file.dart'; + +enum UpdateState { prepare, downloading, installing } +typedef UpdateCallback = Function(double progress, UpdateState state); + +extension UpdateHelper on Release { + Future install({UpdateCallback? updateCallback}) async { + String downloads = await StorageHelper.downloadsPath(); + File apk = File("$downloads/filcnaplo-${version}.apk"); + + if (!await apk.exists()) { + updateCallback!(-1, UpdateState.downloading); + + var bytes = await download(updateCallback: updateCallback); + if (!await StorageHelper.write(apk.path, bytes)) throw "failed to write apk: permission denied"; + } + + updateCallback!(-1, UpdateState.installing); + + var result = await OpenFile.open(apk.path); + + if (result.type != ResultType.done) { + print("ERROR: installUpdate.openFile: " + result.message); + throw result.message; + } + + updateCallback(-1, UpdateState.prepare); + } + + Future download({UpdateCallback? updateCallback}) async { + var response = await FilcAPI.downloadRelease(this); + + List> chunks = []; + int downloaded = 0; + + var completer = Completer(); + + response?.stream.listen((List chunk) { + updateCallback!(downloaded / (response.contentLength ?? 0), UpdateState.downloading); + + chunks.add(chunk); + downloaded += chunk.length; + }, onDone: () { + // Save the file + final Uint8List bytes = Uint8List(response.contentLength ?? 0); + int offset = 0; + for (List chunk in chunks) { + bytes.setRange(offset, offset + chunk.length, chunk); + offset += chunk.length; + } + + completer.complete(bytes); + }); + + return completer.future; + } +} diff --git a/filcnaplo/lib/icons/filc_icons.dart b/filcnaplo/lib/icons/filc_icons.dart new file mode 100644 index 0000000..49257e9 --- /dev/null +++ b/filcnaplo/lib/icons/filc_icons.dart @@ -0,0 +1,10 @@ +import 'package:flutter/widgets.dart'; + +class FilcIcons { + static const IconData home = const FilcIconData(0x41); + static const IconData linux = const FilcIconData(0x42); +} + +class FilcIconData extends IconData { + const FilcIconData(int codePoint) : super(codePoint, fontFamily: "FilcIcons"); +} diff --git a/filcnaplo/lib/main.dart b/filcnaplo/lib/main.dart new file mode 100644 index 0000000..8f41dbd --- /dev/null +++ b/filcnaplo/lib/main.dart @@ -0,0 +1,71 @@ +import 'package:filcnaplo/api/providers/user_provider.dart'; +import 'package:filcnaplo/api/providers/database_provider.dart'; +import 'package:filcnaplo/database/init.dart'; +import 'package:filcnaplo/models/settings.dart'; +import 'package:flutter/material.dart'; +import 'package:filcnaplo/app.dart'; +import 'package:flutter/services.dart'; +import 'package:filcnaplo_mobile_ui/screens/error_screen.dart'; + +/* + * TODO: public beta checklist + * + * Pages: + * - [x] Home + * ~~- [ ] search~~ + * - [x] user data + * - [x] greeting + * - [x] Grades + * - [x] Timetable + * - [x] Messages + * - [x] Absences + * + * - [ ] i18n + * - [ ] auto updater + * - [ ] news WIP + * - [ ] settings (about) + */ + +void main() async { + // Initalize + WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized(); + binding.renderView.automaticSystemUiAdjustment = false; + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); + + // Startup + Startup startup = Startup(); + await startup.start(); + + // Custom error page + ErrorWidget.builder = errorBuilder; + + // Run App + runApp(App(database: startup.database, settings: startup.settings, user: startup.user)); +} + +class Startup { + late SettingsProvider settings; + late UserProvider user; + late DatabaseProvider database; + + Future start() async { + var db = await initDB(); + await db.close(); + database = DatabaseProvider(); + await database.init(); + settings = await database.query.getSettings(); + user = await database.query.getUsers(); + } +} + +Widget errorBuilder(FlutterErrorDetails details) { + return Builder(builder: (context) { + if (Navigator.of(context).canPop()) Navigator.pop(context); + + WidgetsBinding.instance?.addPostFrameCallback((_) { + Navigator.of(context, rootNavigator: true).push(MaterialPageRoute(builder: (ctx) => ErrorScreen(details))); + }); + + return Container(); + }); +} diff --git a/filcnaplo/lib/models/config.dart b/filcnaplo/lib/models/config.dart new file mode 100644 index 0000000..87e022b --- /dev/null +++ b/filcnaplo/lib/models/config.dart @@ -0,0 +1,41 @@ +import 'dart:io'; + +import 'package:package_info_plus/package_info_plus.dart'; + +class Config { + String _userAgent; + String? _version; + Map? json; + + Config({required String userAgent, this.json}) : _userAgent = userAgent { + PackageInfo.fromPlatform().then((value) => _version = value.version); + } + + factory Config.fromJson(Map json) { + return Config( + userAgent: json["user_agent"] ?? "hu.filc.naplo/\$0/\$1/\$2", + json: json, + ); + } + + String get userAgent => _userAgent.replaceAll("\$0", _version ?? "0").replaceAll("\$1", platform).replaceAll("\$2", "0"); + + static String get platform { + if (Platform.isAndroid) { + return "Android"; + } else if (Platform.isIOS) { + return "iOS"; + } else if (Platform.isLinux) { + return "Linux"; + } else if (Platform.isWindows) { + return "Windows"; + } else if (Platform.isMacOS) { + return "MacOS"; + } else { + return "Unknown"; + } + } + + @override + String toString() => json.toString(); +} diff --git a/filcnaplo/lib/models/news.dart b/filcnaplo/lib/models/news.dart new file mode 100644 index 0000000..6bf6aaa --- /dev/null +++ b/filcnaplo/lib/models/news.dart @@ -0,0 +1,31 @@ +class News { + String title; + String content; + String link; + String openLabel; + String platform; + bool emergency; + Map? json; + + News({ + required this.title, + required this.content, + required this.link, + required this.openLabel, + required this.platform, + required this.emergency, + this.json, + }); + + factory News.fromJson(Map json) { + return News( + title: json["title"] ?? "", + content: json["content"] ?? "", + link: json["link"] ?? "", + openLabel: json["open_label"] ?? "", + platform: json["platform"] ?? "", + emergency: json["emergency"] ?? false, + json: json, + ); + } +} diff --git a/filcnaplo/lib/models/release.dart b/filcnaplo/lib/models/release.dart new file mode 100644 index 0000000..ca52bf1 --- /dev/null +++ b/filcnaplo/lib/models/release.dart @@ -0,0 +1,129 @@ +class Release { + String tag; + Version version; + String author; + String body; + List downloads; + bool prerelease; + + Release({ + required this.tag, + required this.author, + required this.body, + required this.downloads, + required this.prerelease, + required this.version, + }); + + factory Release.fromJson(Map json) { + return Release( + tag: json["tag_name"] ?? Version.zero.toString(), + author: json["author"] != null ? json["author"]["login"] ?? "" : "", + body: json["body"] ?? "", + downloads: json["assets"] != null ? json["assets"].map((a) => a["browser_download_url"] ?? "").toList().cast() : [], + prerelease: json["prerelease"] ?? false, + version: Version.fromString(json["tag_name"] ?? ""), + ); + } +} + +class Version { + final int major; + final int minor; + final int patch; + final String prerelease; + final int prever; + + const Version(this.major, this.minor, this.patch, {this.prerelease = "", this.prever = 0}); + + factory Version.fromString(String o) { + String string = o; + int x = 0, y = 0, z = 0; // major, minor, patch (1.1.1) + String pre = ""; // prerelease string (-beta) + int prev = 0; // prerelease version (.1) + + try { + // cut build + string = string.split("+")[0]; + + // cut prerelease + var p = string.split("-"); + string = p[0]; + if (p.length > 1) pre = p[1]; + + // prerelease + p = pre.split("."); + + if (p.length > 1) prev = int.tryParse(p[1]) ?? 0; + + // check for valid prerelease name + if (p[0] != "") { + if (prereleases.contains(p[0].toLowerCase().trim())) + pre = p[0]; + else + throw "invalid prerelease name: ${p[0]}"; + } + + // core + p = string.split("."); + if (p.length != 3) throw "invalid core length: ${p.length}"; + + x = int.tryParse(p[0]) ?? 0; + y = int.tryParse(p[1]) ?? 0; + z = int.tryParse(p[2]) ?? 0; + + return Version(x, y, z, prerelease: pre, prever: prev); + } catch (error) { + print("WARNING: Failed to parse version ($o): $error"); + return Version.zero; + } + } + + @override + bool operator ==(other) { + if (other is! Version) return false; + return other.major == major && other.minor == minor && other.patch == patch && other.prei == prei && other.prever == prever; + } + + int compareTo(Version other) { + if (other == this) return 0; + + if (major > other.major) { + return 1; + } else if (major == other.major) { + if (minor > other.minor) { + return 1; + } else if (minor == other.minor) { + if (patch > other.patch) { + return 1; + } else if (patch == other.patch) { + if (prei > other.prei) { + return 1; + } else if (other.prei == prei) { + if (prever > other.prever) { + return 1; + } + } + } + } + } + + return -1; + } + + @override + String toString() { + String str = "$major.$minor.$patch"; + if (prerelease != "") str += "-$prerelease"; + if (prever != 0) str += ".$prever"; + return str; + } + + int get prei { + if (prerelease != "") return prereleases.indexOf(prerelease); + return prereleases.length; + } + + static const zero = Version(0, 0, 0); + static const List prereleases = ["dev", "pre", "alpha", "beta", "rc"]; +} diff --git a/filcnaplo/lib/models/settings.dart b/filcnaplo/lib/models/settings.dart new file mode 100644 index 0000000..d460dd1 --- /dev/null +++ b/filcnaplo/lib/models/settings.dart @@ -0,0 +1,246 @@ +import 'dart:convert'; + +import 'package:filcnaplo/api/providers/database_provider.dart'; +import 'package:filcnaplo/models/config.dart'; +import 'package:filcnaplo/theme.dart'; +import 'package:flutter/material.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:provider/provider.dart'; + +enum Pages { home, grades, timetable, messages, absences } +enum UpdateChannel { stable, beta, dev } +enum VibrationStrength { light, medium, strong } + +class SettingsProvider extends ChangeNotifier { + PackageInfo? _packageInfo; + + // en_en, hu_hu, de_de + String _language; + Pages _startPage; + // divide by 10 + int _rounding; + ThemeMode _theme; + AccentColor _accentColor; + // zero is one, ... + List _gradeColors; + bool _newsEnabled; + int _newsState; + bool _notificationsEnabled; + /* + notificationsBitfield values: + + 1 << 0 current lesson + 1 << 1 newsletter + 1 << 2 grades + 1 << 3 notes and events + 1 << 4 inbox messages + 1 << 5 substituted lessons and cancelled lessons + 1 << 6 absences and misses + 1 << 7 exams and homework + */ + int _notificationsBitfield; + // minutes: times 15 + int _notificationPollInterval; + bool _developerMode; + bool _vibrate; + VibrationStrength _vibrationStrength; + bool _ABweeks; + bool _swapABweeks; + UpdateChannel _updateChannel; + Config _config; + + SettingsProvider({ + required String language, + required Pages startPage, + required int rounding, + required ThemeMode theme, + required AccentColor accentColor, + required List gradeColors, + required bool newsEnabled, + required int newsState, + required bool notificationsEnabled, + required int notificationsBitfield, + required bool developerMode, + required int notificationPollInterval, + required bool vibrate, + required VibrationStrength vibrationStrength, + required bool ABweeks, + required bool swapABweeks, + required UpdateChannel updateChannel, + required Config config, + }) : _language = language, + _startPage = startPage, + _rounding = rounding, + _theme = theme, + _accentColor = accentColor, + _gradeColors = gradeColors, + _newsEnabled = newsEnabled, + _newsState = newsState, + _notificationsEnabled = notificationsEnabled, + _notificationsBitfield = notificationsBitfield, + _developerMode = developerMode, + _notificationPollInterval = notificationPollInterval, + _vibrate = vibrate, + _vibrationStrength = vibrationStrength, + _ABweeks = ABweeks, + _swapABweeks = swapABweeks, + _updateChannel = updateChannel, + _config = config { + PackageInfo.fromPlatform().then((PackageInfo packageInfo) { + _packageInfo = packageInfo; + }); + } + + factory SettingsProvider.fromMap(Map map) { + return SettingsProvider( + language: map["language"], + startPage: Pages.values[map["start_page"]], + rounding: map["rounding"], + theme: ThemeMode.values[map["theme"]], + accentColor: AccentColor.values[map["accent_color"]], + gradeColors: [ + Color(map["grade_color1"]), + Color(map["grade_color2"]), + Color(map["grade_color3"]), + Color(map["grade_color4"]), + Color(map["grade_color5"]), + ], + newsEnabled: map["news"] == 1 ? true : false, + newsState: map["news_state"], + notificationsEnabled: map["notifications"] == 1 ? true : false, + notificationsBitfield: map["notifications_bitfield"], + notificationPollInterval: map["notification_poll_interval"], + developerMode: map["developer_mode"] == 1 ? true : false, + vibrate: map["vibrate"] == 1 ? true : false, + vibrationStrength: VibrationStrength.values[map["vibration_strength"]], + ABweeks: map["ab_weeks"] == 1 ? true : false, + swapABweeks: map["swap_ab_weeks"] == 1 ? true : false, + updateChannel: UpdateChannel.values[map["update_channel"]], + config: Config.fromJson(jsonDecode(map["config"] ?? "{}")), + ); + } + + Map toMap() { + return { + "language": _language, + "start_page": _startPage.index, + "rounding": _rounding, + "theme": _theme.index, + "accent_color": _accentColor.index, + "news": _newsEnabled ? 1 : 0, + "news_state": _newsState, + "notifications": _notificationsEnabled ? 1 : 0, + "notifications_bitfield": _notificationsBitfield, + "developer_mode": _developerMode ? 1 : 0, + "grade_color1": _gradeColors[0].value, + "grade_color2": _gradeColors[1].value, + "grade_color3": _gradeColors[2].value, + "grade_color4": _gradeColors[3].value, + "grade_color5": _gradeColors[4].value, + "update_channel": _updateChannel.index, + "vibrate": _vibrate ? 1 : 0, + "vibration_strength": _vibrationStrength.index, + "ab_weeks": _ABweeks ? 1 : 0, + "swap_ab_weeks": _swapABweeks ? 1 : 0, + "notification_poll_interval": _notificationPollInterval, + "config": jsonEncode(config.json), + }; + } + + factory SettingsProvider.defaultSettings() { + return SettingsProvider( + language: "hu", + startPage: Pages.home, + rounding: 5, + theme: ThemeMode.system, + accentColor: AccentColor.filc, + gradeColors: [ + DarkAppColors().red, + DarkAppColors().orange, + DarkAppColors().yellow, + DarkAppColors().green, + DarkAppColors().filc, + ], + newsEnabled: true, + newsState: -1, + notificationsEnabled: true, + notificationsBitfield: 255, + developerMode: false, + notificationPollInterval: 1, + vibrate: true, + vibrationStrength: VibrationStrength.medium, + ABweeks: false, + swapABweeks: false, + updateChannel: UpdateChannel.stable, + config: Config.fromJson({}), + ); + } + + // Getters + String get language => _language; + Pages get startPage => _startPage; + int get rounding => _rounding; + ThemeMode get theme => _theme; + AccentColor get accentColor => _accentColor; + List get gradeColors => _gradeColors; + bool get newsEnabled => _newsEnabled; + int get newsState => _newsState; + bool get notificationsEnabled => _notificationsEnabled; + int get notificationsBitfield => _notificationsBitfield; + bool get developerMode => _developerMode; + int get notificationPollInterval => _notificationPollInterval; + bool get vibrate => _vibrate; + VibrationStrength get vibrationStrength => _vibrationStrength; + bool get ABweeks => _ABweeks; + bool get swapABweeks => _swapABweeks; + UpdateChannel get updateChannel => _updateChannel; + PackageInfo? get packageInfo => _packageInfo; + Config get config => _config; + + Future update( + BuildContext context, { + DatabaseProvider? database, + String? language, + Pages? startPage, + int? rounding, + ThemeMode? theme, + AccentColor? accentColor, + List? gradeColors, + bool? newsEnabled, + int? newsState, + bool? notificationsEnabled, + int? notificationsBitfield, + bool? developerMode, + int? notificationPollInterval, + bool? vibrate, + VibrationStrength? vibrationStrength, + bool? ABweeks, + bool? swapABweeks, + UpdateChannel? updateChannel, + Config? config, + }) async { + if (language != null && language != _language) _language = language; + if (startPage != null && startPage != _startPage) _startPage = startPage; + if (rounding != null && rounding != _rounding) _rounding = rounding; + if (theme != null && theme != _theme) _theme = theme; + if (accentColor != null && accentColor != _accentColor) _accentColor = accentColor; + if (gradeColors != null && gradeColors != _gradeColors) _gradeColors = gradeColors; + if (newsEnabled != null && newsEnabled != _newsEnabled) _newsEnabled = newsEnabled; + if (newsState != null && newsState != _newsState) _newsState = newsState; + if (notificationsEnabled != null && notificationsEnabled != _notificationsEnabled) _notificationsEnabled = notificationsEnabled; + if (notificationsBitfield != null && notificationsBitfield != _notificationsBitfield) _notificationsBitfield = notificationsBitfield; + if (developerMode != null && developerMode != _developerMode) _developerMode = developerMode; + if (notificationPollInterval != null && notificationPollInterval != _notificationPollInterval) + _notificationPollInterval = notificationPollInterval; + if (vibrate != null && vibrate != _vibrate) _vibrate = vibrate; + if (vibrationStrength != null && vibrationStrength != _vibrationStrength) _vibrationStrength = vibrationStrength; + if (ABweeks != null && ABweeks != _ABweeks) _ABweeks = ABweeks; + if (swapABweeks != null && swapABweeks != _swapABweeks) _swapABweeks = swapABweeks; + if (updateChannel != null && updateChannel != _updateChannel) _updateChannel = updateChannel; + if (config != null && config != _config) _config = config; + + if (database == null) database = Provider.of(context, listen: false); + await database.store.storeSettings(this); + notifyListeners(); + } +} diff --git a/filcnaplo/lib/models/user.dart b/filcnaplo/lib/models/user.dart new file mode 100644 index 0000000..69719ad --- /dev/null +++ b/filcnaplo/lib/models/user.dart @@ -0,0 +1,64 @@ +import 'dart:convert'; +import 'package:filcnaplo_kreta_api/client/api.dart'; +import 'package:filcnaplo_kreta_api/models/student.dart'; +import 'package:uuid/uuid.dart'; + +class User { + late String id; + String username; + String password; + String instituteCode; + String name; + Student student; + + User({ + String? id, + required this.name, + required this.username, + required this.password, + required this.instituteCode, + required this.student, + }) { + if (id != null) { + this.id = id; + } else { + this.id = Uuid().v4(); + } + } + + factory User.fromMap(Map map) { + return User( + id: map["id"], + instituteCode: map["institute_code"], + username: map["username"], + password: map["password"], + name: map["name"], + student: Student.fromJson(jsonDecode(map["student"])), + ); + } + + Map toMap() { + return { + "id": id, + "username": username, + "password": password, + "institute_code": instituteCode, + "name": name, + "student": jsonEncode(student.json), + }; + } + + static Map loginBody({ + required String username, + required String password, + required String instituteCode, + }) { + return { + "userName": username, + "password": password, + "institute_code": instituteCode, + "grant_type": "password", + "client_id": KretaAPI.CLIENT_ID, + }; + } +} diff --git a/filcnaplo/lib/modules/autoupdate/old/autoUpdater.dart b/filcnaplo/lib/modules/autoupdate/old/autoUpdater.dart new file mode 100644 index 0000000..b776599 --- /dev/null +++ b/filcnaplo/lib/modules/autoupdate/old/autoUpdater.dart @@ -0,0 +1,376 @@ +// import 'dart:io'; +// import 'dart:typed_data'; + +// import 'package:feather_icons_flutter/feather_icons_flutter.dart'; +// import 'package:filcnaplo/data/context/app.dart'; +// import 'package:filcnaplo/ui/common/bottom_card.dart'; +// import 'package:filcnaplo/utils/colors.dart'; +// import 'package:flutter/cupertino.dart'; +// import 'package:flutter/material.dart'; +// import 'package:filcnaplo/generated/i18n.dart'; +// import 'package:flutter_markdown/flutter_markdown.dart'; +// import 'package:http/http.dart' as http; +// import 'package:path_provider/path_provider.dart'; +// import 'package:open_file/open_file.dart'; +// import 'package:filcnaplo/data/context/theme.dart'; + +// import '../../ui/common/custom_snackbar.dart'; + +// enum InstallState { update, downloading, saving, installing } + +// class AutoUpdater extends StatefulWidget { +// @override +// _AutoUpdaterState createState() => _AutoUpdaterState(); +// } + +// class _AutoUpdaterState extends State { +// bool buttonPressed = false; +// double progress; +// bool displayProgress = false; +// InstallState installState; + +// void downloadCallback( +// double progress, bool displayProgress, InstallState installState) { +// if (mounted) { +// setState(() { +// this.progress = progress; +// this.displayProgress = displayProgress; +// this.installState = installState; +// }); +// } +// } + +// @override +// Widget build(BuildContext context) { +// if (!buttonPressed) installState = InstallState.update; + +// String buttonText; + +// switch (installState) { +// case InstallState.update: +// buttonText = I18n.of(context).update; +// break; +// case InstallState.downloading: +// buttonText = I18n.of(context).updateDownloading; +// break; +// case InstallState.saving: +// buttonText = I18n.of(context).updateSaving; +// break; +// case InstallState.installing: +// buttonText = I18n.of(context).updateInstalling; +// break; +// default: +// buttonText = I18n.of(context).error; +// } + +// return BottomCard( +// child: Padding( +// padding: EdgeInsets.only(top: 13), +// child: Column( +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// Expanded( +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Row( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Expanded( +// child: ListTile( +// contentPadding: EdgeInsets.only(left: 8.0), +// title: Text( +// I18n.of(context).updateNewVersion, +// style: TextStyle( +// fontSize: 23, +// fontWeight: FontWeight.bold, +// ), +// ), +// subtitle: Text( +// app.user.sync.release.latestRelease.version, +// style: TextStyle( +// fontWeight: FontWeight.bold, +// fontSize: 18, +// ), +// ), +// ), +// ), +// ClipRRect( +// borderRadius: BorderRadius.circular(12.0), +// child: Image.asset( +// "assets/logo.png", +// width: 60, +// ), +// ), +// ], +// ), +// Padding( +// padding: EdgeInsets.all(8), +// child: Row( +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// Text( +// I18n.of(context).updateChanges + ":", +// style: TextStyle(fontSize: 18), +// ), +// Text( +// I18n.of(context).updateCurrentVersion + +// ": " + +// app.currentAppVersion, +// style: +// TextStyle(color: Colors.white.withAlpha(180)), +// ), +// ], +// ), +// ), +// Expanded( +// child: Padding( +// padding: EdgeInsets.all(8.0), +// child: Builder(builder: (context) { +// try { +// return Markdown( +// shrinkWrap: true, +// data: app.user.sync.release.latestRelease.notes, +// padding: EdgeInsets.all(0), +// physics: BouncingScrollPhysics(), +// styleSheet: MarkdownStyleSheet( +// p: TextStyle( +// fontSize: 15, +// color: app.settings.theme.textTheme +// .bodyText1.color, +// ), +// ), +// ); +// } catch (e) { +// print( +// "ERROR: autoUpdater.dart failed to show markdown release notes: " + +// e.toString()); +// return Text( +// app.user.sync.release.latestRelease.notes); +// } +// })), +// ) +// ], +// ), +// ), +// Stack( +// children: [ +// Row( +// mainAxisSize: MainAxisSize.max, +// mainAxisAlignment: MainAxisAlignment.end, +// crossAxisAlignment: CrossAxisAlignment.center, +// children: [ +// IconButton( +// icon: Icon(FeatherIcons.helpCircle), +// onPressed: () { +// showDialog( +// context: context, +// builder: (context) => HelpDialog()); +// }) +// ], +// ), +// Center( +// child: MaterialButton( +// color: ThemeContext.filcGreen, +// elevation: 0, +// highlightElevation: 0, +// shape: RoundedRectangleBorder( +// borderRadius: BorderRadius.circular(45.0)), +// child: Padding( +// padding: EdgeInsets.symmetric(vertical: 9), +// child: Row( +// mainAxisSize: MainAxisSize.min, +// children: [ +// if (displayProgress) +// Container( +// margin: EdgeInsets.only(right: 10), +// height: 19, +// width: 19, +// child: CircularProgressIndicator( +// value: progress, +// valueColor: AlwaysStoppedAnimation( +// Colors.white), +// strokeWidth: 3.2, +// ), +// ), +// Text( +// buttonText.toUpperCase(), +// style: TextStyle( +// fontSize: 17, +// fontWeight: FontWeight.bold, +// color: Colors.white), +// ) +// ], +// ), +// ), +// onPressed: () { +// if (!buttonPressed) +// installUpdate(context, downloadCallback); +// buttonPressed = true; +// }, +// ), +// ), +// ], +// ), +// ]), +// ), +// ); +// } + +// Future installUpdate(BuildContext context, Function updateDisplay) async { +// updateDisplay(null, true, InstallState.downloading); + +// String dir = (await getApplicationDocumentsDirectory()).path; +// String latestVersion = app.user.sync.release.latestRelease.version; +// String filename = "filcnaplo-$latestVersion.apk"; +// File apk = File("$dir/$filename"); + +// var httpClient = http.Client(); +// var request = new http.Request( +// 'GET', Uri.parse(app.user.sync.release.latestRelease.url)); +// var response = httpClient.send(request); + +// List> chunks = []; +// int downloaded = 0; + +// response.asStream().listen((http.StreamedResponse r) { +// r.stream.listen((List chunk) { +// // Display percentage of completion +// updateDisplay( +// downloaded / r.contentLength, true, InstallState.downloading); + +// chunks.add(chunk); +// downloaded += chunk.length; +// }, onDone: () async { +// // Display percentage of completion +// updateDisplay(null, true, InstallState.saving); + +// // Save the file +// final Uint8List bytes = Uint8List(r.contentLength); +// int offset = 0; +// for (List chunk in chunks) { +// bytes.setRange(offset, offset + chunk.length, chunk); +// offset += chunk.length; +// } +// await apk.writeAsBytes(bytes); + +// updateDisplay(null, true, InstallState.installing); +// if (mounted) { +// OpenFile.open(apk.path).then((result) { +// if (result.type != ResultType.done) { +// print("ERROR: installUpdate.openFile: " + result.message); +// ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( +// message: I18n.of(context).error, +// color: Colors.red, +// )); +// } +// Navigator.pop(context); +// }); +// } +// }); +// }); +// } +// } + +// class HelpDialog extends StatelessWidget { +// @override +// Widget build(BuildContext context) { +// return Scaffold( +// backgroundColor: Colors.transparent, +// body: Container( +// decoration: BoxDecoration( +// color: app.settings.theme.backgroundColor, +// borderRadius: BorderRadius.circular(4.0), +// ), +// padding: EdgeInsets.symmetric(vertical: 15, horizontal: 10), +// margin: EdgeInsets.all(32.0), +// child: Column(children: [ +// Padding( +// padding: EdgeInsets.all(8.0), +// child: Center( +// child: Icon(FeatherIcons.helpCircle), +// ), +// ), +// Expanded( +// child: Markdown( +// shrinkWrap: true, +// data: helpText, +// padding: EdgeInsets.all(0), +// physics: BouncingScrollPhysics(), +// styleSheet: MarkdownStyleSheet( +// p: TextStyle( +// fontSize: 15, +// color: Colors.white, +// ), +// ), +// ), +// ), +// MaterialButton( +// child: Text(I18n.of(context).dialogDone), +// onPressed: () { +// Navigator.of(context).pop(); +// }) +// ]))); +// } +// } + +// const String helpText = +// """A **FRISSÍTÉS** gombot megnyomva az app automatikusan letölti a GitHubról a legfrissebb Filc telepítőt.\\ +// Ez kb. 50 MB adatforgalommal jár.\\ +// A letöltött telepítőt ezután megnyitja az app. + +// Telefonmárkától és Android verziótól függően nagyon különböző a telepítés folyamata, de ezekre figyelj: +// - Ha kérdezi a telefon, **engedélyezd a Filctől származó appok telepítését**, majd nyomd meg a vissza gombot.\\ +// _(Újabb Android verziók)_ +// - Ha szól a telefon hogy a külső appok telepítése biztonsági okokból tiltott, a megjelenő gombbal **ugorj a beállításokba és kapcsold be az Ismeretlen források lehetőséget.**\\ +// _(Régi Android verziók)_ + +// A telepítés után újra megnyílik az app, immár a legfrissebb verzióval. Az indítás során törli a telepítéshez használt .apk fájlokat, így a tárhelyed miatt nem kell aggódnod. +// """; + +// class AutoUpdateButton extends StatelessWidget { +// @override +// Widget build(BuildContext context) { +// return Padding( +// padding: EdgeInsets.only(bottom: 5.0), +// child: Container( +// margin: EdgeInsets.symmetric(horizontal: 14.0), +// child: MaterialButton( +// color: textColor(Theme.of(context).backgroundColor).withAlpha(25), +// elevation: 0, +// highlightElevation: 0, +// shape: +// RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)), +// child: ListTile( +// contentPadding: EdgeInsets.zero, +// leading: Icon(FeatherIcons.download, color: app.settings.appColor), +// title: Row( +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// Flexible( +// child: Text( +// I18n.of(context).updateAvailable, +// softWrap: false, +// overflow: TextOverflow.fade, +// )), +// Text( +// app.user.sync.release.latestRelease.version, +// style: TextStyle( +// color: app.settings.appColor, +// fontWeight: FontWeight.bold), +// ) +// ], +// ), +// ), +// onPressed: () { +// showModalBottomSheet( +// context: context, +// backgroundColor: Colors.transparent, +// builder: (BuildContext context) => AutoUpdater(), +// ); +// }, +// ), +// ), +// ); +// } +// } diff --git a/filcnaplo/lib/modules/autoupdate/old/releaseSync.dart b/filcnaplo/lib/modules/autoupdate/old/releaseSync.dart new file mode 100644 index 0000000..0656b9f --- /dev/null +++ b/filcnaplo/lib/modules/autoupdate/old/releaseSync.dart @@ -0,0 +1,106 @@ +// import 'dart:io'; +// import 'package:filcnaplo/data/context/app.dart'; + +// class ReleaseSync { +// Release latestRelease; +// bool isNew = false; + +// Future sync() async { +// if (!Platform.isAndroid) return; + +// var releasesJson = await app.user.kreta.getReleases(); +// if (!app.settings.preUpdates) { +// for (Map r in releasesJson) { +// if (!r["prerelease"]) { +// latestRelease = Release.fromJson(r); +// break; +// } +// } +// } else { +// // List releases = []; +// latestRelease = Release.fromJson(releasesJson.first); +// } + +// isNew = compareVersions(latestRelease.version, app.currentAppVersion); +// } + +// bool compareVersions(String gitHub, String existing) { +// try { +// bool stableGitHub = false; +// List gitHubPartsStrings = gitHub.split(RegExp(r"[.-]")); +// List gitHubParts = []; + +// for (String s in gitHubPartsStrings) { +// if (s.startsWith("beta")) { +// s = s.replaceAll("beta", ""); +// } else if (s.startsWith("pre")) { +// //! pre versions have lower priority than beta +// s = s.replaceAll("pre", ""); +// gitHubParts.add(0); +// } else { +// stableGitHub = true; +// } +// try { +// gitHubParts.add(int.parse(s)); +// } catch (e) { +// print("ERROR: ReleaseSync.compareVersions: " + e.toString()); +// } +// } +// if (stableGitHub) gitHubParts.add(1000); + +// bool stableExisting = false; +// List existingPartsStrings = existing.split(RegExp(r"[.-]")); +// List existingParts = []; + +// for (String s in existingPartsStrings) { +// if (s.startsWith("beta")) { +// s = s.replaceAll("beta", ""); +// } else if (s.startsWith("pre")) { +// //! pre versions have lower priority than beta +// s = s.replaceAll("pre", ""); +// existingParts.add(0); +// } else { +// stableExisting = true; +// } +// try { +// existingParts.add(int.parse(s)); +// } catch (e) { +// print("ERROR: ReleaseSync.compareVersions: " + e.toString()); +// } +// } +// // what even +// if (stableExisting) existingParts.add(1000); + +// int i = 0; +// for (var gitHubPart in gitHubParts) { +// if (gitHubPart > existingParts[i]) +// return true; +// else if (existingParts[i] > gitHubPart) return false; +// i++; +// } +// return false; +// } catch (e) { +// print("ERROR: ReleaseSync.compareVersions: " + e.toString()); +// return false; +// } +// } +// } + +// class Release { +// String version; +// String notes; +// String url; +// bool isExperimental; + +// Release(this.version, this.notes, this.url, this.isExperimental); + +// factory Release.fromJson(Map json) { +// List assets = []; +// json["assets"].forEach((json) { +// assets.add(json); +// }); +// String url = assets[0]["browser_download_url"]; + +// return Release(json["tag_name"], json["body"], url, json["prerelease"]); +// } +// } diff --git a/filcnaplo/lib/modules/printing/main.dart b/filcnaplo/lib/modules/printing/main.dart new file mode 100644 index 0000000..fd64a55 --- /dev/null +++ b/filcnaplo/lib/modules/printing/main.dart @@ -0,0 +1,148 @@ +// import 'package:filcnaplo/data/context/app.dart'; +// import 'package:filcnaplo/data/models/lesson.dart'; +// import 'package:filcnaplo/generated/i18n.dart'; +// import 'package:filcnaplo/ui/common/custom_snackbar.dart'; +// import 'package:filcnaplo/ui/pages/planner/timetable/day.dart'; +// import 'package:filcnaplo/utils/format.dart'; +// import 'package:filcnaplo/modules/printing/printerDebugScreen.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter/services.dart' show rootBundle; +// import 'package:pdf/pdf.dart'; +// import 'package:pdf/widgets.dart' as pw; +// import 'package:printing/printing.dart'; +// import 'package:filcnaplo/ui/pages/planner/timetable/builder.dart'; +// import 'package:flutter/foundation.dart'; + +// /* +// Author: daaniiieel +// Name: Timetable Printer (Experimental) +// Description: This module prints out the timetable for the selected user on the +// current week. +// */ + +// class TimetablePrinter { +// pw.Document build( +// BuildContext context, pw.Document pdf, List days, int min, int max) { +// List rows = []; + +// // build header row +// List headerChildren = [pw.Container()]; +// days.forEach((day) => headerChildren.add(pw.Padding( +// padding: pw.EdgeInsets.all(5), +// child: +// pw.Center(child: pw.Text(weekdayStringShort(context, day.date.weekday)))))); +// pw.TableRow headerRow = pw.TableRow( +// children: headerChildren, +// verticalAlignment: pw.TableCellVerticalAlignment.middle); +// rows.add(headerRow); + +// // for each row +// for (int i = min; i < max; i++) { +// var children = []; +// var row = pw.TableRow(children: children); + +// children.add(pw.Padding( +// padding: pw.EdgeInsets.all(5), +// child: pw.Center(child: pw.Text('$i. ')))); +// days.forEach((Day day) { +// var lesson = day.lessons.firstWhere( +// (element) => element.lessonIndex != '+' +// ? int.parse(element.lessonIndex) == i +// : false, +// orElse: () => null); + +// children.add(lesson != null +// ? pw.Padding( +// padding: pw.EdgeInsets.fromLTRB(5, 10, 5, 5), +// child: pw.Column(children: [ +// pw.Text(lesson.name ?? lesson.subject.name), +// pw.Footer( +// leading: pw.Text(lesson.room), +// trailing: pw.Text(monogram(lesson.teacher))), +// ])) +// : pw.Padding(padding: pw.EdgeInsets.all(5))); +// }); +// rows.add(row); +// } + +// // add timetable to pdf +// pw.Table table = pw.Table( +// children: rows, +// border: pw.TableBorder.all(), +// defaultVerticalAlignment: pw.TableCellVerticalAlignment.middle, +// ); + +// // header and footer +// pw.Footer footer = pw.Footer( +// trailing: pw.Text('filcnaplo.hu'), +// margin: pw.EdgeInsets.only(top: 12.0), +// ); +// String className = app.user.sync.student.student.className; + +// pw.Footer header = pw.Footer( +// margin: pw.EdgeInsets.all(5), +// title: pw.Text(className, style: pw.TextStyle(fontSize: 30)), +// ); + +// pdf.addPage(pw.Page( +// pageFormat: PdfPageFormat.a4 +// .landscape, // so the page looks normal both in portrait and landscape +// orientation: pw.PageOrientation.landscape, +// build: (pw.Context context) => +// pw.Column(children: [header, table, footer]))); + +// return pdf; +// } + +// void printPDF(final _scaffoldKey, BuildContext context) { +// // pdf theme (for unicode support) +// rootBundle.load("assets/Roboto-Regular.ttf").then((font) { +// pw.ThemeData myTheme = pw.ThemeData.withFont(base: pw.Font.ttf(font)); +// pw.Document pdf = pw.Document(theme: myTheme); + +// // sync indicator +// ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( +// message: I18n.of(context).syncTimetable, +// )); + +// // get a builder and build current week +// final timetableBuilder = TimetableBuilder(); +// timetableBuilder.build(timetableBuilder.getCurrentWeek()); + +// int minLessonIndex = 1; +// int maxLessonIndex = 1; +// List weekDays = timetableBuilder.week.days; +// for (Day day in weekDays) { +// for (Lesson lesson in day.lessons) { +// if (lesson.lessonIndex == '+') { +// continue; +// } +// if (int.parse(lesson.lessonIndex) < minLessonIndex) { +// minLessonIndex = int.parse(lesson.lessonIndex); +// } +// if (int.parse(lesson.lessonIndex) > maxLessonIndex) { +// maxLessonIndex = int.parse(lesson.lessonIndex); +// } +// } +// } + +// pdf = build(context, pdf, weekDays, minLessonIndex, maxLessonIndex); + +// // print pdf +// if (kReleaseMode) { +// Printing.layoutPdf(onLayout: (format) => pdf.save()).then((success) { +// if (success) +// ScaffoldMessenger.of(context).showSnackBar(CustomSnackBar( +// message: I18n.of(context).settingsExportExportTimetableSuccess, +// )); +// }); +// } else { +// Navigator.push( +// context, +// MaterialPageRoute( +// builder: (c) => +// PrintingDebugScreen((format) => Future.value(pdf.save())))); +// } +// }); +// } +// } diff --git a/filcnaplo/lib/modules/printing/printerDebugScreen.dart b/filcnaplo/lib/modules/printing/printerDebugScreen.dart new file mode 100644 index 0000000..71a2bbe --- /dev/null +++ b/filcnaplo/lib/modules/printing/printerDebugScreen.dart @@ -0,0 +1,17 @@ +// import 'dart:typed_data'; + +// import 'package:flutter/material.dart'; +// import 'package:printing/printing.dart'; +// import 'package:pdf/pdf.dart'; + +// class PrintingDebugScreen extends StatelessWidget { +// final Future Function(PdfPageFormat) builder; + +// PrintingDebugScreen( +// this.builder, +// ); +// @override +// Widget build(BuildContext context) { +// return Scaffold(body: Center(child: PdfPreview(build: this.builder))); +// } +// } diff --git a/filcnaplo/lib/theme.dart b/filcnaplo/lib/theme.dart new file mode 100644 index 0000000..da445ba --- /dev/null +++ b/filcnaplo/lib/theme.dart @@ -0,0 +1,145 @@ +import 'package:filcnaplo/models/settings.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class AppTheme { + // Dev note: All of these could be constant variables, but this is better for + // development (you don't have to hot-restart) + + static const String _fontFamily = "Montserrat"; + + // Light Theme + static ThemeData lightTheme(BuildContext context) { + var lightColors = LightAppColors(); + Color accent = accentColorMap[Provider.of(context, listen: false).accentColor] ?? Color(0); + return ThemeData( + brightness: Brightness.light, + fontFamily: _fontFamily, + scaffoldBackgroundColor: lightColors.background, + backgroundColor: lightColors.highlight, + primaryColor: lightColors.filc, + dividerColor: Color(0), + colorScheme: ColorScheme.fromSwatch( + accentColor: accent, + backgroundColor: lightColors.background, + brightness: Brightness.light, + cardColor: lightColors.highlight, + errorColor: lightColors.red, + primaryColorDark: lightColors.filc, + primarySwatch: Colors.teal, + ), + shadowColor: lightColors.shadow, + appBarTheme: AppBarTheme(backgroundColor: lightColors.background), + indicatorColor: accent, + iconTheme: IconThemeData(color: lightColors.text.withOpacity(.75))); + } + + // Dark Theme + static ThemeData darkTheme(BuildContext context) { + var darkColors = DarkAppColors(); + Color accent = accentColorMap[Provider.of(context, listen: false).accentColor] ?? Color(0); + return ThemeData( + brightness: Brightness.dark, + fontFamily: _fontFamily, + scaffoldBackgroundColor: darkColors.background, + backgroundColor: darkColors.highlight, + primaryColor: darkColors.filc, + dividerColor: Color(0), + colorScheme: ColorScheme.fromSwatch( + accentColor: accent, + backgroundColor: darkColors.background, + brightness: Brightness.dark, + cardColor: darkColors.highlight, + errorColor: darkColors.red, + primaryColorDark: darkColors.filc, + primarySwatch: Colors.teal, + ), + shadowColor: darkColors.shadow, + appBarTheme: AppBarTheme(backgroundColor: darkColors.background), + indicatorColor: accent, + iconTheme: IconThemeData(color: darkColors.text.withOpacity(.75))); + } +} + +class AppColors { + static ThemeAppColors of(BuildContext context) { + return Theme.of(context).brightness == Brightness.light ? LightAppColors() : DarkAppColors(); + } +} + +enum AccentColor { filc, blue, green, lime, yellow, orange, red, pink, purple } + +Map accentColorMap = { + AccentColor.filc: Color(0xff20AC9B), + AccentColor.blue: Colors.blue.shade300, + AccentColor.green: Colors.green.shade300, + AccentColor.lime: Colors.lime.shade300, + AccentColor.yellow: Colors.yellow.shade300, + AccentColor.orange: Colors.deepOrange.shade300, + AccentColor.red: Colors.red.shade300, + AccentColor.pink: Colors.pink.shade300, + AccentColor.purple: Colors.purple.shade300, +}; + +abstract class ThemeAppColors { + final Color shadow = Color(0); + final Color text = Color(0); + final Color background = Color(0); + final Color highlight = Color(0); + final Color red = Color(0); + final Color orange = Color(0); + final Color yellow = Color(0); + final Color green = Color(0); + final Color filc = Color(0); + final Color teal = Color(0); + final Color blue = Color(0); + final Color indigo = Color(0); + final Color purple = Color(0); + final Color pink = Color(0); +} + +class LightAppColors implements ThemeAppColors { + final shadow = Color(0xffE8E8E8); + final text = Colors.black; + final background = Color(0xffF4F9FF); + final highlight = Color(0xffFFFFFF); + final red = Color(0xffFF3B30); + final orange = Color(0xffFF9500); + final yellow = Color(0xffFFCC00); + final green = Color(0xff34C759); + final filc = Color(0xff247665); + final teal = Color(0xff5AC8FA); + final blue = Color(0xff007AFF); + final indigo = Color(0xff5856D6); + final purple = Color(0xffAF52DE); + final pink = Color(0xffFF2D55); +} + +class DarkAppColors implements ThemeAppColors { + final shadow = Color(0); + final text = Colors.white; + final background = Color(0xff000000); + final highlight = Color(0xff141516); + final red = Color(0xffFF453A); + final orange = Color(0xffFF9F0A); + final yellow = Color(0xffFFD60A); + final green = Color(0xff32D74B); + final filc = Color(0xff29826F); + final teal = Color(0xff64D2FF); + final blue = Color(0xff0A84FF); + final indigo = Color(0xff5E5CE6); + final purple = Color(0xffBF5AF2); + final pink = Color(0xffFF375F); +} + +class ThemeModeObserver extends ChangeNotifier { + ThemeMode _themeMode; + ThemeMode get themeMode => _themeMode; + + ThemeModeObserver({ThemeMode initialTheme = ThemeMode.system}) : _themeMode = initialTheme; + + void changeTheme(ThemeMode mode) { + _themeMode = mode; + notifyListeners(); + } +} diff --git a/filcnaplo/lib/utils/color.dart b/filcnaplo/lib/utils/color.dart new file mode 100644 index 0000000..40ce045 --- /dev/null +++ b/filcnaplo/lib/utils/color.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; + +class ColorUtils { + static Color stringToColor(String str) { + int hash = 0; + for (var i = 0; i < str.length; i++) { + hash = str.codeUnitAt(i) + ((hash << 5) - hash); + } + + return HSLColor.fromAHSL(1, hash % 360, .8, .75).toColor(); + } + + static Color foregroundColor(Color color) => color.computeLuminance() >= .5 ? Colors.black : Colors.white; +} diff --git a/filcnaplo/lib/utils/format.dart b/filcnaplo/lib/utils/format.dart new file mode 100644 index 0000000..4c7f576 --- /dev/null +++ b/filcnaplo/lib/utils/format.dart @@ -0,0 +1,55 @@ +import 'package:flutter/widgets.dart'; +import 'package:intl/intl.dart'; +import 'package:i18n_extension/i18n_widget.dart'; +import 'package:html/parser.dart'; + +extension StringFormatUtils on String { + String specialChars() => this + .replaceAll("é", "e") + .replaceAll("á", "a") + .replaceAll("ó", "o") + .replaceAll("ő", "o") + .replaceAll("ö", "o") + .replaceAll("ú", "u") + .replaceAll("ű", "u") + .replaceAll("ü", "u") + .replaceAll("í", "i"); + + String capital() => this.length > 0 ? this[0].toUpperCase() + this.substring(1) : ""; + + String capitalize() => this.split(" ").map((w) => this.capital()).join(" "); + + String escapeHtml() { + String htmlString = this; + htmlString = htmlString.replaceAll("\r", ""); + htmlString = htmlString.replaceAll(RegExp(r'
'), "\n"); + htmlString = htmlString.replaceAll(RegExp(r'

'), ""); + htmlString = htmlString.replaceAll(RegExp(r'

'), "\n"); + var document = parse(htmlString); + return document.body?.text.trim() ?? ""; + } +} + +extension DateFormatUtils on DateTime { + String format(BuildContext context, {bool timeOnly = false, bool weekday = false}) { + // Time only + if (timeOnly) return DateFormat("HH:mm").format(this); + + DateTime now = DateTime.now(); + if (this.difference(now).inDays == 0) { + if (this.hour == 0 && this.minute == 0 && this.second == 0) return "Today"; // TODO: i18n + return DateFormat("HH:mm").format(this); + } + if (this.difference(now).inDays == 1) return "Yesterday"; // TODO: i18n + + String formatString; + if (this.year == now.year) + formatString = "MMM dd."; + else + formatString = "yy/MM/dd"; + + if (weekday) formatString += " (EEEE)"; + + return DateFormat(formatString, I18n.of(context).locale.toString()).format(this); + } +} diff --git a/filcnaplo/lib/utils/jwt.dart b/filcnaplo/lib/utils/jwt.dart new file mode 100644 index 0000000..3540f66 --- /dev/null +++ b/filcnaplo/lib/utils/jwt.dart @@ -0,0 +1,18 @@ +import 'dart:convert'; + +class JwtUtils { + static String? getNameFromJWT(String jwt) { + var parts = jwt.split("."); + if (parts.length != 3) return null; + + if (parts[1].length % 4 == 2) { + parts[1] += "=="; + } else if (parts[1].length % 4 == 3) { + parts[1] += "="; + } + + var payload = utf8.decode(base64Url.decode(parts[1])); + var jwtData = jsonDecode(payload); + return jwtData["name"]; + } +} diff --git a/filcnaplo/pubspec.yaml b/filcnaplo/pubspec.yaml new file mode 100644 index 0000000..012f901 --- /dev/null +++ b/filcnaplo/pubspec.yaml @@ -0,0 +1,119 @@ +name: filcnaplo +description: "Nem hivatalos e-napló alkalmazás az e-Kréta rendszerhez" +homepage: https://filcnaplo.hu +publish_to: "none" + +version: 3.0.0-beta.1+120 + +environment: + sdk: ">=2.12.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + cupertino_icons: ^1.0.2 + + # Local filcnaplo packages + filcnaplo_mobile_ui: + path: "../filcnaplo_mobile_ui/" + filcnaplo_desktop_ui: + path: "../filcnaplo_desktop_ui/" + filcnaplo_kreta_api: + path: "../filcnaplo_kreta_api/" + + flutter_localizations: + sdk: flutter + i18n_extension: ^4.1.0 + sqflite: ^2.0.0+3 + intl: ^0.17.0 + provider: ^5.0.0 + http: ^0.13.3 + uuid: ^3.0.4 + html: ^0.15.0 + open_file: ^3.2.1 + path_provider: ^2.0.2 + permission_handler: ^8.1.4+2 + share_plus: ^2.1.4 + package_info_plus: ^1.0.6 + +dev_dependencies: + flutter_test: + sdk: flutter + # flutter_launcher_icons: ^0.9.0 + # flutter_native_splash: ^1.2.0 + +flutter: + uses-material-design: true + + assets: + - assets/icons/ic_launcher.png + - assets/icons/ic_splash.png + + fonts: + - family: FilcIcons + fonts: + - asset: assets/fonts/FilcIcons.ttf + - family: Montserrat + fonts: + - asset: assets/fonts/Montserrat/Montserrat-Black.ttf + weight: 900 + - asset: assets/fonts/Montserrat/Montserrat-BlackItalic.ttf + weight: 900 + style: italic + - asset: assets/fonts/Montserrat/Montserrat-ExtraBold.ttf + weight: 800 + - asset: assets/fonts/Montserrat/Montserrat-ExtraBoldItalic.ttf + weight: 800 + style: italic + - asset: assets/fonts/Montserrat/Montserrat-Bold.ttf + weight: 700 + - asset: assets/fonts/Montserrat/Montserrat-BoldItalic.ttf + weight: 700 + style: italic + - asset: assets/fonts/Montserrat/Montserrat-SemiBold.ttf + weight: 600 + - asset: assets/fonts/Montserrat/Montserrat-SemiBoldItalic.ttf + weight: 600 + style: italic + - asset: assets/fonts/Montserrat/Montserrat-Medium.ttf + weight: 500 + - asset: assets/fonts/Montserrat/Montserrat-MediumItalic.ttf + weight: 500 + style: italic + - asset: assets/fonts/Montserrat/Montserrat-Regular.ttf + weight: 400 + - asset: assets/fonts/Montserrat/Montserrat-Italic.ttf + weight: 400 + style: italic + - asset: assets/fonts/Montserrat/Montserrat-Light.ttf + weight: 300 + - asset: assets/fonts/Montserrat/Montserrat-LightItalic.ttf + weight: 300 + style: italic + - asset: assets/fonts/Montserrat/Montserrat-ExtraLight.ttf + weight: 200 + - asset: assets/fonts/Montserrat/Montserrat-ExtraLightItalic.ttf + weight: 200 + style: italic + - asset: assets/fonts/Montserrat/Montserrat-Thin.ttf + weight: 100 + - asset: assets/fonts/Montserrat/Montserrat-ThinItalic.ttf + weight: 100 + style: italic + +flutter_icons: + image_path: "assets/icons/ic_launcher.png" + adaptive_icon_background: "#1F5B50" + adaptive_icon_foreground: "assets/icons/ic_launcher_foreground.png" + android: true + ios: true + remove_alpha_ios: true + +flutter_native_splash: + color: "#1F5B50" + image: "assets/icons/ic_splash.png" + android: true + android_gravity: center + ios: true + ios_content_mode: center + web: false