diff --git a/.DS_Store b/.DS_Store index 64f782f8..28be10cc 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/Frontend/.metadata b/Frontend/.metadata index 68b331fb..369b5ece 100644 --- a/Frontend/.metadata +++ b/Frontend/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "761747bfc538b5af34aa0d3fac380f1bc331ec49" + revision: "8defaa71a77c16e8547abdbfad2053ce3a6e2d5b" channel: "stable" project_type: app @@ -13,11 +13,26 @@ project_type: app migration: platforms: - platform: root - create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 - base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + create_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b + base_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b + - platform: android + create_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b + base_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b + - platform: ios + create_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b + base_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b + - platform: linux + create_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b + base_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b + - platform: macos + create_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b + base_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b - platform: web - create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 - base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + create_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b + base_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b + - platform: windows + create_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b + base_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b # User provided section diff --git a/Frontend/android/.gitignore b/Frontend/android/.gitignore index 6f568019..be3943c9 100644 --- a/Frontend/android/.gitignore +++ b/Frontend/android/.gitignore @@ -5,9 +5,10 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java +.cxx/ # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/Frontend/android/app/agconnect-services.json b/Frontend/android/app/agconnect-services.json deleted file mode 100644 index a28bd41a..00000000 --- a/Frontend/android/app/agconnect-services.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "agcgw_all":{ - "CN":"connect-drcn.dbankcloud.cn", - "CN_back":"connect-drcn.hispace.hicloud.com", - "DE":"connect-dre.dbankcloud.cn", - "DE_back":"connect-dre.hispace.hicloud.com", - "RU":"connect-drru.hispace.dbankcloud.ru", - "RU_back":"connect-drru.hispace.dbankcloud.cn", - "SG":"connect-dra.dbankcloud.cn", - "SG_back":"connect-dra.hispace.hicloud.com" - }, - "websocketgw_all":{ - "CN":"connect-ws-drcn.hispace.dbankcloud.cn", - "CN_back":"connect-ws-drcn.hispace.dbankcloud.com", - "DE":"connect-ws-dre.hispace.dbankcloud.cn", - "DE_back":"connect-ws-dre.hispace.dbankcloud.com", - "RU":"connect-ws-drru.hispace.dbankcloud.ru", - "RU_back":"connect-ws-drru.hispace.dbankcloud.cn", - "SG":"connect-ws-dra.hispace.dbankcloud.cn", - "SG_back":"connect-ws-dra.hispace.dbankcloud.com" - }, - "client":{ - "cp_id":"30027000028342692", - "product_id":"461323198429473763", - "client_id":"1605107650066918016", - "client_secret":"F0A7DCFC40BBE823749E9CDAFA95BC7EF6CF37AE4CCC47F20E3434521625E1E1", - "project_id":"461323198429473763", - "app_id":"113315335", - "api_key":"DQEDAFJrB8PGLd+BmY2xafYqt6NgGE+W3ShbW2/b8xNkMTX1Elf1u7NLU73LkMl676mVHeQvLkwluYHi0FEJXEzfXtLR6P6jmW5q5A==", - "package_name":"za.co.mzansiinnovationhub.mih" - }, - "oauth_client":{ - "client_id":"113315335", - "client_type":1 - }, - "app_info":{ - "app_id":"113315335", - "package_name":"za.co.mzansiinnovationhub.mih" - }, - "configuration_version":"3.0", - "appInfos":[ - { - "package_name":"za.co.mzansiinnovationhub.mih", - "client":{ - "app_id":"113315335" - }, - "app_info":{ - "package_name":"za.co.mzansiinnovationhub.mih", - "app_id":"113315335" - }, - "oauth_client":{ - "client_type":1, - "client_id":"113315335" - } - } - ] -} \ No newline at end of file diff --git a/Frontend/android/app/build.gradle b/Frontend/android/app/build.gradle deleted file mode 100644 index 4c4dd361..00000000 --- a/Frontend/android/app/build.gradle +++ /dev/null @@ -1,103 +0,0 @@ -plugins { - id "com.android.application" - id "kotlin-android" - id "dev.flutter.flutter-gradle-plugin" -} - -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -def keystoreProperties = new Properties() -def keystorePropertiesFile = rootProject.file('key.properties') -if (keystorePropertiesFile.exists()) { - keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) -} - -android { - namespace "za.co.mzansiinnovationhub.mih" - compileSdkVersion flutter.compileSdkVersion - - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } - - kotlinOptions { - jvmTarget = '17' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "za.co.mzansiinnovationhub.mih" - // You can update the following values to match your application needs. - // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdkVersion 24 //flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - multiDexEnabled true - } - - signingConfigs { - release { - keyAlias = keystoreProperties['keyAlias'] - keyPassword = keystoreProperties['keyPassword'] - storeFile = keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null - storePassword = keystoreProperties['storePassword'] - } - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - // signingConfig signingConfigs.debug - signingConfig = signingConfigs.release - } - } -} - -flutter { - source '../..' -} - -dependencies { - // implementation("com.google.android.material:material:") - implementation "com.google.android.material:material:1.12.0" - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - implementation(platform("org.jetbrains.kotlin:kotlin-bom:$kotlin_version")) - // implementation 'com.google.android.gms:play-services-ads:23.1.0' - // implementation 'com.huawei.agconnect:agconnect-core:1.5.2.300' -} - -// apply plugin: 'com.huawei.agconnect' -configurations.all { - resolutionStrategy { - eachDependency { details -> - if (details.requested.group == 'org.jetbrains.kotlin') { - if (!details.requested.version.startsWith(rootProject.ext.kotlin_version)) { - details.useVersion rootProject.ext.kotlin_version - } - } - } - } -} diff --git a/Frontend/android/app/build.gradle.kts b/Frontend/android/app/build.gradle.kts new file mode 100644 index 00000000..57b57e45 --- /dev/null +++ b/Frontend/android/app/build.gradle.kts @@ -0,0 +1,46 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "za.co.mzansiinnovationhub.mih" + compileSdk = 35 + ndkVersion = "27.0.12077973" + // ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "za.co.mzansiinnovationhub.mih" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = 23 + //minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/Frontend/android/app/src/main/AndroidManifest.xml b/Frontend/android/app/src/main/AndroidManifest.xml index 18359636..41e80cd2 100644 --- a/Frontend/android/app/src/main/AndroidManifest.xml +++ b/Frontend/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ + xmlns:tools="http://schemas.android.com/tools"> @@ -15,18 +15,16 @@ - - + android:icon="@mipmap/launcher_icon"> - - - - + + In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. --> - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/Frontend/android/app/src/main/kotlin/za/co/mzansiinnovationhub/mih/MainActivity.kt b/Frontend/android/app/src/main/kotlin/za/co/mzansiinnovationhub/mih/MainActivity.kt index 3ba56267..7a5defbe 100644 --- a/Frontend/android/app/src/main/kotlin/za/co/mzansiinnovationhub/mih/MainActivity.kt +++ b/Frontend/android/app/src/main/kotlin/za/co/mzansiinnovationhub/mih/MainActivity.kt @@ -1,6 +1,5 @@ package za.co.mzansiinnovationhub.mih -import io.flutter.embedding.android.FlutterFragmentActivity +import io.flutter.embedding.android.FlutterActivity -class MainActivity: FlutterFragmentActivity() { -} \ No newline at end of file +class MainActivity : FlutterActivity() diff --git a/Frontend/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/Frontend/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png index d321b2a4..149b5936 100644 Binary files a/Frontend/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png and b/Frontend/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png differ diff --git a/Frontend/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png b/Frontend/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png index 01b654d2..8fd116f5 100644 Binary files a/Frontend/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png and b/Frontend/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png differ diff --git a/Frontend/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/Frontend/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png index a487b882..0f9afb3e 100644 Binary files a/Frontend/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png and b/Frontend/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png differ diff --git a/Frontend/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png b/Frontend/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png index 1ad75131..34525e34 100644 Binary files a/Frontend/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png and b/Frontend/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png differ diff --git a/Frontend/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png b/Frontend/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png index e332d68d..ff60f903 100644 Binary files a/Frontend/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png and b/Frontend/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png differ diff --git a/Frontend/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml b/Frontend/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml index 5f349f7f..c79c58a3 100644 --- a/Frontend/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml +++ b/Frontend/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml @@ -1,5 +1,9 @@ - + + + diff --git a/Frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/Frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index c2f08f7d..db77bb4b 100644 Binary files a/Frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/Frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/Frontend/android/app/src/main/res/mipmap-hdpi/launcher_icon.png b/Frontend/android/app/src/main/res/mipmap-hdpi/launcher_icon.png index 4c423dc4..9eaccf4d 100644 Binary files a/Frontend/android/app/src/main/res/mipmap-hdpi/launcher_icon.png and b/Frontend/android/app/src/main/res/mipmap-hdpi/launcher_icon.png differ diff --git a/Frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/Frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index fe78952e..17987b79 100644 Binary files a/Frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/Frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/Frontend/android/app/src/main/res/mipmap-mdpi/launcher_icon.png b/Frontend/android/app/src/main/res/mipmap-mdpi/launcher_icon.png index 69aa520f..3af91ff0 100644 Binary files a/Frontend/android/app/src/main/res/mipmap-mdpi/launcher_icon.png and b/Frontend/android/app/src/main/res/mipmap-mdpi/launcher_icon.png differ diff --git a/Frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/Frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 95701a1f..09d43914 100644 Binary files a/Frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/Frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/Frontend/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png b/Frontend/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png index c960415f..1e178c8e 100644 Binary files a/Frontend/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png and b/Frontend/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png differ diff --git a/Frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/Frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 041967ab..d5f1c8d3 100644 Binary files a/Frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/Frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/Frontend/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png b/Frontend/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png index 8cd9b3e8..03e17e9f 100644 Binary files a/Frontend/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png and b/Frontend/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png differ diff --git a/Frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/Frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 661e122c..4d6372ee 100644 Binary files a/Frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/Frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/Frontend/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png b/Frontend/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png index 9dcc20a3..2168f904 100644 Binary files a/Frontend/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png and b/Frontend/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png differ diff --git a/Frontend/android/build.gradle b/Frontend/android/build.gradle deleted file mode 100644 index 0e62ca7d..00000000 --- a/Frontend/android/build.gradle +++ /dev/null @@ -1,34 +0,0 @@ -buildscript { - ext.kotlin_version = '2.1.21' - repositories { - google() - mavenCentral() - // maven { url 'https://developer.huawei.com/repo/' } - } - - dependencies { - classpath "com.android.tools.build:gradle:8.8.1" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - // classpath 'com.huawei.agconnect:agcp:1.5.2.300' - } -} - -allprojects { - repositories { - google() - mavenCentral() - // maven {url 'https://developer.huawei.com/repo/'} - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -//subprojects { -// project.evaluationDependsOn(':app') -//} - -tasks.register("clean", Delete) { - delete rootProject.buildDir -} diff --git a/Frontend/android/build.gradle.kts b/Frontend/android/build.gradle.kts new file mode 100644 index 00000000..89176ef4 --- /dev/null +++ b/Frontend/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/Frontend/android/gradle.properties b/Frontend/android/gradle.properties index 73c3168e..f018a618 100644 --- a/Frontend/android/gradle.properties +++ b/Frontend/android/gradle.properties @@ -1,6 +1,3 @@ -org.gradle.jvmargs=-Xmx4G +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true -android.defaults.buildfeatures.buildconfig=true -android.nonTransitiveRClass=false -android.nonFinalResIds=false diff --git a/Frontend/android/gradle/wrapper/gradle-wrapper.properties b/Frontend/android/gradle/wrapper/gradle-wrapper.properties index 79eb9d00..ac3b4792 100644 --- a/Frontend/android/gradle/wrapper/gradle-wrapper.properties +++ b/Frontend/android/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip -networkTimeout=10000 -validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip diff --git a/Frontend/android/settings.gradle b/Frontend/android/settings.gradle deleted file mode 100644 index b97b87ee..00000000 --- a/Frontend/android/settings.gradle +++ /dev/null @@ -1,30 +0,0 @@ -pluginManagement { - def flutterSdkPath = { - def properties = new Properties() - file("local.properties").withInputStream { properties.load(it) } - def flutterSdkPath = properties.getProperty("flutter.sdk") - assert flutterSdkPath != null, "flutter.sdk not set in local.properties" - return flutterSdkPath - } - settings.ext.flutterSdkPath = flutterSdkPath() - - includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") - - repositories { - google() - mavenCentral() - gradlePluginPortal() - } - - plugins { - id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false - id "org.jetbrains.kotlin.android" version "2.1.21" apply false - } -} - -plugins { - id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version '8.8.1' apply false -} - -include ":app" diff --git a/Frontend/android/settings.gradle.kts b/Frontend/android/settings.gradle.kts new file mode 100644 index 00000000..ab39a10a --- /dev/null +++ b/Frontend/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.3" apply false + id("org.jetbrains.kotlin.android") version "2.1.0" apply false +} + +include(":app") diff --git a/Frontend/flutter_launcher_icons.yaml b/Frontend/flutter_launcher_icons.yaml index c52c4024..ea62ac8f 100644 --- a/Frontend/flutter_launcher_icons.yaml +++ b/Frontend/flutter_launcher_icons.yaml @@ -1,16 +1,16 @@ # flutter pub run flutter_launcher_icons flutter_launcher_icons: - image_path: "images/app_icon/App_Icon_3.png" + image_path: "lib/mih_components/mih_package_components/assets/images/app_icon/mih_app_icon.png" android: "launcher_icon" # image_path_android: "assets/icon/icon.png" min_sdk_android: 21 # android min sdk min:16, default 21 adaptive_icon_background: "#3A4454" - adaptive_icon_foreground: "images/app_icon/App_Icon_3.png" + adaptive_icon_foreground: "lib/mih_components/mih_package_components/assets/images/app_icon/mih_app_icon.png" # adaptive_icon_monochrome: "assets/icon/monochrome.png" ios: true - image_path_ios: "images/app_icon/mih_app_icon.png" + image_path_ios: "lib/mih_components/mih_package_components/assets/images/app_icon/mih_app_icon.png" remove_alpha_channel_ios: true # image_path_ios_dark_transparent: "assets/icon/icon_dark.png" # image_path_ios_tinted_grayscale: "assets/icon/icon_tinted.png" @@ -18,15 +18,15 @@ flutter_launcher_icons: web: generate: true - image_path: "images/app_icon/circle_logo.png" + image_path: "lib/mih_components/mih_package_components/assets/images/app_icon/circle_logo.png" background_color: "#3A4454" theme_color: "#3A4454" windows: generate: true - image_path: "images/app_icon/circle_logo.png" + image_path: "lib/mih_components/mih_package_components/assets/images/app_icon/circle_logo.png" icon_size: 48 # min:48, max:256, default: 48 macos: generate: true - image_path: "images/app_icon/circle_logo.png" + image_path: "lib/mih_components/mih_package_components/assets/images/app_icon/circle_logo.png" diff --git a/Frontend/ios/Podfile.lock b/Frontend/ios/Podfile.lock index 53f8781d..0e15c6d8 100644 --- a/Frontend/ios/Podfile.lock +++ b/Frontend/ios/Podfile.lock @@ -37,6 +37,8 @@ PODS: - file_picker (0.0.1): - DKImagePickerController/PhotoGallery - Flutter + - file_saver (0.0.1): + - Flutter - fl_downloader (0.0.1): - Flutter - Flutter (1.0.0) @@ -60,6 +62,8 @@ PODS: - mobile_scanner (7.0.0): - Flutter - FlutterMacOS + - package_info_plus (0.4.5): + - Flutter - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS @@ -91,6 +95,7 @@ DEPENDENCIES: - app_settings (from `.symlinks/plugins/app_settings/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) + - file_saver (from `.symlinks/plugins/file_saver/ios`) - fl_downloader (from `.symlinks/plugins/fl_downloader/ios`) - Flutter (from `Flutter`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) @@ -99,6 +104,7 @@ DEPENDENCIES: - google_mobile_ads (from `.symlinks/plugins/google_mobile_ads/ios`) - local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`) - mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`) + - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - printing (from `.symlinks/plugins/printing/ios`) - screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`) @@ -125,6 +131,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/device_info_plus/ios" file_picker: :path: ".symlinks/plugins/file_picker/ios" + file_saver: + :path: ".symlinks/plugins/file_saver/ios" fl_downloader: :path: ".symlinks/plugins/fl_downloader/ios" Flutter: @@ -141,6 +149,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/local_auth_darwin/darwin" mobile_scanner: :path: ".symlinks/plugins/mobile_scanner/darwin" + package_info_plus: + :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" printing: @@ -166,6 +176,7 @@ SPEC CHECKSUMS: DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be + file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6 fl_downloader: dc99aa8dd303f862cccb830087f37acc9b0156ee Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf @@ -176,6 +187,7 @@ SPEC CHECKSUMS: GoogleUserMessagingPlatform: f8d0cdad3ca835406755d0a69aa634f00e76d576 local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391 mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 printing: 54ff03f28fe9ba3aa93358afb80a8595a071dd07 screen_brightness_ios: 9953fd7da5bd480f1a93990daeec2eb42d4f3b52 diff --git a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index 2ba4b695..550c1375 100644 Binary files a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 992fcd53..5bd61bd4 100644 Binary files a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 2065fd9b..0ea48825 100644 Binary files a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index 6961bd50..2d4cbe47 100644 Binary files a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 8cf59ee9..d0cf82cb 100644 Binary files a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index e070e432..9f709813 100644 Binary files a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index 062fde51..bebc5023 100644 Binary files a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 2065fd9b..0ea48825 100644 Binary files a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index c9ea1730..febb81c9 100644 Binary files a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index 47150dbd..fbc70a4f 100644 Binary files a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png index 26dcef9b..e58bbcc8 100644 Binary files a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png and b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ diff --git a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png index fbcbff5d..9f1992fb 100644 Binary files a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png and b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ diff --git a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png index 3067a572..678e8057 100644 Binary files a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png and b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png index 299eebc6..b3028465 100644 Binary files a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png and b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index 47150dbd..fbc70a4f 100644 Binary files a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index df9094b9..ddd18d00 100644 Binary files a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png index bf3a4727..9eaccf4d 100644 Binary files a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png and b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png index d5b01f24..03e17e9f 100644 Binary files a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png and b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index 9af533bc..993b89b4 100644 Binary files a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 511913fd..d916fb3b 100644 Binary files a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index 48f03b18..92c735a6 100644 Binary files a/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/Frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/Frontend/lib/main.dart b/Frontend/lib/main.dart index 483df2b2..4bdbd191 100644 --- a/Frontend/lib/main.dart +++ b/Frontend/lib/main.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:mzansi_innovation_hub/mih_config/mih_colors.dart'; +import 'package:upgrader/upgrader.dart'; import 'mih_config/mih_env.dart'; import 'mih_config/mih_theme.dart'; @@ -72,6 +73,12 @@ class _MzansiInnovationHubState extends State { darkTheme: theme.getThemeData(), debugShowCheckedModeBanner: false, routerConfig: widget.router, + builder: (context, child) { + return UpgradeAlert( + navigatorKey: widget.router.routerDelegate.navigatorKey, + child: child ?? const Text('Upgrade Alert'), + ); + }, ); } -} \ No newline at end of file +} diff --git a/Frontend/lib/mih_components/mih_objects/user_consent.dart b/Frontend/lib/mih_components/mih_objects/user_consent.dart new file mode 100644 index 00000000..f62ebb06 --- /dev/null +++ b/Frontend/lib/mih_components/mih_objects/user_consent.dart @@ -0,0 +1,29 @@ +class UserConsent { + String app_id; + DateTime privacy_policy_accepted; + DateTime terms_of_services_accepted; + + UserConsent({ + required this.app_id, + required this.privacy_policy_accepted, + required this.terms_of_services_accepted, + }); + + factory UserConsent.fromJson(Map json) { + return UserConsent( + app_id: json['app_id'], + privacy_policy_accepted: DateTime.parse(json['privacy_policy_accepted']), + terms_of_services_accepted: + DateTime.parse(json['terms_of_services_accepted']), + ); + } + + Map toJson() { + return { + 'app_id': app_id, + 'privacy_policy_accepted': privacy_policy_accepted.toIso8601String(), + 'terms_of_services_accepted': + terms_of_services_accepted.toIso8601String(), + }; + } +} diff --git a/Frontend/lib/mih_components/mih_package_components/mih_circle_avatar.dart b/Frontend/lib/mih_components/mih_package_components/mih_circle_avatar.dart index 5001a343..4f0c3e71 100644 --- a/Frontend/lib/mih_components/mih_package_components/mih_circle_avatar.dart +++ b/Frontend/lib/mih_components/mih_package_components/mih_circle_avatar.dart @@ -73,6 +73,7 @@ class _MihCircleAvatarState extends State { Visibility( visible: imagePreview != null, child: Positioned( + right: widget.width * 0.03, child: CircleAvatar( radius: widget.width / 2.2, backgroundColor: widget.backgroundColor, diff --git a/Frontend/lib/mih_components/mih_package_components/mih_package_alert.dart b/Frontend/lib/mih_components/mih_package_components/mih_package_alert.dart index 29d94682..ce87d21f 100644 --- a/Frontend/lib/mih_components/mih_package_components/mih_package_alert.dart +++ b/Frontend/lib/mih_components/mih_package_components/mih_package_alert.dart @@ -1,3 +1,4 @@ +import 'package:go_router/go_router.dart'; import 'package:mzansi_innovation_hub/main.dart'; import 'package:flutter/material.dart'; import 'package:mzansi_innovation_hub/mih_config/mih_colors.dart'; @@ -99,7 +100,7 @@ class _MihPackageAlertState extends State { height: 50, child: IconButton( onPressed: () { - Navigator.pop(context); + context.pop(); }, icon: Icon( Icons.close, diff --git a/Frontend/lib/mih_components/mih_package_components/mih_toggle.dart b/Frontend/lib/mih_components/mih_package_components/mih_toggle.dart index ecab7a05..2ddc7c8b 100644 --- a/Frontend/lib/mih_components/mih_package_components/mih_toggle.dart +++ b/Frontend/lib/mih_components/mih_package_components/mih_toggle.dart @@ -8,6 +8,7 @@ class MihToggle extends StatefulWidget { final Color fillColor; final Color secondaryFillColor; final bool? readOnly; + final double? elevation; final void Function(bool) onChange; const MihToggle({ super.key, @@ -16,6 +17,7 @@ class MihToggle extends StatefulWidget { required this.fillColor, required this.secondaryFillColor, this.readOnly, + this.elevation, required this.onChange, }); @@ -59,49 +61,73 @@ class _MihToggleState extends State { ), ), const SizedBox(width: 10), - Switch( - value: togglePosition, - trackOutlineColor: WidgetStateProperty.resolveWith( - (states) { - if (widget.readOnly == true) { - return Colors.grey; - } - if (states.contains(WidgetState.selected)) { - return MihColors.getGreenColor( + // Material( + // elevation: widget.elevation ?? 0.01, + // shadowColor: widget.secondaryFillColor.withOpacity(0.5), + // color: Colors.transparent, + // shape: StadiumBorder(), + Container( + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular( + 30), // Adjust the border radius to match the toggle + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.15), + offset: Offset( + 0, widget.elevation ?? 10), // Adjust the vertical offset + blurRadius: widget.elevation ?? 10, + spreadRadius: 0, + ), + ], + ), + child: Switch( + value: togglePosition, + trackOutlineColor: WidgetStateProperty.resolveWith( + (states) { + if (widget.readOnly == true) { + return Colors.grey; + } + if (states.contains(WidgetState.selected)) { + return MihColors.getGreenColor( + MzansiInnovationHub.of(context)!.theme.mode == + "Dark"); // Outline color when active + } + return MihColors.getRedColor( MzansiInnovationHub.of(context)!.theme.mode == "Dark"); // Outline color when active - } - return MihColors.getRedColor( - MzansiInnovationHub.of(context)!.theme.mode == - "Dark"); // Outline color when active - }, + }, + ), + activeColor: widget.readOnly == true + ? Colors.grey + : widget.secondaryFillColor, + activeTrackColor: widget.readOnly == true + ? Colors.grey.shade400 + : MihColors.getGreenColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + inactiveThumbColor: widget.readOnly == true + ? Colors.grey + : widget.secondaryFillColor, + inactiveTrackColor: widget.readOnly == true + ? Colors.grey.shade400 + : MihColors.getRedColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + // activeColor: widget.secondaryFillColor, + // activeTrackColor: widget.fillColor, + // inactiveThumbColor: widget.fillColor, + // inactiveTrackColor: widget.secondaryFillColor, + // onChanged: widget.readOnly != true ? widget.onChange : null, + onChanged: widget.readOnly != true + ? (newValue) { + setState(() { + togglePosition = newValue; // Update internal state + }); + widget.onChange(newValue); // Call the parent's onChange + } + : null, ), - activeColor: - widget.readOnly == true ? Colors.grey : widget.secondaryFillColor, - activeTrackColor: widget.readOnly == true - ? Colors.grey.shade400 - : MihColors.getGreenColor( - MzansiInnovationHub.of(context)!.theme.mode == "Dark"), - inactiveThumbColor: - widget.readOnly == true ? Colors.grey : widget.secondaryFillColor, - inactiveTrackColor: widget.readOnly == true - ? Colors.grey.shade400 - : MihColors.getRedColor( - MzansiInnovationHub.of(context)!.theme.mode == "Dark"), - // activeColor: widget.secondaryFillColor, - // activeTrackColor: widget.fillColor, - // inactiveThumbColor: widget.fillColor, - // inactiveTrackColor: widget.secondaryFillColor, - // onChanged: widget.readOnly != true ? widget.onChange : null, - onChanged: widget.readOnly != true - ? (newValue) { - setState(() { - togglePosition = newValue; // Update internal state - }); - widget.onChange(newValue); // Call the parent's onChange - } - : null, ), + const SizedBox(width: 10), ], ); } diff --git a/Frontend/lib/mih_config/mih_theme.dart b/Frontend/lib/mih_config/mih_theme.dart index e582f7cc..eed96378 100644 --- a/Frontend/lib/mih_config/mih_theme.dart +++ b/Frontend/lib/mih_config/mih_theme.dart @@ -14,7 +14,7 @@ class MihTheme { late String loadingAssetText; late TargetPlatform platform; bool kIsWeb = const bool.fromEnvironment('dart.library.js_util'); - String latestVersion = "1.2.0"; + String latestVersion = "1.2.1"; // Options:- // f3f9d2 = Cream // f0f0c9 = cream2 diff --git a/Frontend/lib/mih_packages/about_mih/mih_policy_tos_ext/mih_privacy_polocy_external.dart b/Frontend/lib/mih_packages/about_mih/mih_policy_tos_ext/mih_privacy_polocy_external.dart deleted file mode 100644 index ee912597..00000000 --- a/Frontend/lib/mih_packages/about_mih/mih_policy_tos_ext/mih_privacy_polocy_external.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:mzansi_innovation_hub/mih_components/mih_layout/mih_action.dart'; -import 'package:mzansi_innovation_hub/mih_components/mih_layout/mih_body.dart'; -import 'package:mzansi_innovation_hub/mih_components/mih_layout/mih_header.dart'; -import 'package:mzansi_innovation_hub/mih_components/mih_layout/mih_layout_builder.dart'; -import 'package:mzansi_innovation_hub/mih_components/mih_objects/arguments.dart'; -import 'package:mzansi_innovation_hub/mih_packages/about_mih/mih_policy_tos_ext/policy_and_terms_text.dart'; -import 'package:flutter/material.dart'; - -class MIHPrivacyPolocyExternal extends StatefulWidget { - const MIHPrivacyPolocyExternal({super.key}); - - @override - State createState() => - _MIHPrivacyPolocyExternalState(); -} - -class _MIHPrivacyPolocyExternalState extends State { - MIHAction getActionButton() { - return MIHAction( - icon: const Icon(Icons.arrow_back), - iconSize: 35, - onTap: () { - Navigator.of(context).pushNamedAndRemoveUntil( - '/', - arguments: AuthArguments(true, false), - (route) => false, - ); - }, - ); - } - - MIHHeader getHeader() { - return const MIHHeader( - headerAlignment: MainAxisAlignment.center, - headerItems: [], - ); - } - - MIHBody getBody() { - return MIHBody( - borderOn: false, - bodyItems: PolicyAndTermsText().getPrivacyPolicyText(context), - ); - } - - @override - Widget build(BuildContext context) { - return MIHLayoutBuilder( - actionButton: getActionButton(), - header: getHeader(), - secondaryActionButton: null, - body: getBody(), - actionDrawer: null, - secondaryActionDrawer: null, - bottomNavBar: null, - pullDownToRefresh: false, - onPullDown: () async {}, - ); - } -} diff --git a/Frontend/lib/mih_packages/about_mih/mih_policy_tos_ext/mih_terms_of_service_external.dart b/Frontend/lib/mih_packages/about_mih/mih_policy_tos_ext/mih_terms_of_service_external.dart deleted file mode 100644 index c9977330..00000000 --- a/Frontend/lib/mih_packages/about_mih/mih_policy_tos_ext/mih_terms_of_service_external.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:mzansi_innovation_hub/mih_components/mih_layout/mih_action.dart'; -import 'package:mzansi_innovation_hub/mih_components/mih_layout/mih_body.dart'; -import 'package:mzansi_innovation_hub/mih_components/mih_layout/mih_header.dart'; -import 'package:mzansi_innovation_hub/mih_components/mih_layout/mih_layout_builder.dart'; -import 'package:mzansi_innovation_hub/mih_components/mih_objects/arguments.dart'; -import 'package:mzansi_innovation_hub/mih_packages/about_mih/mih_policy_tos_ext/policy_and_terms_text.dart'; -import 'package:flutter/material.dart'; - -class MIHTermsOfServiceExternal extends StatefulWidget { - const MIHTermsOfServiceExternal({super.key}); - - @override - State createState() => - _MIHTermsOfServiceExternalState(); -} - -class _MIHTermsOfServiceExternalState extends State { - MIHAction getActionButton() { - return MIHAction( - icon: const Icon(Icons.arrow_back), - iconSize: 35, - onTap: () { - Navigator.of(context).pushNamedAndRemoveUntil( - '/', - arguments: AuthArguments(true, false), - (route) => false, - ); - }, - ); - } - - MIHHeader getHeader() { - return const MIHHeader( - headerAlignment: MainAxisAlignment.center, - headerItems: [], - ); - } - - MIHBody getBody() { - return MIHBody( - borderOn: false, - bodyItems: PolicyAndTermsText().getTermsOfServiceText(context), - ); - } - - @override - Widget build(BuildContext context) { - return MIHLayoutBuilder( - actionButton: getActionButton(), - header: getHeader(), - secondaryActionButton: null, - body: getBody(), - actionDrawer: null, - secondaryActionDrawer: null, - bottomNavBar: null, - pullDownToRefresh: false, - onPullDown: () async {}, - ); - } -} diff --git a/Frontend/lib/mih_packages/about_mih/mih_policy_tos_ext/policy_and_terms_text.dart b/Frontend/lib/mih_packages/about_mih/mih_policy_tos_ext/policy_and_terms_text.dart index 4552d9af..c01a1162 100644 --- a/Frontend/lib/mih_packages/about_mih/mih_policy_tos_ext/policy_and_terms_text.dart +++ b/Frontend/lib/mih_packages/about_mih/mih_policy_tos_ext/policy_and_terms_text.dart @@ -4,27 +4,36 @@ import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_ import 'package:mzansi_innovation_hub/mih_config/mih_colors.dart'; class PolicyAndTermsText { - List getPrivacyPolicyText(BuildContext context) { - String effectDate = "6 December 2024"; - String intro = - "Mzansi Innovation Hub - MIH (\"we,\" \"our,\" \"us\") values your privacy and is committed to protecting your personal data. This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you use our app, Mzansi Innovation Hub - MIH, available globally."; - String infoCollect = - "We collect the following personal information to provide and improve our services:\n• Personal Details: Name, ID, address, phone number etc.\n• Medical Information: Medical aid details (if applicable).\n• Loyalty Card Information: Loyalty card numbers for the Mzansi Wallet feature."; - String useInfo = - "Your personal information is used for the following purposes:\n• To create and manage your account.\n• To facilitate interactions between clients and businesses.\n• To enable the storage of loyalty card information within the Mzansi Wallet.\n• To provide technical support and improve our app's functionality."; - String dataShare = - "We only share your data under the following conditions:\n• With Your Consent: Businesses can access your information only with your explicit permission.\n• Legal Obligations: We may disclose information to comply with applicable laws or regulations."; - String dataSec = - "We implement advanced security measures to protect your personal data:\n• Data encryption during transmission.\n• Secure authentication protocols to prevent unauthorized access.\n• Regular audits to identify and address vulnerabilities."; + List getPrivacyPolicyText(BuildContext context, bool english) { + String effectDate = english ? "6 December 2024" : "2024年12月6日"; + String intro = english + ? "Mzansi Innovation Hub - MIH (\"we,\" \"our,\" \"us\") values your privacy and is committed to protecting your personal data. This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you use our app, Mzansi Innovation Hub - MIH, available globally." + : "Mzansi Innovation Hub - MIH(“我们”)重视您的隐私,并致力于保护您的个人数据。本隐私政策解释了当您使用我们面向全球推出的应用程序 Mzansi Innovation Hub - MIH 时,我们如何收集、使用、披露和保护您的信息。"; + String infoCollect = english + ? "We collect the following personal information to provide and improve our services:\n• Personal Details: Name, ID, address, phone number etc.\n• Medical Information: Medical aid details (if applicable).\n• Loyalty Card Information: Loyalty card numbers for the Mzansi Wallet feature." + : "我们收集以下个人信息以提供和改进我们的服务:\n• 个人详细信息:姓名、身份证、地址、电话号码等。\n• 医疗信息:医疗援助详情(如适用)。\n• 忠诚卡信息:Mzansi Wallet 功能的忠诚卡号码。"; + String useInfo = english + ? "Your personal information is used for the following purposes:\n• To create and manage your account.\n• To facilitate interactions between clients and businesses.\n• To enable the storage of loyalty card information within the Mzansi Wallet.\n• To provide technical support and improve our app's functionality." + : "您的个人信息用于以下目的:\n• 创建和管理您的账户。\n• 促进客户与企业之间的互动。\n• 使忠诚卡信息能够存储在 Mzansi Wallet 中。\n• 提供技术支持并改进我们应用程序的功能。"; + String dataShare = english + ? "We only share your data under the following conditions:\n• With Your Consent: Businesses can access your information only with your explicit permission.\n• Legal Obligations: We may disclose information to comply with applicable laws or regulations." + : "我们仅在以下情况下共享您的数据:\n• 经您同意:企业只能在您明确许可的情况下访问您的信息。\n• 法律义务:我们可能会披露信息以遵守适用的法律或法规。"; + String dataSec = english + ? "We implement advanced security measures to protect your personal data:\n• Data encryption during transmission.\n• Secure authentication protocols to prevent unauthorized access.\n• Regular audits to identify and address vulnerabilities." + : "我们实施先进的安全措施来保护您的个人数据:\n• 传输过程中对数据进行加密。\n• 安全的身份验证协议以防止未经授权的访问。\n• 定期审计以识别和解决漏洞。"; - String yourRights = - "You have the following rights regarding your personal data:\n• Access and Correction: View and update your information via your account settings.\n• Data Deletion: Request the deletion of your account and associated data.\n• Withdrawal of Consent: Revoke permissions for businesses to access your data is restricted once granted.\n• To exercise these rights, contact us at mzansi.innovation.hub@gmail.com."; - String dataRet = - "We retain your personal data for as long as necessary to provide our services. Upon account deletion, your data will be permanently removed unless required by law to retain certain records."; - String policyChanges = - "We may update this Privacy Policy to reflect changes in our practices or legal requirements. We will notify you of significant updates via in app notifications and/ or email."; - String contactUs = - "If you have questions or concerns about this Privacy Policy, please contact us:\n• Email: mzansi.innovation.hub@gmail.com\n• Phone: +27 655 530 195\n"; + String yourRights = english + ? "You have the following rights regarding your personal data:\n• Access and Correction: View and update your information via your account settings.\n• Data Deletion: Request the deletion of your account and associated data.\n• Withdrawal of Consent: Revoke permissions for businesses to access your data is restricted once granted.\n• To exercise these rights, contact us at mzansi.innovation.hub@gmail.com." + : "您对您的个人数据享有以下权利:\n• 访问和更正:通过您的账户设置查看和更新您的信息。\n• 数据删除:请求删除您的账户及相关数据。\n• 撤回同意:一旦授予权限,撤销企业访问您数据的权限将受到限制。\n• 要行使这些权利,请通过 mzansi.innovation.hub@gmail.com 与我们联系。"; + String dataRet = english + ? "We retain your personal data for as long as necessary to provide our services. Upon account deletion, your data will be permanently removed unless required by law to retain certain records." + : "我们会在提供服务所需的时间内保留您的个人数据。账户删除后,您的数据将被永久删除,除非法律要求保留某些记录。"; + String policyChanges = english + ? "We may update this Privacy Policy to reflect changes in our practices or legal requirements. We will notify you of significant updates via in app notifications and/ or email." + : "我们可能会更新本隐私政策以反映我们的做法或法律要求的变化。我们将通过应用内通知和/或电子邮件通知您重大更新。"; + String contactUs = english + ? "If you have questions or concerns about this Privacy Policy, please contact us:\n• Email: mzansi.innovation.hub@gmail.com\n• Phone: +27 655 530 195\n" + : "如果您对本隐私政策有任何疑问或担忧,请通过以下方式与我们联系:\n• 电子邮件: mzansi.innovation.hub@gmail.com.\n• 电话: +27 655 530 195"; return [ SizedBox( width: 165, @@ -42,8 +51,8 @@ class PolicyAndTermsText { width: 1250, child: Row( children: [ - const Text( - "Effective Date: ", + Text( + english ? "Effective Date: " : "生效日期: ", textAlign: TextAlign.center, style: TextStyle( fontWeight: FontWeight.bold, @@ -86,13 +95,13 @@ class PolicyAndTermsText { height: 10, ), //=============== 1. Information We Collect =============== - const SizedBox( + SizedBox( width: 1250, child: Row( // crossAxisAlignment: WrapCrossAlignment.start, children: [ Text( - "1. Information We Collect", + english ? "1. Information We Collect" : "1. 我们收集的信息", textAlign: TextAlign.start, softWrap: true, style: TextStyle( @@ -124,13 +133,13 @@ class PolicyAndTermsText { height: 10, ), //=============== 2. How We Use Your Information =============== - const SizedBox( + SizedBox( width: 1250, child: Row( // crossAxisAlignment: WrapCrossAlignment.start, children: [ Text( - "2. How We Use Your Information", + english ? "2. How We Use Your Information" : "2. 我们如何使用您的信息", textAlign: TextAlign.start, softWrap: true, style: TextStyle( @@ -162,13 +171,13 @@ class PolicyAndTermsText { height: 10, ), //=============== 3. Data Sharing =============== - const SizedBox( + SizedBox( width: 1250, child: Row( // crossAxisAlignment: WrapCrossAlignment.start, children: [ Text( - "3. Data Sharing", + english ? "3. Data Sharing" : "3. 数据共享", textAlign: TextAlign.start, softWrap: true, style: TextStyle( @@ -200,13 +209,13 @@ class PolicyAndTermsText { height: 10, ), //=============== 4. Data Security =============== - const SizedBox( + SizedBox( width: 1250, child: Row( // crossAxisAlignment: WrapCrossAlignment.start, children: [ Text( - "4. Data Security", + english ? "4. Data Security" : "4. 数据安全", textAlign: TextAlign.start, softWrap: true, style: TextStyle( @@ -238,13 +247,13 @@ class PolicyAndTermsText { height: 10, ), //=============== 5. Your Rights =============== - const SizedBox( + SizedBox( width: 1250, child: Row( // crossAxisAlignment: WrapCrossAlignment.start, children: [ Text( - "5. Your Rights", + english ? "5. Your Rights" : "5. 您的权利", textAlign: TextAlign.start, softWrap: true, style: TextStyle( @@ -276,13 +285,13 @@ class PolicyAndTermsText { height: 10, ), //=============== 6. Data Retention =============== - const SizedBox( + SizedBox( width: 1250, child: Row( // crossAxisAlignment: WrapCrossAlignment.start, children: [ Text( - "6. Data Retention", + english ? "6. Data Retention" : "6. 数据保留", textAlign: TextAlign.start, softWrap: true, style: TextStyle( @@ -314,13 +323,13 @@ class PolicyAndTermsText { height: 10, ), //=============== 7. Changes to This Privacy Policy =============== - const SizedBox( + SizedBox( width: 1250, child: Row( // crossAxisAlignment: WrapCrossAlignment.start, children: [ Text( - "7. Changes to This Privacy Policy", + english ? "7. Changes to This Privacy Policy" : "7. 本隐私政策的变更", textAlign: TextAlign.start, softWrap: true, style: TextStyle( @@ -352,13 +361,13 @@ class PolicyAndTermsText { height: 10, ), //=============== 8. Contact Us =============== - const SizedBox( + SizedBox( width: 1250, child: Row( // crossAxisAlignment: WrapCrossAlignment.start, children: [ Text( - "8. Contact Us", + english ? "8. Contact Us" : "8. 联系我们", textAlign: TextAlign.start, softWrap: true, style: TextStyle( @@ -392,37 +401,51 @@ class PolicyAndTermsText { ]; } - List getTermsOfServiceText(BuildContext context) { - String effectDate = "6 December 2024"; - String intro = - "Welcome to Mzansi Innovation Hub (MIH)! These Terms of Service (\"Terms\") govern your access to and use of our application and services (\"Services\"). By accessing or using Mzansi Innovation Hub (MIH), you agree to these Terms."; - String acceptTerms = - "By creating an account or using the app, you agree to be bound by these Terms. If you do not agree, please do not use the Services."; - String eligib = - "You must be at least 18 years old or the age of majority in your jurisdiction to use this app. By using the app, you represent and warrant that you meet these eligibility requirements."; - String yourResponse = - "• Account Security: You are responsible for maintaining the confidentiality of your login credentials.\n• Accurate Information: Ensure that all data you provide is accurate and up to date.\n• Prohibited Uses:\n\t\t• Do not use the app for unlawful purposes.\n\t\t• Do not engage in activities that could harm the app or its users, such as hacking, data scraping, or introducing malware."; - String ourServ = - "• We provide tools to help businesses and clients interact, including a patient manager and the Mzansi Wallet etc.\n• We do not guarantee uninterrupted or error-free services, though we strive to maintain high reliability."; + List getTermsOfServiceText(BuildContext context, bool english) { + String effectDate = english ? "6 December 2024" : "2024年12月6日"; + String intro = english + ? "Welcome to Mzansi Innovation Hub (MIH)! These Terms of Service (\"Terms\") govern your access to and use of our application and services (\"Services\"). By accessing or using Mzansi Innovation Hub (MIH), you agree to these Terms." + : "欢迎使用 Mzansi Innovation Hub (MIH)!本服务条款(“条款”)管理您对我们应用程序和服务(“服务”)的访问和使用。通过访问或使用 Mzansi Innovation Hub (MIH),您同意这些条款。"; + String acceptTerms = english + ? "By creating an account or using the app, you agree to be bound by these Terms. If you do not agree, please do not use the Services." + : "通过创建账户或使用该应用程序,您同意受这些条款的约束。如果您不同意,请不要使用该服务。"; + String eligib = english + ? "You must be at least 18 years old or the age of majority in your jurisdiction to use this app. By using the app, you represent and warrant that you meet these eligibility requirements." + : "您必须年满18岁或达到您所在司法管辖区的法定年龄才能使用此应用程序。通过使用该应用程序,您声明并保证您符合这些资格要求。"; + String yourResponse = english + ? "• Account Security: You are responsible for maintaining the confidentiality of your login credentials.\n• Accurate Information: Ensure that all data you provide is accurate and up to date.\n• Prohibited Uses:\n\t\t• Do not use the app for unlawful purposes.\n\t\t• Do not engage in activities that could harm the app or its users, such as hacking, data scraping, or introducing malware." + : "• 账户安全:您有责任维护您的登录凭据的机密性。\n• 准确信息:确保您提供的所有数据都是准确和最新的。\n• 禁止使用:\n\t\t• 不得将该应用程序用于非法目的。\n\t\t• 不得从事可能损害该应用程序或其用户的活动,例如黑客攻击、数据抓取或引入恶意软件。"; + String ourServ = english + ? "• We provide tools to help businesses and clients interact, including a patient manager and the Mzansi Wallet etc.\n• We do not guarantee uninterrupted or error-free services, though we strive to maintain high reliability." + : "• 我们提供工具来帮助企业和客户互动,包括患者管理器和 Mzansi Wallet 等。\n• 我们不保证服务不中断或无错误,但我们努力保持高可靠性。"; - String dataCol = - "Your use of the app is subject to our Privacy Policy, which explains how we collect, use, and protect your data. By using the app, you consent to these practices."; - String userContent = - "• Ownership: Any content you submit to the app (e.g., loyalty card data, client profiles) remains your property.\n• License: By using the app, you grant us a non-exclusive license to use your content solely to operate the app and provide services."; - String intelProp = - "• All rights to the app, including designs, code, and trademarks, belong to Mzansi Innovation Hub.\n• Users may not copy, distribute, or reverse-engineer any part of the app."; - String termUse = - "We reserve the right to suspend or terminate your account for violations of these Terms or if required by law."; - String disclaimerWarens = - "The app and services are provided \"as is\" without warranties of any kind, whether express or implied. We do not guarantee that the app will meet your expectations or requirements."; - String limitLiability = - "To the maximum extent permitted by law, we are not liable for:\n• Indirect, incidental, or consequential damages arising from the use or inability to use the app.\n• Loss of data, revenue, or profits."; - String modifyTerms = - "We may update these Terms periodically. Continued use of the app after changes are posted constitutes your acceptance of the new Terms."; - String governLaw = - "These Terms are governed by the laws of South Africa. Any disputes will be resolved in courts located in South Africa."; - String contactUs = - "If you have questions about these Terms, please contact us:\n• Email: mzansi.innovation.hub@gmail.com\n• Phone: +27 655 530 195\n"; + String dataCol = english + ? "Your use of the app is subject to our Privacy Policy, which explains how we collect, use, and protect your data. By using the app, you consent to these practices." + : "您对该应用程序的使用受我们的隐私政策约束,该政策解释了我们如何收集、使用和保护您的数据。通过使用该应用程序,您同意这些做法。"; + String userContent = english + ? "• Ownership: Any content you submit to the app (e.g., loyalty card data, client profiles) remains your property.\n• License: By using the app, you grant us a non-exclusive license to use your content solely to operate the app and provide services." + : "• 所有权:您提交到该应用程序的任何内容(例如,忠诚卡数据、客户档案)仍然是您的财产。\n• 许可:通过使用该应用程序,您授予我们非独占许可,仅用于运营该应用程序和提供服务。"; + String intelProp = english + ? "• All rights to the app, including designs, code, and trademarks, belong to Mzansi Innovation Hub.\n• Users may not copy, distribute, or reverse-engineer any part of the app." + : "• 该应用程序的所有权利,包括设计、代码和商标,均属于 Mzansi Innovation Hub。\n• 用户不得复制、分发或反向工程该应用程序的任何部分。"; + String termUse = english + ? "We reserve the right to suspend or terminate your account for violations of these Terms or if required by law." + : "我们保留因违反这些条款或法律要求而暂停或终止您账户的权利。"; + String disclaimerWarens = english + ? "The app and services are provided \"as is\" without warranties of any kind, whether express or implied. We do not guarantee that the app will meet your expectations or requirements." + : "该应用程序和服务按“原样”提供,不附带任何形式的明示或暗示保证。我们不保证该应用程序将满足您的期望或要求。"; + String limitLiability = english + ? "To the maximum extent permitted by law, we are not liable for:\n• Indirect, incidental, or consequential damages arising from the use or inability to use the app.\n• Loss of data, revenue, or profits." + : "在法律允许的最大范围内,我们不对以下事项承担责任:\n• 因使用或无法使用该应用程序而产生的间接、附带或后果性损害。\n• 数据、收入或利润的损失。"; + String modifyTerms = english + ? "We may update these Terms periodically. Continued use of the app after changes are posted constitutes your acceptance of the new Terms." + : "我们可能会定期更新这些条款。在更改发布后继续使用该应用程序即表示您接受新的条款。"; + String governLaw = english + ? "These Terms are governed by the laws of South Africa. Any disputes will be resolved in courts located in South Africa." + : "这些条款受南非法律管辖。任何争议将由位于南非的法院解决。"; + String contactUs = english + ? "If you have questions about these Terms, please contact us:\n• Email: mzansi.innovation.hub@gmail.com\n• Phone: +27 655 530 195\n" + : "如果您对这些条款有任何疑问,请通过以下方式与我们联系:\n• 电子邮件: mzansi.innovation.hub@gmail.com.\n• 电话: +27 655 530 195"; return [ SizedBox( width: 165, @@ -442,8 +465,8 @@ class PolicyAndTermsText { width: 1250, child: Row( children: [ - const Text( - "Effective Date: ", + Text( + english ? "Effective Date: " : "生效日期: ", textAlign: TextAlign.center, style: TextStyle( fontWeight: FontWeight.bold, @@ -486,13 +509,13 @@ class PolicyAndTermsText { height: 10, ), //=============== 1. Acceptance of Terms =============== - const SizedBox( + SizedBox( width: 1250, child: Row( // crossAxisAlignment: WrapCrossAlignment.start, children: [ Text( - "1. Acceptance of Terms", + english ? "1. Acceptance of Terms" : "1. 接受条款", textAlign: TextAlign.start, softWrap: true, style: TextStyle( @@ -524,13 +547,13 @@ class PolicyAndTermsText { height: 10, ), //=============== 2. Eligibility =============== - const SizedBox( + SizedBox( width: 1250, child: Row( // crossAxisAlignment: WrapCrossAlignment.start, children: [ Text( - "2. Eligibility", + english ? "2. Eligibility" : "2. 资格", textAlign: TextAlign.start, softWrap: true, style: TextStyle( @@ -562,13 +585,13 @@ class PolicyAndTermsText { height: 10, ), //=============== 3. Your Responsibilities =============== - const SizedBox( + SizedBox( width: 1250, child: Row( // crossAxisAlignment: WrapCrossAlignment.start, children: [ Text( - "3. Your Responsibilities", + english ? "3. Your Responsibilities" : "3. 您的责任", textAlign: TextAlign.start, softWrap: true, style: TextStyle( @@ -600,13 +623,13 @@ class PolicyAndTermsText { height: 10, ), //=============== 4. Data Security =============== - const SizedBox( + SizedBox( width: 1250, child: Row( // crossAxisAlignment: WrapCrossAlignment.start, children: [ Text( - "4. Our Services", + english ? "4. Our Services" : "4. 我们的服务", textAlign: TextAlign.start, softWrap: true, style: TextStyle( @@ -638,13 +661,13 @@ class PolicyAndTermsText { height: 10, ), //=============== 5. Data Collection and Privacy =============== - const SizedBox( + SizedBox( width: 1250, child: Row( // crossAxisAlignment: WrapCrossAlignment.start, children: [ Text( - "5. Data Collection and Privacy", + english ? "5. Data Collection and Privacy" : "5. 数据收集和隐私", textAlign: TextAlign.start, softWrap: true, style: TextStyle( @@ -676,13 +699,13 @@ class PolicyAndTermsText { height: 10, ), //=============== 6. User-Generated Content =============== - const SizedBox( + SizedBox( width: 1250, child: Row( // crossAxisAlignment: WrapCrossAlignment.start, children: [ Text( - "6. User-Generated Content", + english ? "6. User-Generated Content" : "6. 用户生成的内容", textAlign: TextAlign.start, softWrap: true, style: TextStyle( @@ -714,13 +737,13 @@ class PolicyAndTermsText { height: 10, ), //=============== 7. Intellectual Property =============== - const SizedBox( + SizedBox( width: 1250, child: Row( // crossAxisAlignment: WrapCrossAlignment.start, children: [ Text( - "7. Intellectual Property", + english ? "7. Intellectual Property" : "7. 知识产权", textAlign: TextAlign.start, softWrap: true, style: TextStyle( @@ -752,13 +775,13 @@ class PolicyAndTermsText { height: 10, ), //=============== 8. Termination of Use =============== - const SizedBox( + SizedBox( width: 1250, child: Row( // crossAxisAlignment: WrapCrossAlignment.start, children: [ Text( - "8. Termination of Use", + english ? "8. Termination of Use" : "8. 使用终止", textAlign: TextAlign.start, softWrap: true, style: TextStyle( @@ -790,13 +813,13 @@ class PolicyAndTermsText { height: 10, ), //=============== 9. Disclaimer of Warranties =============== - const SizedBox( + SizedBox( width: 1250, child: Row( // crossAxisAlignment: WrapCrossAlignment.start, children: [ Text( - "9. Disclaimer of Warranties", + english ? "9. Disclaimer of Warranties" : "9. 免责声明", textAlign: TextAlign.start, softWrap: true, style: TextStyle( @@ -828,13 +851,13 @@ class PolicyAndTermsText { height: 10, ), //=============== 10. Limitation of Liability =============== - const SizedBox( + SizedBox( width: 1250, child: Row( // crossAxisAlignment: WrapCrossAlignment.start, children: [ Text( - "10. Limitation of Liability", + english ? "10. Limitation of Liability" : "10. 责任限制", textAlign: TextAlign.start, softWrap: true, style: TextStyle( @@ -866,13 +889,13 @@ class PolicyAndTermsText { height: 10, ), //=============== 11. Modifications to Terms =============== - const SizedBox( + SizedBox( width: 1250, child: Row( // crossAxisAlignment: WrapCrossAlignment.start, children: [ Text( - "11. Modifications to Terms", + english ? "11. Modifications to Terms" : "11. 条款修改", textAlign: TextAlign.start, softWrap: true, style: TextStyle( @@ -904,13 +927,13 @@ class PolicyAndTermsText { height: 10, ), //=============== 12. Governing Law =============== - const SizedBox( + SizedBox( width: 1250, child: Row( // crossAxisAlignment: WrapCrossAlignment.start, children: [ Text( - "12. Governing Law", + english ? "12. Governing Law" : "12. 适用法律", textAlign: TextAlign.start, softWrap: true, style: TextStyle( @@ -942,13 +965,13 @@ class PolicyAndTermsText { height: 10, ), //=============== 13. Contact Information =============== - const SizedBox( + SizedBox( width: 1250, child: Row( // crossAxisAlignment: WrapCrossAlignment.start, children: [ Text( - "13. Contact Information", + english ? "13. Contact Information" : "13. 联系信息", textAlign: TextAlign.start, softWrap: true, style: TextStyle( diff --git a/Frontend/lib/mih_packages/about_mih/package_tools/mih_info.dart b/Frontend/lib/mih_packages/about_mih/package_tools/mih_info.dart index 0fdf31ca..98f090aa 100644 --- a/Frontend/lib/mih_packages/about_mih/package_tools/mih_info.dart +++ b/Frontend/lib/mih_packages/about_mih/package_tools/mih_info.dart @@ -1,6 +1,7 @@ import 'package:flutter_speed_dial/flutter_speed_dial.dart'; import 'package:mzansi_innovation_hub/main.dart'; import 'package:mzansi_innovation_hub/mih_config/mih_colors.dart'; +import 'package:mzansi_innovation_hub/mih_services/mih_business_details_services.dart'; import 'package:mzansi_innovation_hub/mih_services/mih_install_services.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_single_child_scroll.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_layout/mih_tile.dart'; @@ -11,7 +12,9 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_floating_menu.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_icons.dart'; +import 'package:mzansi_innovation_hub/mih_services/mih_user_services.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:redacted/redacted.dart'; import 'package:share_plus/share_plus.dart'; class MihInfo extends StatefulWidget { @@ -22,6 +25,8 @@ class MihInfo extends StatefulWidget { } class _MihInfoState extends State { + late Future _futureUserCount; + late Future _futureBusinessCount; final Uri _tiktokUrl = Uri.parse('https://www.tiktok.com/@mzansi.innovation.hub'); final Uri _whatsappUrl = @@ -87,7 +92,7 @@ class _MihInfoState extends State { textAlign: TextAlign.center, style: const TextStyle( //fontWeight: FontWeight.bold, - fontSize: 15, + fontSize: 17, ), ), ), @@ -144,7 +149,7 @@ class _MihInfoState extends State { textAlign: TextAlign.center, style: const TextStyle( //fontWeight: FontWeight.bold, - fontSize: 15, + fontSize: 17, ), ), ], @@ -178,7 +183,7 @@ class _MihInfoState extends State { textAlign: TextAlign.center, style: const TextStyle( //fontWeight: FontWeight.bold, - fontSize: 15, + fontSize: 17, ), ), ], @@ -506,6 +511,123 @@ class _MihInfoState extends State { ); } + Widget displayBusinessCount() { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + FutureBuilder( + future: _futureBusinessCount, + builder: (context, snapshot) { + bool isLoading = true; + String userCount = "⚠️"; + if (snapshot.connectionState == ConnectionState.waiting) { + isLoading = true; + } else if (snapshot.connectionState == ConnectionState.done && + snapshot.hasError) { + isLoading = false; + } else if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) { + isLoading = false; + userCount = snapshot.data.toString(); + } else { + isLoading = true; + } + return SizedBox( + child: Text( + userCount, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 23, + ), + ), + ).redacted( + context: context, + redact: isLoading, + configuration: RedactedConfiguration( + defaultBorderRadius: BorderRadius.circular(5), + redactedColor: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark", + ), + ), + ); + }, + ), + const SizedBox(width: 10), + Text( + "Businesses", + textAlign: TextAlign.center, + style: TextStyle( + fontWeight: FontWeight.normal, + fontSize: 20, + ), + ), + ], + ); + } + + Widget displayUserCount() { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + FutureBuilder( + future: _futureUserCount, + builder: (context, snapshot) { + bool isLoading = true; + String userCount = "⚠️"; + if (snapshot.connectionState == ConnectionState.waiting) { + isLoading = true; + } else if (snapshot.connectionState == ConnectionState.done && + snapshot.hasError) { + isLoading = false; + } else if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) { + isLoading = false; + userCount = snapshot.data.toString(); + } else { + isLoading = true; + } + return SizedBox( + child: Text( + userCount, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 23, + ), + ), + ).redacted( + context: context, + redact: isLoading, + configuration: RedactedConfiguration( + defaultBorderRadius: BorderRadius.circular(5), + redactedColor: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark", + ), + ), + ); + }, + ), + const SizedBox(width: 10), + Text( + "Users", + textAlign: TextAlign.center, + style: TextStyle( + fontWeight: FontWeight.normal, + fontSize: 20, + ), + ), + ], + ); + } + + @override + void initState() { + super.initState(); + _futureUserCount = MihUserServices().fetchUserCount(); + _futureBusinessCount = MihBusinessDetailsServices().fetchBusinessCount(); + } + @override Widget build(BuildContext context) { return MihPackageToolBody( @@ -536,6 +658,7 @@ class _MihInfoState extends State { ), const Text( "Mzansi Innovation Hub", + textAlign: TextAlign.center, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 30, @@ -543,30 +666,83 @@ class _MihInfoState extends State { ), Text( "MIH App Version: ${MzansiInnovationHub.of(context)!.theme.getLatestVersion()}", + textAlign: TextAlign.center, style: const TextStyle( fontWeight: FontWeight.normal, fontSize: 15, ), ), - const Padding( - padding: EdgeInsets.symmetric(vertical: 10.0), - child: Divider(), + // ===================== Divider + // Padding( + // padding: EdgeInsets.symmetric( + // vertical: 10.0, + // horizontal: 25, + // ), + // child: Divider( + // thickness: 1, + // color: MihColors.getGreyColor( + // MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + // ), + // ), + const SizedBox( + height: 10, + ), + // Text( + // "The MIH Community", + // textAlign: TextAlign.center, + // style: TextStyle( + // fontWeight: FontWeight.bold, + // fontSize: 22, + // ), + // ), + Wrap( + alignment: WrapAlignment.spaceAround, + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 25, + runSpacing: 10, + children: [ + displayUserCount(), + displayBusinessCount(), + ], + ), + Text( + "The MIH Community", + textAlign: TextAlign.center, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 22, + ), + ), + // ===================== Divider + Padding( + padding: EdgeInsets.symmetric( + vertical: 10.0, + horizontal: 25, + ), + child: Divider( + thickness: 1, + color: MihColors.getGreyColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + ), ), // const SizedBox( // height: 10, // ), - Wrap( - alignment: WrapAlignment.start, - crossAxisAlignment: WrapCrossAlignment.start, - spacing: 10, - runSpacing: 10, - children: [ - ourVision(), - ourMission(), - ], + Padding( + padding: const EdgeInsets.symmetric(horizontal: 25.0), + child: Wrap( + alignment: WrapAlignment.start, + crossAxisAlignment: WrapCrossAlignment.start, + spacing: 10, + runSpacing: 10, + children: [ + ourVision(), + ourMission(), + ], + ), ), const SizedBox( - height: 10, + height: 25, ), Wrap( alignment: WrapAlignment.center, @@ -658,9 +834,17 @@ class _MihInfoState extends State { const SizedBox( height: 10, ), - const Padding( - padding: EdgeInsets.symmetric(vertical: 10.0), - child: Divider(), + // ===================== Divider + Padding( + padding: EdgeInsets.symmetric( + vertical: 10.0, + horizontal: 25, + ), + child: Divider( + thickness: 1, + color: MihColors.getGreyColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + ), ), Column( mainAxisAlignment: MainAxisAlignment.center, @@ -673,9 +857,17 @@ class _MihInfoState extends State { founderBio(), ], ), - const Padding( - padding: EdgeInsets.symmetric(vertical: 10.0), - child: Divider(), + // ===================== Divider + Padding( + padding: EdgeInsets.symmetric( + vertical: 10.0, + horizontal: 25, + ), + child: Divider( + thickness: 1, + color: MihColors.getGreyColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + ), ), mihSocials(), ], diff --git a/Frontend/lib/mih_packages/about_mih/package_tools/mih_privacy_policy.dart b/Frontend/lib/mih_packages/about_mih/package_tools/mih_privacy_policy.dart index c0d6e10e..b5054614 100644 --- a/Frontend/lib/mih_packages/about_mih/package_tools/mih_privacy_policy.dart +++ b/Frontend/lib/mih_packages/about_mih/package_tools/mih_privacy_policy.dart @@ -1,11 +1,20 @@ +import 'package:mzansi_innovation_hub/main.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_button.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_single_child_scroll.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_tool_body.dart'; +import 'package:mzansi_innovation_hub/mih_config/mih_colors.dart'; import 'package:mzansi_innovation_hub/mih_packages/about_mih/mih_policy_tos_ext/policy_and_terms_text.dart'; import 'package:flutter/material.dart'; -class MihPrivacyPolicy extends StatelessWidget { +class MihPrivacyPolicy extends StatefulWidget { const MihPrivacyPolicy({super.key}); + @override + State createState() => _MihPrivacyPolicyState(); +} + +class _MihPrivacyPolicyState extends State { + bool englishOn = true; @override Widget build(BuildContext context) { return MihPackageToolBody( @@ -16,9 +25,39 @@ class MihPrivacyPolicy extends StatelessWidget { } Widget getBody(BuildContext context) { + List children = [ + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + MihButton( + onPressed: () { + setState(() { + englishOn = !englishOn; + }); + }, + buttonColor: MihColors.getGreenColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + elevation: 10, + child: Text( + englishOn ? "Simplified Chinese" : "English", + style: TextStyle( + color: MihColors.getPrimaryColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + const SizedBox(height: 10), + ]; + children + .addAll(PolicyAndTermsText().getPrivacyPolicyText(context, englishOn)); return MihSingleChildScroll( child: Column( - children: PolicyAndTermsText().getPrivacyPolicyText(context), + mainAxisSize: MainAxisSize.max, + children: children, ), ); } diff --git a/Frontend/lib/mih_packages/about_mih/package_tools/mih_terms_of_service.dart b/Frontend/lib/mih_packages/about_mih/package_tools/mih_terms_of_service.dart index 882be16c..e4384464 100644 --- a/Frontend/lib/mih_packages/about_mih/package_tools/mih_terms_of_service.dart +++ b/Frontend/lib/mih_packages/about_mih/package_tools/mih_terms_of_service.dart @@ -1,11 +1,20 @@ +import 'package:mzansi_innovation_hub/main.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_button.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_single_child_scroll.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_tool_body.dart'; +import 'package:mzansi_innovation_hub/mih_config/mih_colors.dart'; import 'package:mzansi_innovation_hub/mih_packages/about_mih/mih_policy_tos_ext/policy_and_terms_text.dart'; import 'package:flutter/material.dart'; -class MIHTermsOfService extends StatelessWidget { +class MIHTermsOfService extends StatefulWidget { const MIHTermsOfService({super.key}); + @override + State createState() => _MIHTermsOfServiceState(); +} + +class _MIHTermsOfServiceState extends State { + bool englishOn = true; @override Widget build(BuildContext context) { return MihPackageToolBody( @@ -16,9 +25,38 @@ class MIHTermsOfService extends StatelessWidget { } Widget getBody(BuildContext context) { + List children = [ + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + MihButton( + onPressed: () { + setState(() { + englishOn = !englishOn; + }); + }, + buttonColor: MihColors.getGreenColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + elevation: 10, + child: Text( + englishOn ? "Simplified Chinese" : "English", + style: TextStyle( + color: MihColors.getPrimaryColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + const SizedBox(height: 10), + ]; + children + .addAll(PolicyAndTermsText().getTermsOfServiceText(context, englishOn)); return MihSingleChildScroll( child: Column( - children: PolicyAndTermsText().getTermsOfServiceText(context), + children: children, ), ); } diff --git a/Frontend/lib/mih_packages/mih_home/components/mih_app_drawer.dart b/Frontend/lib/mih_packages/mih_home/components/mih_app_drawer.dart index 40cb8f85..ad15cbef 100644 --- a/Frontend/lib/mih_packages/mih_home/components/mih_app_drawer.dart +++ b/Frontend/lib/mih_packages/mih_home/components/mih_app_drawer.dart @@ -200,7 +200,13 @@ class _MIHAppDrawerState extends State { ], ), onTap: () { - context.goNamed("aboutMih", extra: 1); + context.goNamed( + "aboutMih", + extra: AboutArguments( + true, + 1, + ), + ); }, ), ListTile( @@ -230,7 +236,13 @@ class _MIHAppDrawerState extends State { ], ), onTap: () { - context.goNamed("aboutMih", extra: 2); + context.goNamed( + "aboutMih", + extra: AboutArguments( + true, + 2, + ), + ); }, ), ListTile( diff --git a/Frontend/lib/mih_packages/mih_home/mih_home.dart b/Frontend/lib/mih_packages/mih_home/mih_home.dart index 8747ff7e..1e90ccb0 100644 --- a/Frontend/lib/mih_packages/mih_home/mih_home.dart +++ b/Frontend/lib/mih_packages/mih_home/mih_home.dart @@ -1,10 +1,17 @@ +import 'package:go_router/go_router.dart'; +import 'package:ken_logger/ken_logger.dart'; import 'package:mzansi_innovation_hub/main.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_objects/arguments.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_objects/user_consent.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_button.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_action.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_alert.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_tools.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_circle_avatar.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_objects/app_user.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_window.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_scack_bar.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_pop_up_messages/mih_loading_circle.dart'; import 'package:mzansi_innovation_hub/mih_config/mih_colors.dart'; import 'package:mzansi_innovation_hub/mih_config/mih_env.dart'; @@ -14,32 +21,13 @@ import 'package:mzansi_innovation_hub/mih_packages/mih_home/package_tools/mih_bu import 'package:mzansi_innovation_hub/mih_packages/mih_home/package_tools/mih_personal_home.dart'; import 'package:flutter/material.dart'; import 'package:mzansi_innovation_hub/mih_services/mih_service_calls.dart'; +import 'package:mzansi_innovation_hub/mih_services/mih_user_consent_services.dart'; // ignore: must_be_immutable class MihHome extends StatefulWidget { - // final AppUser signedInUser; - // final BusinessUser? businessUser; - // final Business? business; - // final Patient? patient; - // final List notifications; - // final ImageProvider? propicFile; - // final bool isUserNew; - // final bool isBusinessUser; - // final bool isBusinessUserNew; - // final bool isDevActive; final bool personalSelected; const MihHome({ super.key, - // required this.signedInUser, - // required this.businessUser, - // required this.business, - // required this.patient, - // required this.notifications, - // required this.propicFile, - // required this.isUserNew, - // required this.isBusinessUser, - // required this.isBusinessUserNew, - // required this.isDevActive, required this.personalSelected, }); @@ -52,6 +40,79 @@ class _MihHomeState extends State { late int _selcetedIndex; late bool _personalSelected; late Future profileData; + late Future futureUserConsent; + bool showUserConsent = false; + DateTime latestPrivacyPolicyDate = DateTime.parse("2024-12-01"); + DateTime latestTermOfServiceDate = DateTime.parse("2024-12-01"); + + bool showPolicyWindow(UserConsent? userConsent) { + if (userConsent == null) { + return true; + } else { + if (userConsent.privacy_policy_accepted + .isAfter(latestPrivacyPolicyDate) && + userConsent.terms_of_services_accepted + .isAfter(latestTermOfServiceDate)) { + return false; + } else { + return true; + } + } + } + + void createOrUpdateAccpetance(UserConsent? userConsent, String app_id) { + userConsent != null + ? MihUserConsentServices() + .updateUserConsentStatus( + app_id, + DateTime.now().toIso8601String(), + DateTime.now().toIso8601String(), + ) + .then((value) { + if (value == 200) { + // setState(() { + // showUserConsent = false; + // }); + context.goNamed("mihHome", extra: false); + ScaffoldMessenger.of(context).showSnackBar( + MihSnackBar( + child: Text("Thank you for accepting our Policies"), + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + MihSnackBar( + child: Text("There was an error, please try again later"), + ), + ); + } + }) + : MihUserConsentServices() + .insertUserConsentStatus( + app_id, + DateTime.now().toIso8601String(), + DateTime.now().toIso8601String(), + ) + .then((value) { + if (value == 201) { + // setState(() { + // showUserConsent = false; + // }); + context.goNamed("mihHome", extra: false); + ScaffoldMessenger.of(context).showSnackBar( + MihSnackBar( + child: Text("Thank you for accepting our Policies"), + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + MihSnackBar( + child: Text("There was an error, please try again later"), + ), + ); + } + }); + } @override void dispose() { @@ -62,6 +123,7 @@ class _MihHomeState extends State { void initState() { super.initState(); profileData = MIHApiCalls().getProfile(10, context); + futureUserConsent = MihUserConsentServices().getUserConsentStatus(); if (widget.personalSelected == true) { setState(() { _selcetedIndex = 0; @@ -96,30 +158,259 @@ class _MihHomeState extends State { ); } else if (asyncSnapshot.connectionState == ConnectionState.done && asyncSnapshot.hasData) { - return MihPackage( - appActionButton: getAction(asyncSnapshot.data!.profilePicUrl), - appTools: - getTools(asyncSnapshot.data!.signedInUser.type != "personal"), - appBody: getToolBody(asyncSnapshot.data!), - appToolTitles: getToolTitle(), - actionDrawer: getActionDrawer( - asyncSnapshot.data!.signedInUser, - asyncSnapshot.data!.profilePicUrl, - ), - selectedbodyIndex: _selcetedIndex, - onIndexChange: (newValue) { - if (_selcetedIndex == 0) { - setState(() { - _selcetedIndex = newValue; - _personalSelected = true; - }); - } else { - setState(() { - _selcetedIndex = newValue; - _personalSelected = false; - }); - } - }, + return Stack( + children: [ + MihPackage( + appActionButton: getAction(asyncSnapshot.data!.profilePicUrl), + appTools: getTools( + asyncSnapshot.data!.signedInUser.type != "personal"), + appBody: getToolBody(asyncSnapshot.data!), + appToolTitles: getToolTitle(), + actionDrawer: getActionDrawer( + asyncSnapshot.data!.signedInUser, + asyncSnapshot.data!.profilePicUrl, + ), + selectedbodyIndex: _selcetedIndex, + onIndexChange: (newValue) { + if (_selcetedIndex == 0) { + setState(() { + _selcetedIndex = newValue; + _personalSelected = true; + }); + } else { + setState(() { + _selcetedIndex = newValue; + _personalSelected = false; + }); + } + }, + ), + FutureBuilder( + future: futureUserConsent, + builder: (context, asyncSnapshotUserConsent) { + if (asyncSnapshotUserConsent.connectionState == + ConnectionState.waiting) { + showUserConsent = false; + } else if (asyncSnapshotUserConsent.connectionState == + ConnectionState.done && + asyncSnapshotUserConsent.hasData) { + showUserConsent = + showPolicyWindow(asyncSnapshotUserConsent.data); + } else if (asyncSnapshotUserConsent.connectionState == + ConnectionState.done && + !asyncSnapshotUserConsent.hasData) { + showUserConsent = true; + } else { + showUserConsent = false; + } + return Visibility( + visible: showUserConsent, + child: Container( + color: Colors.black.withValues(alpha: 0.5), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + MihPackageWindow( + fullscreen: false, + windowTitle: + "Privacy Policy & Terms Of Service Alert!", + onWindowTapClose: () { + showDialog( + context: context, + builder: (context) { + return MihPackageAlert( + alertIcon: Icon( + Icons.warning_amber_rounded, + size: 100, + color: MihColors.getRedColor( + MzansiInnovationHub.of(context)! + .theme + .mode == + "Dark", + ), + ), + alertTitle: + "Oops, Looks like you missed a step!", + alertBody: Text( + "We're excited for you to keep using the MIH app! Before you do, please take a moment to accept our Privacy Policy and Terms of Service. Thanks for helping us keep your experience great!", + textAlign: TextAlign.center, + style: TextStyle( + color: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)! + .theme + .mode == + "Dark", + ), + fontSize: 15, + fontWeight: FontWeight.normal, + ), + ), + alertColour: MihColors.getRedColor( + MzansiInnovationHub.of(context)! + .theme + .mode == + "Dark", + ), + ); + }); + }, + windowBody: Column( + children: [ + Icon( + Icons.policy, + size: 150, + color: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)! + .theme + .mode == + "Dark", + ), + ), + const SizedBox(height: 10), + Text( + "Welcome to the MIH App", + textAlign: TextAlign.center, + style: TextStyle( + color: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)! + .theme + .mode == + "Dark", + ), + fontSize: 30, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + Text( + "To keep using the MIH app, please take a moment to review and accept our Policies. Our agreements helps us keep things running smoothly and securely.", + textAlign: TextAlign.center, + style: TextStyle( + color: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)! + .theme + .mode == + "Dark", + ), + fontSize: 15, + fontWeight: FontWeight.normal, + ), + ), + const SizedBox(height: 20), + Center( + child: Wrap( + alignment: WrapAlignment.center, + spacing: 10, + runSpacing: 10, + children: [ + MihButton( + onPressed: () { + context.goNamed( + "aboutMih", + extra: AboutArguments( + widget.personalSelected, + 1, + ), + ); + }, + buttonColor: MihColors.getOrangeColor( + MzansiInnovationHub.of(context)! + .theme + .mode == + "Dark"), + elevation: 10, + width: 300, + child: Text( + "Privacy Policy", + style: TextStyle( + color: MihColors.getPrimaryColor( + MzansiInnovationHub.of( + context)! + .theme + .mode == + "Dark"), + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + MihButton( + onPressed: () { + context.goNamed( + "aboutMih", + extra: AboutArguments( + widget.personalSelected, + 2, + ), + ); + }, + buttonColor: MihColors.getYellowColor( + MzansiInnovationHub.of(context)! + .theme + .mode == + "Dark"), + elevation: 10, + width: 300, + child: Text( + "Terms of Service", + style: TextStyle( + color: MihColors.getPrimaryColor( + MzansiInnovationHub.of( + context)! + .theme + .mode == + "Dark"), + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + MihButton( + onPressed: () { + DateTime now = DateTime.now(); + KenLogger.success( + "Date Time Now: $now"); + createOrUpdateAccpetance( + asyncSnapshotUserConsent.data, + asyncSnapshot + .data!.signedInUser.app_id, + ); + }, + buttonColor: MihColors.getGreenColor( + MzansiInnovationHub.of(context)! + .theme + .mode == + "Dark"), + elevation: 10, + width: 300, + child: Text( + "Accept", + style: TextStyle( + color: MihColors.getPrimaryColor( + MzansiInnovationHub.of( + context)! + .theme + .mode == + "Dark"), + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + const SizedBox(height: 10), + ], + ), + ), + ], + ), + ), + ); + }), + ], ); } else { return MihHomeError( diff --git a/Frontend/lib/mih_packages/mzansi_ai/package_tools/ai_chat.dart b/Frontend/lib/mih_packages/mzansi_ai/package_tools/ai_chat.dart index 43d69089..8f789669 100644 --- a/Frontend/lib/mih_packages/mzansi_ai/package_tools/ai_chat.dart +++ b/Frontend/lib/mih_packages/mzansi_ai/package_tools/ai_chat.dart @@ -223,15 +223,17 @@ class _AiChatState extends State { child: Column( mainAxisSize: MainAxisSize.max, children: [ - GptMarkdown( - snapshot.requireData, - textAlign: TextAlign.left, - style: TextStyle( - color: MihColors.getSecondaryColor( - MzansiInnovationHub.of(context)!.theme.mode == - "Dark"), - fontSize: _chatFrontSize, - fontWeight: FontWeight.bold, + SelectionArea( + child: GptMarkdown( + snapshot.requireData, + textAlign: TextAlign.left, + style: TextStyle( + color: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)!.theme.mode == + "Dark"), + fontSize: _chatFrontSize, + fontWeight: FontWeight.bold, + ), ), ), ], diff --git a/Frontend/lib/mih_packages/mzansi_profile/business_profile/package_tools/mih_business_qr_code.dart b/Frontend/lib/mih_packages/mzansi_profile/business_profile/package_tools/mih_business_qr_code.dart index 1d31a2c4..4f1b34d9 100644 --- a/Frontend/lib/mih_packages/mzansi_profile/business_profile/package_tools/mih_business_qr_code.dart +++ b/Frontend/lib/mih_packages/mzansi_profile/business_profile/package_tools/mih_business_qr_code.dart @@ -1,8 +1,12 @@ +import 'dart:typed_data'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:file_picker/file_picker.dart'; +import 'package:file_saver/file_saver.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_speed_dial/flutter_speed_dial.dart'; import 'package:go_router/go_router.dart'; +import 'package:ken_logger/ken_logger.dart'; import 'package:mzansi_innovation_hub/main.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_objects/business.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_button.dart'; @@ -16,8 +20,8 @@ import 'package:mzansi_innovation_hub/mih_config/mih_env.dart'; import 'package:mzansi_innovation_hub/mih_services/mih_file_services.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_tool_body.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_circle_avatar.dart'; +import 'package:screenshot/screenshot.dart'; import 'package:supertokens_flutter/supertokens.dart'; -import 'package:url_launcher/url_launcher.dart'; class MihBusinessQrCode extends StatefulWidget { final Business business; @@ -38,6 +42,8 @@ class _MihBusinessQrCodeState extends State { late String qrCodedata; int qrSize = 500; bool _isUserSignedIn = false; + ScreenshotController screenshotController = ScreenshotController(); + Uint8List? businessQRImageFile; Future _checkUserSession() async { final doesSessionExist = await SuperTokens.doesSessionExist(); @@ -65,12 +71,88 @@ class _MihBusinessQrCodeState extends State { return "https://api.qrserver.com/v1/create-qr-code/?data=$encodedData&size=${qrSize}x${qrSize}&bgcolor=$bgColor&color=$color"; } + Future saveImage(Uint8List imageBytes) async { + final String filename = + "${widget.business.Name}_QR_Code_${DateTime.now().millisecondsSinceEpoch}.png"; + if (kIsWeb) { + await FileSaver.instance.saveFile( + name: filename, + bytes: imageBytes, + fileExtension: "png", + mimeType: MimeType.png, + ); + } else { + await FileSaver.instance.saveAs( + name: filename, + bytes: imageBytes, + fileExtension: "png", + mimeType: MimeType.png, + ); + } + // if (kIsWeb) { + // final blob = html.Blob([imageBytes]); + // final url = html.Url.createObjectUrlFromBlob(blob); + // final anchor = html.document.createElement('a') as html.AnchorElement + // ..href = url + // ..style.display = 'none' + // ..download = filename; // Suggested filename for the download + + // html.document.body!.children.add(anchor); + // anchor.click(); // Programmatically click the link to trigger download + + // html.document.body!.children.remove(anchor); + // html.Url.revokeObjectUrl(url); + // } else { + // var permission = await FlDownloader.requestPermission(); + // if (permission == StoragePermissionStatus.granted) { + // try { + // mihLoadingPopUp(); + // KenLogger.success("Downloading from URL: $url"); + // await FlDownloader.download(url); + // Navigator.of(context).pop(); + // } on Exception catch (error) { + // Navigator.of(context).pop(); + // print(error); + // } + // } else { + // print("denied"); + // } + // try { + // final directory = await getDownloadsDirectory(); + // final file = File('${directory?.path}/$filename'); + // await file.writeAsBytes(imageBytes); + // KenLogger.success("File saved at: ${file.path}"); + // } catch (e) { + // KenLogger.error("Error saving file: $e"); + // } + // } + } + + void mihLoadingPopUp() { + showDialog( + context: context, + builder: (context) { + return const Mihloadingcircle(); + }, + ); + } + Future downloadQrCode() async { if (_isUserSignedIn) { - final Uri uri = Uri.parse(getQrCodeData(1024)); - if (!await launchUrl(uri)) { - throw 'Could not launch $uri'; - } + // final Uri uri = Uri.parse(getQrCodeData(1024)); + // if (!await launchUrl(uri)) { + // throw 'Could not launch $uri'; + // } + await screenshotController.capture().then((image) { + KenLogger.success("Image Captured: $image"); + setState(() { + businessQRImageFile = image; + }); + }).catchError((onError) { + KenLogger.error(onError); + }); + KenLogger.success("QR Code Image Captured : $businessQRImageFile"); + saveImage(businessQRImageFile!); } else { showSignInRequiredAlert(); } @@ -134,6 +216,150 @@ class _MihBusinessQrCodeState extends State { ); } + Widget displayBusinessQRCode(double profilePictureWidth) { + return Screenshot( + controller: screenshotController, + child: Material( + color: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark") + .withValues(alpha: 0.6), + borderRadius: BorderRadius.circular(25), + elevation: 10, + shadowColor: Colors.black, + child: Container( + decoration: BoxDecoration( + color: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + borderRadius: BorderRadius.circular(20), + ), + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + FutureBuilder( + future: futureImageUrl, + builder: (context, asyncSnapshot) { + if (asyncSnapshot.connectionState == + ConnectionState.done && + asyncSnapshot.hasData) { + if (asyncSnapshot.requireData != "") { + return MihCircleAvatar( + imageFile: NetworkImage(asyncSnapshot.requireData), + width: profilePictureWidth, + editable: false, + fileNameController: TextEditingController(), + userSelectedfile: file, + frameColor: MihColors.getPrimaryColor( + MzansiInnovationHub.of(context)!.theme.mode == + "Dark"), + backgroundColor: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)!.theme.mode == + "Dark"), + onChange: () {}, + ); + } else { + return Icon( + MihIcons.iDontKnow, + size: profilePictureWidth, + color: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)!.theme.mode == + "Dark"), + ); + } + } else { + return Icon( + MihIcons.mihRing, + size: profilePictureWidth, + color: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)!.theme.mode == + "Dark"), + ); + } + }, + ), + FittedBox( + child: Text( + widget.business.Name, + style: TextStyle( + fontSize: 35, + fontWeight: FontWeight.bold, + color: MihColors.getPrimaryColor( + MzansiInnovationHub.of(context)!.theme.mode == + "Dark"), + ), + ), + ), + FittedBox( + child: Text( + widget.business.type, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w600, + color: MihColors.getPrimaryColor( + MzansiInnovationHub.of(context)!.theme.mode == + "Dark"), + ), + ), + ), + const SizedBox(height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FittedBox( + child: Text( + "Powered by MIH", + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + color: MihColors.getPrimaryColor( + MzansiInnovationHub.of(context)!.theme.mode == + "Dark"), + ), + ), + ), + const SizedBox(width: 5), + Icon( + MihIcons.mihLogo, + size: 20, + color: MihColors.getPrimaryColor( + MzansiInnovationHub.of(context)!.theme.mode == + "Dark"), + ), + ], + ), + const SizedBox(height: 10), + SizedBox( + width: 300, + height: 300, + child: CachedNetworkImage( + imageUrl: getQrCodeData(qrSize.toInt()), + placeholder: (context, url) => const Mihloadingcircle(), + errorWidget: (context, url, error) => + const Icon(Icons.error), + ), + ), + const SizedBox(height: 10), + FittedBox( + child: Text( + "Scan & Connect", + style: TextStyle( + fontSize: 30, + fontWeight: FontWeight.bold, + color: MihColors.getPrimaryColor( + MzansiInnovationHub.of(context)!.theme.mode == + "Dark"), + ), + ), + ), + ], + )), + ), + ), + ); + } + @override void dispose() { super.dispose(); @@ -175,137 +401,7 @@ class _MihBusinessQrCodeState extends State { horizontal: screenSize.width * 0), //.075), child: Padding( padding: const EdgeInsets.only(top: 10.0), - child: Material( - color: MihColors.getSecondaryColor( - MzansiInnovationHub.of(context)!.theme.mode == "Dark") - .withValues(alpha: 0.6), - borderRadius: BorderRadius.circular(25), - elevation: 10, - shadowColor: Colors.black, - child: Container( - decoration: BoxDecoration( - color: MihColors.getSecondaryColor( - MzansiInnovationHub.of(context)!.theme.mode == - "Dark"), - borderRadius: BorderRadius.circular(20), - ), - child: Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - FutureBuilder( - future: futureImageUrl, - builder: (context, asyncSnapshot) { - if (asyncSnapshot.connectionState == - ConnectionState.done && - asyncSnapshot.hasData) { - if (asyncSnapshot.requireData != "") { - return MihCircleAvatar( - imageFile: NetworkImage( - asyncSnapshot.requireData), - width: profilePictureWidth, - editable: false, - fileNameController: - TextEditingController(), - userSelectedfile: file, - frameColor: MihColors.getPrimaryColor( - MzansiInnovationHub.of(context)! - .theme - .mode == - "Dark"), - backgroundColor: - MihColors.getSecondaryColor( - MzansiInnovationHub.of(context)! - .theme - .mode == - "Dark"), - onChange: () {}, - ); - } else { - return Icon( - MihIcons.iDontKnow, - size: profilePictureWidth, - color: MihColors.getSecondaryColor( - MzansiInnovationHub.of(context)! - .theme - .mode == - "Dark"), - ); - } - } else { - return Icon( - MihIcons.mihRing, - size: profilePictureWidth, - color: MihColors.getSecondaryColor( - MzansiInnovationHub.of(context)! - .theme - .mode == - "Dark"), - ); - } - }, - ), - FittedBox( - child: Text( - widget.business.Name, - style: TextStyle( - fontSize: 35, - fontWeight: FontWeight.bold, - color: MihColors.getPrimaryColor( - MzansiInnovationHub.of(context)! - .theme - .mode == - "Dark"), - ), - ), - ), - FittedBox( - child: Text( - widget.business.type, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w600, - color: MihColors.getPrimaryColor( - MzansiInnovationHub.of(context)! - .theme - .mode == - "Dark"), - ), - ), - ), - const SizedBox(height: 10), - SizedBox( - width: 300, - height: 300, - child: CachedNetworkImage( - imageUrl: getQrCodeData(qrSize.toInt()), - placeholder: (context, url) => - const Mihloadingcircle(), - errorWidget: (context, url, error) => - const Icon(Icons.error), - ), - ), - const SizedBox(height: 10), - FittedBox( - child: Text( - "Scan & Connect", - style: TextStyle( - fontSize: 30, - fontWeight: FontWeight.bold, - color: MihColors.getPrimaryColor( - MzansiInnovationHub.of(context)! - .theme - .mode == - "Dark"), - ), - ), - ), - ], - )), - ), - ), + child: displayBusinessQRCode(profilePictureWidth), ), ), ), diff --git a/Frontend/lib/mih_packages/mzansi_wallet/builder/build_loyalty_card_list.dart b/Frontend/lib/mih_packages/mzansi_wallet/builder/build_loyalty_card_list.dart index 688cfcc4..b791c364 100644 --- a/Frontend/lib/mih_packages/mzansi_wallet/builder/build_loyalty_card_list.dart +++ b/Frontend/lib/mih_packages/mzansi_wallet/builder/build_loyalty_card_list.dart @@ -575,7 +575,7 @@ class _BuildLoyaltyCardListState extends State { KenLogger.success("Can change system brightness: $canChange"); if (canChange) { // Permission is granted, you can now change the system brightness - ScreenBrightness.instance.system.then((brightness) { + await ScreenBrightness.instance.system.then((brightness) { setState(() { _originalBrightness = brightness; }); diff --git a/Frontend/lib/mih_services/mih_business_details_services.dart b/Frontend/lib/mih_services/mih_business_details_services.dart index 252c707d..b59840c1 100644 --- a/Frontend/lib/mih_services/mih_business_details_services.dart +++ b/Frontend/lib/mih_services/mih_business_details_services.dart @@ -9,6 +9,21 @@ import '../mih_components/mih_pop_up_messages/mih_error_message.dart'; import 'package:supertokens_flutter/http.dart' as http; class MihBusinessDetailsServices { + Future fetchBusinessCount() async { + var response = await http.get( + Uri.parse("${AppEnviroment.baseApiUrl}/business/count/"), + headers: { + "Content-Type": "application/json; charset=UTF-8" + }, + ); + if (response.statusCode == 200) { + var jsonBody = jsonDecode(response.body); + return jsonBody['count']; + } else { + return 0; + } + } + Future> fetchAllBusinessTypes() async { var response = await http.get( Uri.parse("${AppEnviroment.baseApiUrl}/business/types/"), diff --git a/Frontend/lib/mih_services/mih_user_consent_services.dart b/Frontend/lib/mih_services/mih_user_consent_services.dart new file mode 100644 index 00000000..f4246916 --- /dev/null +++ b/Frontend/lib/mih_services/mih_user_consent_services.dart @@ -0,0 +1,57 @@ +import 'dart:convert'; + +import 'package:mzansi_innovation_hub/mih_components/mih_objects/user_consent.dart'; +import 'package:mzansi_innovation_hub/mih_config/mih_env.dart'; +import 'package:supertokens_flutter/http.dart' as http; +import 'package:supertokens_flutter/supertokens.dart'; + +class MihUserConsentServices { + Future getUserConsentStatus() async { + var app_id = await SuperTokens.getUserId(); + final response = await http.get( + Uri.parse("${AppEnviroment.baseApiUrl}/user-consent/user/$app_id")); + if (response.statusCode == 200) { + Map userMap = jsonDecode(response.body); + UserConsent userConsent = UserConsent.fromJson(userMap); + return userConsent; + } else { + return null; + } + } + + Future insertUserConsentStatus( + String app_id, + String latestPrivacyPolicyDate, + String latestTermOfServiceDate, + ) async { + UserConsent userConsent = UserConsent( + app_id: app_id, + privacy_policy_accepted: DateTime.parse(latestPrivacyPolicyDate), + terms_of_services_accepted: DateTime.parse(latestTermOfServiceDate), + ); + final response = await http.post( + Uri.parse("${AppEnviroment.baseApiUrl}/user-consent/insert/"), + headers: {"Content-Type": "application/json"}, + body: jsonEncode(userConsent.toJson()), + ); + return response.statusCode; + } + + Future updateUserConsentStatus( + String app_id, + String latestPrivacyPolicyDate, + String latestTermOfServiceDate, + ) async { + UserConsent userConsent = UserConsent( + app_id: app_id, + privacy_policy_accepted: DateTime.parse(latestPrivacyPolicyDate), + terms_of_services_accepted: DateTime.parse(latestTermOfServiceDate), + ); + final response = await http.put( + Uri.parse("${AppEnviroment.baseApiUrl}/user-consent/update"), + headers: {"Content-Type": "application/json"}, + body: jsonEncode(userConsent.toJson()), + ); + return response.statusCode; + } +} diff --git a/Frontend/lib/mih_services/mih_user_services.dart b/Frontend/lib/mih_services/mih_user_services.dart index 4b267345..44f83c87 100644 --- a/Frontend/lib/mih_services/mih_user_services.dart +++ b/Frontend/lib/mih_services/mih_user_services.dart @@ -33,6 +33,21 @@ class MihUserServices { } } + Future fetchUserCount() async { + var response = await http.get( + Uri.parse("${AppEnviroment.baseApiUrl}/users/count/"), + headers: { + "Content-Type": "application/json; charset=UTF-8" + }, + ); + if (response.statusCode == 200) { + var jsonBody = jsonDecode(response.body); + return jsonBody['count']; + } else { + return 0; + } + } + Future createUser( String email, String app_id, diff --git a/Frontend/linux/flutter/generated_plugin_registrant.cc b/Frontend/linux/flutter/generated_plugin_registrant.cc index 2dccc220..88c7a8bd 100644 --- a/Frontend/linux/flutter/generated_plugin_registrant.cc +++ b/Frontend/linux/flutter/generated_plugin_registrant.cc @@ -6,10 +6,14 @@ #include "generated_plugin_registrant.h" +#include #include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_saver_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSaverPlugin"); + file_saver_plugin_register_with_registrar(file_saver_registrar); g_autoptr(FlPluginRegistrar) printing_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "PrintingPlugin"); printing_plugin_register_with_registrar(printing_registrar); diff --git a/Frontend/linux/flutter/generated_plugins.cmake b/Frontend/linux/flutter/generated_plugins.cmake index 45f23698..561713c0 100644 --- a/Frontend/linux/flutter/generated_plugins.cmake +++ b/Frontend/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_saver printing url_launcher_linux ) diff --git a/Frontend/linux/runner/CMakeLists.txt b/Frontend/linux/runner/CMakeLists.txt new file mode 100644 index 00000000..e97dabc7 --- /dev/null +++ b/Frontend/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/Frontend/linux/runner/main.cc b/Frontend/linux/runner/main.cc new file mode 100644 index 00000000..e7c5c543 --- /dev/null +++ b/Frontend/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/Frontend/linux/runner/my_application.cc b/Frontend/linux/runner/my_application.cc new file mode 100644 index 00000000..eded6ced --- /dev/null +++ b/Frontend/linux/runner/my_application.cc @@ -0,0 +1,130 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "mzansi_innovation_hub"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "mzansi_innovation_hub"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/Frontend/linux/runner/my_application.h b/Frontend/linux/runner/my_application.h new file mode 100644 index 00000000..72271d5e --- /dev/null +++ b/Frontend/linux/runner/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/Frontend/macos/Flutter/GeneratedPluginRegistrant.swift b/Frontend/macos/Flutter/GeneratedPluginRegistrant.swift index 6abc3575..d79f29c2 100644 --- a/Frontend/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/Frontend/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,10 +8,12 @@ import Foundation import app_settings import device_info_plus import file_picker +import file_saver import flutter_tts import geolocator_apple import local_auth_darwin import mobile_scanner +import package_info_plus import path_provider_foundation import printing import screen_brightness_macos @@ -26,10 +28,12 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AppSettingsPlugin.register(with: registry.registrar(forPlugin: "AppSettingsPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) + FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin")) FlutterTtsPlugin.register(with: registry.registrar(forPlugin: "FlutterTtsPlugin")) GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) FLALocalAuthPlugin.register(with: registry.registrar(forPlugin: "FLALocalAuthPlugin")) MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PrintingPlugin.register(with: registry.registrar(forPlugin: "PrintingPlugin")) ScreenBrightnessMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenBrightnessMacosPlugin")) diff --git a/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index c9db10c7..24832016 100644 Binary files a/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index f2df8559..e5246083 100644 Binary files a/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index aca5af0e..4f4c0c5b 100644 Binary files a/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index e2b78bc9..30ae352a 100644 Binary files a/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index 67e01f1d..a97ff5f5 100644 Binary files a/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index b5ac2f88..68fe215f 100644 Binary files a/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 511dc4d8..549aaddf 100644 Binary files a/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/Frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/Frontend/pubspec.lock b/Frontend/pubspec.lock index cc62b381..006b5171 100644 --- a/Frontend/pubspec.lock +++ b/Frontend/pubspec.lock @@ -417,6 +417,14 @@ packages: url: "https://pub.dev" source: hosted version: "10.1.9" + file_saver: + dependency: "direct main" + description: + name: file_saver + sha256: "9d93db09bd4da9e43238f9dd485360fc51a5c138eea5ef5f407ec56e58079ac0" + url: "https://pub.dev" + source: hosted + version: "0.3.1" fixnum: dependency: transitive description: @@ -936,6 +944,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.2+1" + os_detect: + dependency: transitive + description: + name: os_detect + sha256: "7d87c0dd98c6faf110d5aa498e9a6df02ffce4bb78cc9cfc8ad02929be9bb71f" + url: "https://pub.dev" + source: hosted + version: "2.0.3" package_config: dependency: transitive description: @@ -944,6 +960,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d + url: "https://pub.dev" + source: hosted + version: "9.0.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" + url: "https://pub.dev" + source: hosted + version: "3.2.1" path: dependency: transitive description: @@ -1208,6 +1240,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + screenshot: + dependency: "direct main" + description: + name: screenshot + sha256: "63817697a7835e6ce82add4228e15d233b74d42975c143ad8cfe07009fab866b" + url: "https://pub.dev" + source: hosted + version: "3.0.0" scroll_to_index: dependency: transitive description: @@ -1557,6 +1597,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.2" + upgrader: + dependency: "direct main" + description: + name: upgrader + sha256: e4878f33198ed627af9ec64cb12626ca12672ad94e9671feccd58625ccb484b6 + url: "https://pub.dev" + source: hosted + version: "12.0.0" url_launcher: dependency: "direct main" description: @@ -1661,6 +1709,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + version: + dependency: transitive + description: + name: version + sha256: "3d4140128e6ea10d83da32fef2fa4003fccbf6852217bb854845802f04191f94" + url: "https://pub.dev" + source: hosted + version: "3.0.2" visibility_detector: dependency: transitive description: diff --git a/Frontend/pubspec.yaml b/Frontend/pubspec.yaml index 02360f76..43d2c8fa 100644 --- a/Frontend/pubspec.yaml +++ b/Frontend/pubspec.yaml @@ -1,7 +1,8 @@ name: mzansi_innovation_hub description: "" publish_to: 'none' # Remove this line if you wish to publish to pub.dev -version: 1.2.0+97 +version: 1.2.1+98 +# version: 1.1.1+97 #--- Updated version for upgrader package testing environment: sdk: '>=3.5.3 <4.0.0' @@ -38,7 +39,7 @@ dependencies: flutter_chat_ui: ^1.6.15 flutter_chat_types: ^3.6.2 uuid: ^4.5.1 - flutter_tts: ^4.2.2 + flutter_tts: ^4.2.3 flutter_speed_dial: ^7.0.0 share_plus: ^11.0.0 app_settings: ^6.1.1 @@ -52,6 +53,9 @@ dependencies: screen_brightness: ^2.1.6 cached_network_image: ^3.4.1 gpt_markdown: ^1.1.2 + upgrader: ^12.0.0 + screenshot: ^3.0.0 + file_saver: ^0.3.1 dev_dependencies: flutter_test: diff --git a/Frontend/web/favicon.png b/Frontend/web/favicon.png index aca5af0e..4f4c0c5b 100644 Binary files a/Frontend/web/favicon.png and b/Frontend/web/favicon.png differ diff --git a/Frontend/web/icons/Icon-192.png b/Frontend/web/icons/Icon-192.png index 9f2c031b..3fe523f0 100644 Binary files a/Frontend/web/icons/Icon-192.png and b/Frontend/web/icons/Icon-192.png differ diff --git a/Frontend/web/icons/Icon-512.png b/Frontend/web/icons/Icon-512.png index b5ac2f88..68fe215f 100644 Binary files a/Frontend/web/icons/Icon-512.png and b/Frontend/web/icons/Icon-512.png differ diff --git a/Frontend/web/icons/Icon-maskable-192.png b/Frontend/web/icons/Icon-maskable-192.png index 9f2c031b..3fe523f0 100644 Binary files a/Frontend/web/icons/Icon-maskable-192.png and b/Frontend/web/icons/Icon-maskable-192.png differ diff --git a/Frontend/web/icons/Icon-maskable-512.png b/Frontend/web/icons/Icon-maskable-512.png index b5ac2f88..68fe215f 100644 Binary files a/Frontend/web/icons/Icon-maskable-512.png and b/Frontend/web/icons/Icon-maskable-512.png differ diff --git a/Frontend/web/index.html b/Frontend/web/index.html index a3679a41..3bb4dfc8 100644 --- a/Frontend/web/index.html +++ b/Frontend/web/index.html @@ -150,6 +150,10 @@ + + + + @@ -280,6 +284,10 @@ + + + + diff --git a/Frontend/web/privacy-simplified-chinese.html b/Frontend/web/privacy-simplified-chinese.html new file mode 100644 index 00000000..4d1df4c2 --- /dev/null +++ b/Frontend/web/privacy-simplified-chinese.html @@ -0,0 +1,153 @@ + + + + + + + + +

隐私政策

+ + + +

生效日期: 2024年12月6日

+ Mzansi Innovation Hub - MIH(“我们”)重视您的隐私,并致力于保护您的个人数据。本隐私政策解释了当您使用我们面向全球推出的应用程序 Mzansi Innovation Hub - MIH + 时,我们如何收集、使用、披露和保护您的信息。 +

+

1. 我们收集的信息

+

+ 我们收集以下个人信息以提供和改进我们的服务: +

    +
  • 个人信息:姓名、身份证号、地址、电话号码等。
  • +
  • 医疗信息:医疗援助信息(如适用)。
  • +
  • 会员卡信息:用于 Mzansi Wallet 功能的会员卡号。
  • +
+

+

2. 我们如何使用您的信息

+

+ 您的个人信息将用于以下目的: +

    +
  • 创建和管理您的帐户。
  • +
  • 促进客户与企业之间的互动。
  • +
  • 在 Mzansi Wallet 中存储会员卡信息。
  • +
  • 提供技术支持并改进我们应用程序的功能。
  • +
+

+

3. 数据共享

+

+ 我们仅在以下情况下共享您的数据: +

    +
  • 征得您的同意:企业只有在您明确许可的情况下才能访问您的信息。
  • +
  • 法律义务:我们可能会根据适用法律法规披露信息。
  • +
+

+

4. 数据安全

+

我们实施先进的安全措施来保护您的个人数据: +

    +
  • 传输过程中的数据加密。
  • +
  • 安全的身份验证协议,以防止未经授权的访问。
  • +
  • 定期审核以识别和解决漏洞。
  • +
+

+

5. 您的权利

+

您对您的个人数据拥有以下权利: +

    +
  • 访问和更正:通过您的帐户设置查看和更新​​您的信息。
  • +
  • 数据删除:请求删除您的帐户和相关数据。
  • +
  • 撤回同意:一旦获得授权,企业访问您数据的权限将被限制撤销。 +
  • +
  • 要行使这些权利,请通过 mzansi.innovation.hub@gmail.com 与我们联系。
  • +
+

+

6. 数据保留

+

我们会在提供服务所需的时间内保留您的个人数据。帐户删除后,您的数据将被永久删除,除非法律要求保留某些记录。

+

7. 本隐私政策的变更

+

我们可能会更新本隐私政策,以反映我们实践或法律要求的变化。我们将通过应用内通知和/或电子邮件通知您重大更新。

+

8. 联系我们

+

+ 如果您对本隐私政策有任何疑问或疑虑,请联系我们: +

    +
  • 邮箱: mzansi.innovation.hub@gmail.com.
  • +
  • 电话: +27 655 530 195
  • +
+

+ + + \ No newline at end of file diff --git a/Frontend/web/privacy.html b/Frontend/web/privacy.html index 4f9e4a80..ef9479cd 100644 --- a/Frontend/web/privacy.html +++ b/Frontend/web/privacy.html @@ -47,7 +47,21 @@ width: 40%; } - .privacy-btn { + .simplified-chinese-btn { + background-color: #bedcfe; + color: #3A4454; + border: none; + padding: 10px 24px; + border-radius: 20px; + font-size: 14px; + cursor: pointer; + position: absolute; + top: 5px; + right: 5px; + } + + + .back-to-mih-btn { background-color: #bedcfe; color: #3A4454; border: none; @@ -70,7 +84,9 @@

Privacy Policy

- +

Effective Date: 6 December 2024

Mzansi Innovation Hub - MIH ("we," "our," "us") values your privacy and is committed to protecting your personal diff --git a/Frontend/windows/flutter/generated_plugin_registrant.cc b/Frontend/windows/flutter/generated_plugin_registrant.cc index 3632ed0b..9dbdf6de 100644 --- a/Frontend/windows/flutter/generated_plugin_registrant.cc +++ b/Frontend/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,7 @@ #include "generated_plugin_registrant.h" +#include #include #include #include @@ -17,6 +18,8 @@ #include void RegisterPlugins(flutter::PluginRegistry* registry) { + FileSaverPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSaverPlugin")); FlDownloaderPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlDownloaderPluginCApi")); FlutterTtsPluginRegisterWithRegistrar( diff --git a/Frontend/windows/flutter/generated_plugins.cmake b/Frontend/windows/flutter/generated_plugins.cmake index 6c32f4da..5d18711f 100644 --- a/Frontend/windows/flutter/generated_plugins.cmake +++ b/Frontend/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + file_saver fl_downloader flutter_tts geolocator_windows diff --git a/Frontend/windows/runner/resources/app_icon.ico b/Frontend/windows/runner/resources/app_icon.ico index b643450e..e787f95b 100644 Binary files a/Frontend/windows/runner/resources/app_icon.ico and b/Frontend/windows/runner/resources/app_icon.ico differ diff --git a/backend/.DS_Store b/backend/.DS_Store index 60c256ce..74465b9f 100644 Binary files a/backend/.DS_Store and b/backend/.DS_Store differ diff --git a/backend/main.py b/backend/main.py index f542a1f4..8210a6ad 100644 --- a/backend/main.py +++ b/backend/main.py @@ -18,6 +18,7 @@ import routers.access_request as access_request import routers.patient_access as patient_access import routers.mzansi_wallet as mzansi_wallet import routers.mzansi_directory as mzansi_directory +import routers.user_consent as user_consent import routers.icd10_codes as icd10_codes from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware import Middleware @@ -94,6 +95,7 @@ app.include_router(business.router) app.include_router(notifications.router) app.include_router(mzansi_wallet.router) app.include_router(mzansi_directory.router) +app.include_router(user_consent.router) app.include_router(icd10_codes.router) app.include_router(appointments.router) diff --git a/backend/mih_database/mihDbObjects.py b/backend/mih_database/mihDbObjects.py index df8c6e54..a1332903 100644 --- a/backend/mih_database/mihDbObjects.py +++ b/backend/mih_database/mihDbObjects.py @@ -1,4 +1,4 @@ -from sqlalchemy import DateTime, Column, Integer, String +from sqlalchemy import DateTime, Column, Integer, String, text from sqlalchemy.orm import declarative_base Base = declarative_base() @@ -81,4 +81,20 @@ class BookmarkedBusiness(Base): return ( f"" + ) + +class UserConsent(Base): + __tablename__ = 'user_consent' + __table_args__ = {'schema': 'app_data'} + iduser_consent = Column(Integer, primary_key=True) + app_id = Column(String(128), nullable=False,server_default=text("''")) + privacy_policy_accepted = Column(DateTime, nullable=True) + terms_of_services_accepted = Column(DateTime, nullable=True) + + def __repr__(self): + return ( + f"" ) \ No newline at end of file diff --git a/backend/routers/business.py b/backend/routers/business.py index e1384d29..560e8b81 100644 --- a/backend/routers/business.py +++ b/backend/routers/business.py @@ -6,6 +6,7 @@ import mih_database.mihDbConnections from mih_database.mihDbObjects import User, Business, BusinessRating, BookmarkedBusiness from sqlalchemy import desc, or_ from sqlalchemy.orm import Session +from sqlalchemy.sql import func #SuperToken Auth from front end from supertokens_python.recipe.session.framework.fastapi import verify_session from supertokens_python.recipe.session import SessionContainer @@ -62,6 +63,24 @@ class businessUpdateRequestV2(BaseModel): rating: str mission_vision: str +@router.get("/business/count/", tags=["MIH Business"]) +async def read_business_by_business_id(): #, session: SessionContainer = Depends(verify_session()) + dbEngine = mih_database.mihDbConnections.dbAllConnect() + dbSession = Session(dbEngine) + try: + queryResults = dbSession.query(func.count(Business.business_id)).scalar() + response_data = {"count": queryResults} + return response_data + except Exception as e: + print(f"An error occurred during the ORM query: {e}") + if dbSession.is_active: + dbSession.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Failed to retrieve records due to an internal server error." + ) + finally: + dbSession.close() # Get List of all files @router.get("/business/types/", tags=["MIH Business"]) diff --git a/backend/routers/user_consent.py b/backend/routers/user_consent.py new file mode 100644 index 00000000..747961d9 --- /dev/null +++ b/backend/routers/user_consent.py @@ -0,0 +1,138 @@ +from fastapi import APIRouter, HTTPException, status +from pydantic import BaseModel +#from ..mih_database import dbConnection +import mih_database +import mih_database.mihDbConnections +from mih_database.mihDbObjects import UserConsent +from sqlalchemy import desc, or_ +from sqlalchemy.orm import Session +from sqlalchemy.sql import func +from sqlalchemy.exc import IntegrityError, SQLAlchemyError +#SuperToken Auth from front end +from supertokens_python.recipe.session.framework.fastapi import verify_session +from supertokens_python.recipe.session import SessionContainer +from fastapi import Depends +from datetime import datetime +import uuid + +router = APIRouter() + +class userConsentInsertRequest(BaseModel): + app_id: str + privacy_policy_accepted: datetime + terms_of_services_accepted: datetime + +class userConsentUpdateRequest(BaseModel): + app_id: str + privacy_policy_accepted: datetime + terms_of_services_accepted: datetime + +@router.get("/user-consent/user/{app_id}", tags=["User Consent"]) +async def get_user_consent(app_id: str, session: SessionContainer = Depends(verify_session())): + dbEngine = mih_database.mihDbConnections.dbAllConnect() + dbSession = Session(dbEngine) + try: + queryResults = dbSession.query(UserConsent).\ + filter(UserConsent.app_id == app_id).\ + first() + if queryResults: + return { + "idUserConsent": queryResults.iduser_consent, + "app_id": queryResults.app_id, + "privacy_policy_accepted": queryResults.privacy_policy_accepted, + "terms_of_services_accepted": queryResults.terms_of_services_accepted + } + else: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User Consent not found") + except HTTPException as http_exc: + # Re-raise HTTPException directly if it was raised within the try block + raise http_exc + except Exception as e: + print(f"An error occurred during the ORM query: {e}") + if dbSession.is_active: + dbSession.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Failed to retrieve records due to an internal server error." + ) + finally: + dbSession.close() + +@router.post("/user-consent/insert/", + tags=["User Consent"], + status_code=status.HTTP_201_CREATED) +async def insert_user_consent(itemRequest: userConsentInsertRequest, + session: SessionContainer = Depends(verify_session())): + dbEngine = mih_database.mihDbConnections.dbAllConnect() + dbSession = Session(dbEngine) + try: + newUserConsent = UserConsent( + app_id = itemRequest.app_id, + privacy_policy_accepted = itemRequest.privacy_policy_accepted, + terms_of_services_accepted = itemRequest.terms_of_services_accepted, + ) + dbSession.add(newUserConsent) + dbSession.commit() + dbSession.refresh(newUserConsent) + return {"message": "Successfully Created file Record"} + except IntegrityError as e: + dbSession.rollback() + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, # 409 Conflict is often suitable for constraint errors + detail=f"Data integrity error: The provided data violates a database constraint. Details: {e.orig}" + ) from e + except SQLAlchemyError as e: + dbSession.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"A database error occurred during insertion. Details: {e.orig}" + ) from e + except Exception as e: + dbSession.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"An unexpected error occurred: {e}" + ) from e + finally: + dbSession.close() + +@router.put("/user-consent/update/", tags=["User Consent"]) +async def update_user_consent(itemRequest: userConsentUpdateRequest, + session: SessionContainer = Depends(verify_session())): + dbEngine = mih_database.mihDbConnections.dbAllConnect() + dbSession = Session(dbEngine) + # pp_accepted_dt = datetime.strptime(itemRequest.privacy_policy_accepted, "%Y-%m-%d %H:%M:%S") + # tos_accepted_dt = datetime.strptime(itemRequest.terms_of_services_accepted, "%Y-%m-%d %H:%M:%S") + try: + existing_consent = dbSession.query(UserConsent).filter(UserConsent.app_id == itemRequest.app_id).first() + if not existing_consent: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User Consent not found") + + existing_consent.privacy_policy_accepted = itemRequest.privacy_policy_accepted + existing_consent.terms_of_services_accepted = itemRequest.terms_of_services_accepted + + dbSession.commit() + return {"message": "Successfully Updated User Consent Record"} + except HTTPException as http_exc: + # Re-raise HTTPException directly if it was raised within the try block + raise http_exc + except IntegrityError as e: + dbSession.rollback() + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=f"Data integrity error: The provided data violates a database constraint. Details: {e.orig}" + ) from e + except SQLAlchemyError as e: + dbSession.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"A database error occurred during update. Details: {e.orig}" + ) from e + except Exception as e: + dbSession.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"An unexpected error occurred: {e}" + ) from e + finally: + dbSession.close() \ No newline at end of file diff --git a/backend/routers/users.py b/backend/routers/users.py index 60f0d1f2..5aede146 100644 --- a/backend/routers/users.py +++ b/backend/routers/users.py @@ -1,7 +1,12 @@ -from fastapi import APIRouter, HTTPException +from fastapi import APIRouter, HTTPException, status from pydantic import BaseModel #from ..mih_database import dbConnection import mih_database +import mih_database.mihDbConnections +from mih_database.mihDbObjects import User, Business, BusinessRating, BookmarkedBusiness +from sqlalchemy import desc, or_ +from sqlalchemy.orm import Session +from sqlalchemy.sql import func #SuperToken Auth from front end from supertokens_python.recipe.session.framework.fastapi import verify_session from supertokens_python.recipe.session import SessionContainer @@ -67,6 +72,26 @@ class userDeleteRequest(BaseModel): # return items[0] + +@router.get("/users/count/", tags=["MIH Users"]) +async def read_users_by_app_id(): #, session: SessionContainer = Depends(verify_session()) + dbEngine = mih_database.mihDbConnections.dbAllConnect() + dbSession = Session(dbEngine) + try: + queryResults = dbSession.query(func.count(User.app_id)).scalar() + response_data = {"count": queryResults} + return response_data + except Exception as e: + print(f"An error occurred during the ORM query: {e}") + if dbSession.is_active: + dbSession.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Failed to retrieve records due to an internal server error." + ) + finally: + dbSession.close() + # Get List of all files @router.get("/users/search/{search}", tags=["MIH Users"]) async def read_all_users(search: str, session: SessionContainer = Depends(verify_session())): #, session: SessionContainer = Depends(verify_session()) diff --git a/docker-compose.yml b/docker-compose.yml index dd58ff7c..23edff70 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -65,21 +65,21 @@ services: timeout: 5s retries: 5 #============== PHP My Admin ==================================================================== - phpmyadmin: - platform: linux/amd64 - image: phpmyadmin/phpmyadmin - container_name: MIH-phpmyadmin - environment: - PMA_HOST: mysqlDB - PMA_PORT: 3306 - PMA_ARBITRARY: - networks: - - MIH-network - restart: always - ports: - - 8081:80 - depends_on: - - mysqldb + # phpmyadmin: + # platform: linux/amd64 + # image: phpmyadmin/phpmyadmin + # container_name: MIH-phpmyadmin + # environment: + # PMA_HOST: mysqlDB + # PMA_PORT: 3306 + # PMA_ARBITRARY: + # networks: + # - MIH-network + # restart: always + # ports: + # - 8081:80 + # depends_on: + # - mysqldb #============== Nginx Proxy Server ==================================================================== nginx: container_name: nginx @@ -127,7 +127,8 @@ services: platform: linux/amd64 container_name: MIH-Minio hostname: minio - image: docker.io/bitnami/minio:2022 + # image: docker.io/bitnami/minio:2022 + image: minio/minio ports: - '9000:9000' - '9001:9001' @@ -138,6 +139,7 @@ services: MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PW} networks: - MIH-network + command: ["server", "/data", "--console-address", ":9001"] #============== MIH-Monitor Portainer ==================================================================== portainer: container_name: MIH-Monitor @@ -168,15 +170,15 @@ services: - OLLAMA_HOST=0.0.0.0 networks: - MIH-network - # === Added section for NVIDIA GPU acceleration === - runtime: nvidia - deploy: - resources: - reservations: - devices: - - driver: nvidia - count: all # or specify a number of GPUs - capabilities: [ gpu ] +# === Added section for NVIDIA GPU acceleration === +runtime: nvidia +deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all # or specify a number of GPUs + capabilities: [ gpu ] #============== Firebaase ==================================================================== # firebase: # container_name: MIH-firebase-emulator