diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..7c84ebb5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,87 @@ +# Miscellaneous +*.class +*.lock +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# Visual Studio Code related +.vscode/ + +# Flutter repo-specific +/bin/cache/ +/bin/mingit/ +/dev/benchmarks/mega_gallery/ +/dev/bots/.recipe_deps +/dev/bots/android_tools/ +/dev/docs/doc/ +/dev/docs/lib/ +/dev/docs/pubspec.yaml +/packages/flutter/coverage/ +version + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.packages +.pub-cache/ +.pub/ +build/ +flutter_*.png +linked_*.ds +unlinked.ds +unlinked_spec.ds + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages \ No newline at end of file diff --git a/.metadata b/.metadata new file mode 100644 index 00000000..82403edb --- /dev/null +++ b/.metadata @@ -0,0 +1,8 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: c7ea3ca377e909469c68f2ab878a5bc53d3cf66b + channel: beta diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 00000000..65b7315a --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,10 @@ +*.iml +*.class +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +GeneratedPluginRegistrant.java diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 00000000..d2d18a9f --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,59 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 27 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.nkust.ap.flutter" + minSdkVersion 16 + targetSdkVersion 27 + versionCode 301 + versionName "0.3.1" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + + 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 + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.1' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' +} + +apply plugin: 'com.google.gms.google-services' \ No newline at end of file diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 00000000..3131a3d1 --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,42 @@ +{ + "project_info": { + "project_number": "141403473068", + "firebase_url": "https://nkust-ap-flutter.firebaseio.com", + "project_id": "nkust-ap-flutter", + "storage_bucket": "nkust-ap-flutter.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:141403473068:android:65aa63de8e144164", + "android_client_info": { + "package_name": "com.nkust.ap.flutter" + } + }, + "oauth_client": [ + { + "client_id": "141403473068-v71akg1vi0v2ohdtqog3tln8ot678qo8.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyBBIKkKaOfI4nDmj_SN738yB_IUVEOZiAo" + } + ], + "services": { + "analytics_service": { + "status": 1 + }, + "appinvite_service": { + "status": 1, + "other_platform_oauth_client": [] + }, + "ads_service": { + "status": 2 + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..ef84dd60 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/com/nkust/nkustap/MainActivity.kt b/android/app/src/main/kotlin/com/nkust/nkustap/MainActivity.kt new file mode 100644 index 00000000..67b6e987 --- /dev/null +++ b/android/app/src/main/kotlin/com/nkust/nkustap/MainActivity.kt @@ -0,0 +1,13 @@ +package com.nkust.ap.flutter + +import android.os.Bundle + +import io.flutter.app.FlutterActivity +import io.flutter.plugins.GeneratedPluginRegistrant + +class MainActivity(): FlutterActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + GeneratedPluginRegistrant.registerWith(this) + } +} diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000..304732f8 --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..faa97da5 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..55f7aa31 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..83832ea5 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..724b75ee Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..bbb17c29 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..00fa4417 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 00000000..d8d5be4b --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,32 @@ +buildscript { + ext.kotlin_version = '1.2.71' + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.1.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'com.google.gms:google-services:3.1.2' + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 00000000..8bd86f68 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1 @@ +org.gradle.jvmargs=-Xmx1536M diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..9372d0f3 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 00000000..5a2f14fb --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,15 @@ +include ':app' + +def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() + +def plugins = new Properties() +def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') +if (pluginsFile.exists()) { + pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } +} + +plugins.each { name, path -> + def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() + include ":$name" + project(":$name").projectDir = pluginDirectory +} diff --git a/assets/images/ic_email.png b/assets/images/ic_email.png new file mode 100644 index 00000000..0bda5503 Binary files /dev/null and b/assets/images/ic_email.png differ diff --git a/assets/images/ic_fb.png b/assets/images/ic_fb.png new file mode 100644 index 00000000..fbead9b7 Binary files /dev/null and b/assets/images/ic_fb.png differ diff --git a/assets/images/ic_github.png b/assets/images/ic_github.png new file mode 100644 index 00000000..4da7bcae Binary files /dev/null and b/assets/images/ic_github.png differ diff --git a/assets/images/kuas_itc.png b/assets/images/kuas_itc.png new file mode 100644 index 00000000..33286bc9 Binary files /dev/null and b/assets/images/kuas_itc.png differ diff --git a/assets/images/kuasap.png b/assets/images/kuasap.png new file mode 100644 index 00000000..d8989fbb Binary files /dev/null and b/assets/images/kuasap.png differ diff --git a/assets/images/kuasap2.png b/assets/images/kuasap2.png new file mode 100644 index 00000000..dbee46ee Binary files /dev/null and b/assets/images/kuasap2.png differ diff --git a/assets/images/kuasap3.png b/assets/images/kuasap3.png new file mode 100644 index 00000000..c1e69dcc Binary files /dev/null and b/assets/images/kuasap3.png differ diff --git a/assets/images/kuasap_text.png b/assets/images/kuasap_text.png new file mode 100644 index 00000000..fdd348b4 Binary files /dev/null and b/assets/images/kuasap_text.png differ diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 00000000..79cc4da8 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,45 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/app.flx +/Flutter/app.zip +/Flutter/flutter_assets/ +/Flutter/App.framework +/Flutter/Flutter.framework +/Flutter/Generated.xcconfig +/ServiceDefinitions.json + +Pods/ +.symlinks/ diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000..9367d483 --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 8.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000..e8efba11 --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000..399e9340 --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 00000000..2dfb5019 --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,65 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +def parse_KV_file(file, separator='=') + file_abs_path = File.expand_path(file) + if !File.exists? file_abs_path + return []; + end + pods_ary = [] + skip_line_start_symbols = ["#", "/"] + File.foreach(file_abs_path) { |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + pods_ary.push({:name => podname, :path => podpath}); + else + puts "Invalid plugin specification: #{line}" + end + } + return pods_ary +end + +target 'Runner' do + use_frameworks! + + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + system('rm -rf .symlinks') + system('mkdir -p .symlinks/plugins') + + # Flutter Pods + generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') + if generated_xcode_build_settings.empty? + puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." + end + generated_xcode_build_settings.map { |p| + if p[:name] == 'FLUTTER_FRAMEWORK_DIR' + symlink = File.join('.symlinks', 'flutter') + File.symlink(File.dirname(p[:path]), symlink) + pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) + end + } + + # Plugin Pods + plugin_pods = parse_KV_file('../.flutter-plugins') + plugin_pods.map { |p| + symlink = File.join('.symlinks', 'plugins', p[:name]) + File.symlink(p[:path], symlink) + pod p[:name], :path => File.join(symlink, 'ios') + } +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + end + end +end diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..6549f79c --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,532 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 16830D86CF5AEAFD81C84EBD /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46B9B6A6AB1EB3B198547386 /* Pods_Runner.framework */; }; + 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + A45559A0217B2AE100687009 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = A455599F217B2AE100687009 /* GoogleService-Info.plist */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; + 46B9B6A6AB1EB3B198547386 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A455599F217B2AE100687009 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + A45559A5217C871600687009 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, + 16830D86CF5AEAFD81C84EBD /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 34AB1F91613D54B84C45D9F7 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 46B9B6A6AB1EB3B198547386 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 6F25BA31EB4E6864CAF4D56F /* Pods */ = { + isa = PBXGroup; + children = ( + ); + name = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 2D5378251FAA1A9400D5DBA9 /* flutter_assets */, + 3B80C3931E831B6300D905FE /* App.framework */, + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEBA1CF902C7004384FC /* Flutter.framework */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 6F25BA31EB4E6864CAF4D56F /* Pods */, + 34AB1F91613D54B84C45D9F7 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + A45559A5217C871600687009 /* Runner.entitlements */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + A455599F217B2AE100687009 /* GoogleService-Info.plist */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 3BB8511C87A2DDE3F5418E48 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 206ACDB7DFB38C569AEBD820 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1000; + ORGANIZATIONNAME = "The Chromium Authors"; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + DevelopmentTeam = 75S866VSMR; + LastSwiftMigration = 0910; + SystemCapabilities = { + com.apple.Push = { + enabled = 1; + }; + }; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + A45559A0217B2AE100687009 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 206ACDB7DFB38C569AEBD820 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", + "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework", + "${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework", + "${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework", + "${BUILT_PRODUCTS_DIR}/fluttertoast/fluttertoast.framework", + "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", + "${BUILT_PRODUCTS_DIR}/shared_preferences/shared_preferences.framework", + "${BUILT_PRODUCTS_DIR}/url_launcher/url_launcher.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Protobuf.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/fluttertoast.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + }; + 3BB8511C87A2DDE3F5418E48 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 75S866VSMR; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.nkust.ap.flutter; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 75S866VSMR; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.nkust.ap.flutter; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..1d526a16 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..ba1a9822 --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..21a3cc14 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..949b6789 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + BuildSystemType + Original + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000..71cc41e3 --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d36b1fab --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 00000000..397afa26 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 00000000..5b1baf51 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 00000000..bfe03fc9 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 00000000..9f5e8bf2 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 00000000..8ecfbc8f Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 00000000..2b1859f0 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 00000000..5b1baf51 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 00000000..f95396df Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 00000000..46e2ab4c Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png new file mode 100644 index 00000000..3d588678 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png new file mode 100644 index 00000000..f5513778 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png new file mode 100644 index 00000000..bfe03fc9 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 00000000..46e2ab4c Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 00000000..cd1842e0 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png new file mode 100644 index 00000000..635da1ac Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png new file mode 100644 index 00000000..1dddbe1f Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 00000000..59f42cc2 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 00000000..58b2c263 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png new file mode 100644 index 00000000..fc6b1212 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 00000000..cba19f6d Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png new file mode 100644 index 00000000..019d52d6 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png new file mode 100644 index 00000000..0d911133 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png new file mode 100644 index 00000000..e9a1b63e Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 00000000..0bedcf2f --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000..89c2725b --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..f2e259c7 --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f3c28516 --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/GoogleService-Info.plist b/ios/Runner/GoogleService-Info.plist new file mode 100644 index 00000000..66c63950 --- /dev/null +++ b/ios/Runner/GoogleService-Info.plist @@ -0,0 +1,40 @@ + + + + + AD_UNIT_ID_FOR_BANNER_TEST + ca-app-pub-3940256099942544/2934735716 + AD_UNIT_ID_FOR_INTERSTITIAL_TEST + ca-app-pub-3940256099942544/4411468910 + CLIENT_ID + 141403473068-b9ugkddmql2nefsuggoc9unte0qm23eb.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.141403473068-b9ugkddmql2nefsuggoc9unte0qm23eb + API_KEY + AIzaSyBCAbWwVkQZEcy-x3_NhGqXxjcTdtB6Kj4 + GCM_SENDER_ID + 141403473068 + PLIST_VERSION + 1 + BUNDLE_ID + com.nkust.ap.flutter + PROJECT_ID + nkust-ap-flutter + STORAGE_BUCKET + nkust-ap-flutter.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:141403473068:ios:65aa63de8e144164 + DATABASE_URL + https://nkust-ap-flutter.firebaseio.com + + \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 00000000..502763f6 --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,50 @@ + + + + + CFBundleDevelopmentRegion + + en + zh + + CFBundleDisplayName + 高科校務通 + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + nkust_ap + CFBundlePackageType + APPL + CFBundleShortVersionString + 0.3.1 + CFBundleSignature + ???? + CFBundleVersion + 301 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..7335fdf9 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" \ No newline at end of file diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements new file mode 100644 index 00000000..903def2a --- /dev/null +++ b/ios/Runner/Runner.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/lib/api/helper.dart b/lib/api/helper.dart new file mode 100644 index 00000000..a4fddf5d --- /dev/null +++ b/lib/api/helper.dart @@ -0,0 +1,215 @@ +import 'dart:async'; +import 'package:dio/dio.dart'; +import 'dart:convert'; +import 'package:intl/intl.dart'; + +const HOST = "kuas.grd.idv.tw"; +const PORT = '14769'; + +const VERSION = 'latest'; + +class Helper { + static Helper _instance; + static Options options; + static Dio dio; + + static Helper get instance { + if (_instance == null) { + _instance = new Helper(); + } + return _instance; + } + + Helper() { + options = new Options( + baseUrl: 'https://$HOST:$PORT', + connectTimeout: 20000, + receiveTimeout: 10000, + ); + dio = new Dio(options); + } + + login(String username, String password) async { + dio.options.headers = _createBasicAuth(username, password); + try { + var response = await dio.get("/$VERSION/token"); + return response.data; + } on DioError catch (e) { + if (e.response != null) { + print(e.response.data); + print(e.response.headers); + print(e.response.request.headers); + print(e.response.request.baseUrl); + } else { + print(e.message); + } + return null; + } + } + + Future getAllNews() async { + try { + var response = await dio.get("/$VERSION/news/all"); + return response; + } on DioError catch (e) { + if (e.response != null) { + print(e.response.data); + } else { + print(e.message); + } + return null; + } + } + + Future getUsersInfo() async { + try { + var response = await dio.get("/$VERSION/ap/users/info"); + return response; + } on DioError catch (e) { + if (e.response != null) { + print(e.response.data); + } else { + print(e.message); + } + return null; + } + } + + Future getUsersPicture() async { + try { + var response = await dio.get("/$VERSION/ap/users/picture"); + return response; + } on DioError catch (e) { + if (e.response != null) { + print(e.response.data); + } else { + print(e.message); + } + return null; + } + } + + Future getSemester() async { + try { + var response = await dio.get("/$VERSION/ap/semester"); + return response; + } on DioError catch (e) { + if (e.response != null) { + print(e.response.data); + } else { + print(e.message); + } + return null; + } + } + + Future getScore(String year, String semester) async { + try { + var response = await dio + .get("/$VERSION/ap/users/scores/" + year + "/" + semester); + return response; + } on DioError catch (e) { + if (e.response != null) { + print(e.response.data); + } else { + print(e.message); + } + return null; + } + } + + Future getCourseTables(String year, String semester) async { + try { + var response = await dio + .get("/$VERSION/ap/users/coursetables/" + year + "/" + semester); + return response; + } on DioError catch (e) { + if (e.response != null) { + print(e.response.data); + } else { + print(e.message); + } + return null; + } + } + + Future getBusTimeTables(DateTime dateTime) async { + var formatter = new DateFormat('yyyy-MM-dd'); + var date = formatter.format(dateTime); + try { + var response = await dio.get("/$VERSION/bus/timetables?date=$date"); + return response; + } on DioError catch (e) { + if (e.response != null) { + print(e.response.data); + } else { + print(e.message); + } + return null; + } + } + + Future getBusReservations() async { + try { + var response = await dio.get("/$VERSION/bus/reservations"); + return response; + } on DioError catch (e) { + if (e.response != null) { + print(e.response.data); + } else { + print(e.message); + } + return null; + } + } + + Future bookingBusReservation(String busId) async { + try { + var response = await dio.put("/$VERSION/bus/reservations/$busId"); + return response; + } on DioError catch (e) { + if (e.response != null) { + print(e.response.data); + } else { + print(e.message); + } + return null; + } + } + + Future cancelBusReservation(String cancelKey) async { + try { + var response = await dio.delete("/$VERSION/bus/reservations/$cancelKey"); + return response; + } on DioError catch (e) { + if (e.response != null) { + print(e.response.data); + } else { + print(e.message); + } + return null; + } + } + + Future getNotifications(int page) async { + try { + var response = await dio.get("/$VERSION/notifications/$page"); + return response; + } on DioError catch (e) { + if (e.response != null) { + print(e.response.data); + } else { + print(e.message); + } + return null; + } + } + + _createBasicAuth(String username, String password) { + var text = username + ":" + password; + var encoded = utf8.encode(text); + return { + "Authorization": "Basic " + base64.encode(encoded.toList(growable: false)) + }; + } +} diff --git a/lib/config/constants.dart b/lib/config/constants.dart new file mode 100644 index 00000000..756d1097 --- /dev/null +++ b/lib/config/constants.dart @@ -0,0 +1,14 @@ +class Constants { + static const PREF_FIRST_ENTER_APP = "pref_first_enter_app"; + static const PREF_REMEMBER_PASSWORD = "pref_remember_password"; + static const PREF_USERNAME = "pref_username"; + static const PREF_PASSWORD = "pref_password"; + static const PREF_NOTIFY_COURSE = "pref_notify_course"; + static const PREF_NOTIFY_BUS = "pref_notify_bus"; + static const PREF_DISPLAY_PICTURE = "pref_display_picture"; + static const PREF_VIBRATE_COURSE = "pref_vibrate_course"; + + static const SCHEDULE_DATA = "schedule_data"; + + static const APP_VERSION = "v0.3.1"; +} diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 00000000..0de71d0b --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:nkust_ap/res/string.dart'; +import 'package:nkust_ap/pages/page.dart'; +import 'package:firebase_analytics/firebase_analytics.dart'; +import 'package:firebase_analytics/observer.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:nkust_ap/utils/app_localizations.dart'; +import 'package:nkust_ap/res/resource.dart' as Resource; +import 'package:firebase_messaging/firebase_messaging.dart'; + +void main() => runApp(new MyApp()); + +class MyApp extends StatelessWidget { + final FirebaseAnalytics analytics = new FirebaseAnalytics(); + final FirebaseMessaging _firebaseMessaging = FirebaseMessaging(); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + //_firebaseMessaging.requestNotificationPermissions(); + return new MaterialApp( + localeResolutionCallback: + (Locale locale, Iterable supportedLocales) { + return locale; + }, + title: Strings.app_name, + debugShowCheckedModeBanner: false, + routes: { + Navigator.defaultRouteName: (context) => LoginPage(), + LoginPage.routerName: (BuildContext context) => LoginPage(), + HomePage.routerName: (BuildContext context) => HomePage(), + CoursePage.routerName: (BuildContext context) => CoursePage(), + BusPage.routerName: (BuildContext context) => BusPage(), + ScorePage.routerName: (BuildContext context) => ScorePage(), + SchoolInfoPage.routerName: (BuildContext context) => SchoolInfoPage(), + SettingPage.routerName: (BuildContext context) => SettingPage(), + AboutUsPage.routerName: (BuildContext context) => AboutUsPage(), + MyLicencePage.routerName: (BuildContext context) => MyLicencePage(), + }, + theme: new ThemeData( + hintColor: Colors.white, + accentColor: Colors.blue, + inputDecorationTheme: new InputDecorationTheme( + labelStyle: new TextStyle(color: Colors.white), + border: new UnderlineInputBorder( + borderSide: new BorderSide(color: Colors.white)), + ), + ), + navigatorObservers: [ + new FirebaseAnalyticsObserver(analytics: analytics), + ], + localizationsDelegates: [ + const AppLocalizationsDelegate(), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + supportedLocales: [ + const Locale('en', 'US'), // English + const Locale('zh', 'TW'), // Hebrew + // ... other locales the app supports + ], + ); + } +} diff --git a/lib/models/bus_data.dart b/lib/models/bus_data.dart new file mode 100644 index 00000000..cf91c6d2 --- /dev/null +++ b/lib/models/bus_data.dart @@ -0,0 +1,148 @@ +import 'package:flutter/material.dart'; +import 'package:nkust_ap/res/resource.dart' as Resource; +import 'package:intl/intl.dart'; +import 'package:intl/date_symbol_data_local.dart'; + +class BusData { + String date; + List timetable; + + BusData({ + this.date, + this.timetable, + }); + + static BusData fromJson(Map json) { + return BusData( + date: json['date'], + timetable: BusTime.toList(json["timetable"]), + ); + } + + Map toJson() => { + 'date': date, + 'timetable': timetable, + }; +} + +class BusTime { + String endEnrollDateTime; + String runDateTime; + String time; + String endStation; + String busId; + String reserveCount; + String limitCount; + int isReserve; + String specialTrain; + String specialTrainRemark; + int cancelKey; + + BusTime({ + this.endEnrollDateTime, + this.runDateTime, + this.time, + this.endStation, + this.busId, + this.reserveCount, + this.limitCount, + this.isReserve, + this.specialTrain, + this.specialTrainRemark, + this.cancelKey, + }); + + String getSpecialTrainTitle() { + switch (specialTrain) { + case "1": + return "特殊班次"; + case "2": + return "試辦車次"; + default: + return ""; + } + } + + String getSpecialTrainRemark() { + print(specialTrainRemark); + if (specialTrainRemark.length == 0) + return ""; + else + return "${specialTrainRemark.replaceAll("
", "\n")}\n"; + } + + static List toList(List jsonArray) { + List list = []; + for (var item in (jsonArray ?? [])) list.add(BusTime.fromJson(item)); + return list; + } + + static BusTime fromJson(Map json) { + return BusTime( + endEnrollDateTime: json['EndEnrollDateTime'], + runDateTime: json['runDateTime'], + time: json['Time'], + endStation: json['endStation'], + busId: json['busId'], + reserveCount: json['reserveCount'], + limitCount: json['limitCount'], + isReserve: json['isReserve'], + specialTrain: json['SpecialTrain'], + specialTrainRemark: json['SpecialTrainRemark'], + cancelKey: json['cancelKey'], + ); + } + + Map toJson() => { + 'EndEnrollDateTime': endEnrollDateTime, + 'runDateTime': runDateTime, + 'Time': time, + 'endStation': endStation, + 'busId': busId, + 'reserveCount': reserveCount, + 'limitCount': limitCount, + 'isReserve': isReserve, + 'SpecialTrain': specialTrain, + 'SpecialTrainRemark': specialTrainRemark, + 'cancelKey': cancelKey, + }; + + bool hasReserve() { + var now = new DateTime.now(); + initializeDateFormatting(); + var formatter = new DateFormat('yyyy-MM-dd HH:mm'); + var endEnrollDateTime = formatter.parse(this.endEnrollDateTime); + //print(endEnrollDateTime); + return now.millisecondsSinceEpoch <= + endEnrollDateTime.millisecondsSinceEpoch; + } + + Color getColorState() { + return isReserve == 1 + ? Resource.Colors.blue + : hasReserve() ? Resource.Colors.grey : Colors.grey; + } + + String getReserveState() { + return isReserve == 1 ? "已預約" : hasReserve() ? "預約" : "無法預約"; + } + + String getDate() { + initializeDateFormatting(); + var formatter = new DateFormat('yyyy-MM-dd HH:mm'); + var formatterTime = new DateFormat('yyyy-MM-dd'); + var time = formatter.parse(this.runDateTime); + return formatterTime.format(time); + } + + String getStart() { + switch (endStation) { + case "建工": + return "燕巢"; + case "燕巢": + return "建工"; + default: + return "未知"; + } + } +} diff --git a/lib/models/bus_reservations_data.dart b/lib/models/bus_reservations_data.dart new file mode 100644 index 00000000..e4811376 --- /dev/null +++ b/lib/models/bus_reservations_data.dart @@ -0,0 +1,98 @@ +import 'package:flutter/material.dart'; +import 'package:nkust_ap/res/resource.dart' as Resource; +import 'package:intl/intl.dart'; +import 'package:intl/date_symbol_data_local.dart'; + +class BusReservationsData { + List reservations; + + BusReservationsData({ + this.reservations, + }); + + static BusReservationsData fromJson(Map json) { + return BusReservationsData( + reservations: BusReservation.toList(json['reservation']), + ); + } + + Map toJson() => { + 'reservation': reservations, + }; +} + +class BusReservation { + String time; + String endTime; + String cancelKey; + String end; + + BusReservation({ + this.time, + this.endTime, + this.cancelKey, + this.end, + }); + + static BusReservation fromJson(Map json) { + return BusReservation( + time: json['time'], + endTime: json['endTime'], + cancelKey: json['cancelKey'], + end: json['end'], + ); + } + + Map toJson() => { + 'time': time, + 'endTime': endTime, + 'cancelKey': cancelKey, + 'end': end, + }; + + static List toList(List jsonArray) { + List list = []; + for (var item in (jsonArray ?? [])) list.add(BusReservation.fromJson(item)); + return list; + } + + Color getColorState() { + return Resource.Colors.grey; + } + + String getDate() { + initializeDateFormatting(); + var formatter = new DateFormat('yyyy-MM-dd HH:mm'); + var formatterTime = new DateFormat('yyyy-MM-dd'); + var time = formatter.parse(this.time); + return formatterTime.format(time); + } + + String getTime() { + initializeDateFormatting(); + var formatter = new DateFormat('yyyy-MM-dd HH:mm'); + var formatterTime = new DateFormat('HH:mm'); + var time = formatter.parse(this.time); + return formatterTime.format(time); + } + + String getStart() { + switch (end) { + case "建工": + return "燕巢"; + case "燕巢": + return "建工"; + default: + return "未知"; + } + } + + bool canCancel() { + var now = new DateTime.now(); + initializeDateFormatting(); + var formatter = new DateFormat('yyyy-MM-dd HH:mm'); + var endEnrollDateTime = formatter.parse(this.endTime); + return now.millisecondsSinceEpoch < + endEnrollDateTime.millisecondsSinceEpoch; + } +} diff --git a/lib/models/course_data.dart b/lib/models/course_data.dart new file mode 100644 index 00000000..7b93f0cb --- /dev/null +++ b/lib/models/course_data.dart @@ -0,0 +1,113 @@ +class CourseData { + int status; + String messages; + CourseTables courseTables; + + CourseData({ + this.status, + this.messages, + this.courseTables, + }); + + static CourseData fromJson(Map json) { + return CourseData( + status: json['status'], + messages: json['messages'], + courseTables: CourseTables.fromJson(json['coursetables']), + ); + } + + Map toJson() => { + 'status': status, + 'messages': messages, + 'coursetables': courseTables, + }; +} + +class CourseTables { + List tuesday; + List friday; + List saturday; + List thursday; + List monday; + List wednesday; + List timeCode; + + CourseTables({ + this.tuesday, + this.friday, + this.saturday, + this.thursday, + this.monday, + this.wednesday, + this.timeCode, + }); + + static CourseTables fromJson(Map json) { + return CourseTables( + tuesday: Course.toList(json['Tuesday']), + friday: Course.toList(json['Friday']), + saturday: Course.toList(json['Saturday']), + thursday: Course.toList(json['Thursday']), + wednesday: Course.toList(json['Wednesday']), + monday: Course.toList(json['Monday']), + timeCode: List.from(json['timecode'] ?? []), + ); + } + + Map toJson() => { + 'Tuesday': tuesday, + 'Friday': friday, + 'Saturday': saturday, + 'Thursday': thursday, + 'Monday': monday, + 'Wednesday': wednesday, + 'timecode': timeCode, + }; +} + +class Course { + String title; + String startTime; + String endTime; + String weekday; + String section; + String building; + String room; + List instructors; + + Course({ + this.title, + this.startTime, + this.endTime, + this.weekday, + this.section, + this.building, + this.room, + this.instructors, + }); + + static List toList(List jsonArray) { + List list = []; + for (var item in (jsonArray ?? [])) list.add(Course.fromJson(item)); + return list; + } + + static Course fromJson(Map json) { + return Course( + title: json['title'], + startTime: json['date']["start_time"], + endTime: json['date']["end_time"], + weekday: json['date']["weekday"], + section: json['date']["section"], + building: json['location']["building"], + room: json['location']["room"], + instructors: List.from(json['instructors']), + ); + } + + Map toJson() => { + 'title': title, + 'instructors': instructors, + }; +} \ No newline at end of file diff --git a/lib/models/models.dart b/lib/models/models.dart new file mode 100644 index 00000000..45e2b258 --- /dev/null +++ b/lib/models/models.dart @@ -0,0 +1,12 @@ + + +export 'package:nkust_ap/models/user_info.dart'; +export 'package:nkust_ap/models/semester_data.dart'; +export 'package:nkust_ap/models/score_data.dart'; +export 'package:nkust_ap/models/course_data.dart'; +export 'package:nkust_ap/models/bus_data.dart'; +export 'package:nkust_ap/models/bus_reservations_data.dart'; +export 'package:nkust_ap/models/news.dart'; +export 'package:nkust_ap/models/notification_data.dart'; +export 'package:nkust_ap/models/phone_model.dart'; +export 'package:nkust_ap/models/schedule_data.dart'; \ No newline at end of file diff --git a/lib/models/news.dart b/lib/models/news.dart new file mode 100644 index 00000000..15727af0 --- /dev/null +++ b/lib/models/news.dart @@ -0,0 +1,35 @@ +class News{ + String title; + int weight; + String image; + String url; + String content; + + News({ + this.title,this.weight,this.image,this.url,this.content, + }); + + static News fromJson(Map json){ + return News( + title: json['news_title'], + weight: json['news_weight'], + image: json['news_image'], + url: json['news_url'], + content: json['news_content'], + ); + } + + Map toJson() => { + 'news_title': title, + 'news_weight': weight, + 'news_image': image, + 'news_url': url, + 'news_content': content, + }; + + static List toList(List jsonArray) { + List list = []; + for (var item in (jsonArray ?? [])) list.add(News.fromJson(item)); + return list; + } +} \ No newline at end of file diff --git a/lib/models/notification_data.dart b/lib/models/notification_data.dart new file mode 100644 index 00000000..92e8fa58 --- /dev/null +++ b/lib/models/notification_data.dart @@ -0,0 +1,79 @@ +class NotificationData { + int page; + List notifications; + + NotificationData({ + this.page, + this.notifications, + }); + + static NotificationData fromJson(Map json) { + return NotificationData( + page: json['page'], + notifications: NotificationModel.toList(json['notification']), + ); + } + + Map toJson() => { + 'page': page, + 'notification': notifications, + }; +} + +class NotificationModel { + String link; + Info info; + + NotificationModel({ + this.link, + this.info, + }); + + static NotificationModel fromJson(Map json) { + return NotificationModel( + link: json['link'], + info: Info.fromJson(json['info']), + ); + } + + Map toJson() => { + 'link': link, + 'info': info, + }; + + static List toList(List jsonArray) { + List list = []; + for (var item in (jsonArray ?? [])) list.add(NotificationModel.fromJson(item)); + return list; + } +} + +class Info { + String id; + String title; + String department; + String date; + + Info({ + this.id, + this.title, + this.department, + this.date, + }); + + static Info fromJson(Map json) { + return Info( + id: json['id'], + title: json['title'], + department: json['department'], + date: json['date'], + ); + } + + Map toJson() => { + 'id': id, + 'title': title, + 'department': department, + 'date': date, + }; +} diff --git a/lib/models/phone_model.dart b/lib/models/phone_model.dart new file mode 100644 index 00000000..80766071 --- /dev/null +++ b/lib/models/phone_model.dart @@ -0,0 +1,9 @@ +class PhoneModel { + String name; + String number; + + PhoneModel( + this.name, + this.number, + ); +} diff --git a/lib/models/schedule_data.dart b/lib/models/schedule_data.dart new file mode 100644 index 00000000..7a2577fe --- /dev/null +++ b/lib/models/schedule_data.dart @@ -0,0 +1,27 @@ +class ScheduleData { + String week; + List events; + + ScheduleData({ + this.week, + this.events, + }); + + static ScheduleData fromJson(Map json) { + return ScheduleData( + week: json['week'], + events: List.from(json['events']), + ); + } + + Map toJson() => { + 'week': week, + 'events': events, + }; + + static List toList(List jsonArray) { + List list = []; + for (var item in (jsonArray ?? [])) list.add(ScheduleData.fromJson(item)); + return list; + } +} diff --git a/lib/models/score_data.dart b/lib/models/score_data.dart new file mode 100644 index 00000000..39823a25 --- /dev/null +++ b/lib/models/score_data.dart @@ -0,0 +1,117 @@ + +class ScoreData{ + int status; + String messages; + Content content; + + ScoreData({ + this.status,this.messages,this.content, + }); + + static ScoreData fromJson(Map json){ + return ScoreData( + status: json['status'], + messages: json['messages'], + content: Content.fromJson(json['scores']), + ); + } + + Map toJson() => { + 'status': status, + 'messages': messages, + 'scores': content, + }; +} + +class Content{ + List scores; + Detail detail; + + Content({ + this.scores,this.detail, + }); + + static Content fromJson(Map json){ + return Content( + scores:Score.toList( json['scores']), + detail: Detail.fromJson( json['detail']), + ); + } + + Map toJson() => { + 'scores': scores, + 'detail': detail, + }; +} + +class Score{ + String title; + String units; + String hours; + String required; + String at; + String middleScore; + String finalScore; + String remark; + + Score({ + this.title,this.units,this.hours,this.required,this.at,this.middleScore,this.finalScore,this.remark, + }); + + static List toList(List jsonArray) { + List list = []; + for (var item in (jsonArray ?? [])) list.add(Score.fromJson(item)); + return list; + } + + static Score fromJson(Map json){ + return Score( + title: json['title'], + units: json['units'], + hours: json['hours'], + required: json['required'], + at: json['at'], + middleScore: json['middle_score'], + finalScore: json['final_score'], + remark: json['remark'], + ); + } + + Map toJson() => { + 'title': title, + 'units': units, + 'hours': hours, + 'required': required, + 'at': at, + 'middle_score': middleScore, + 'final_score': finalScore, + 'remark': remark, + }; +} + +class Detail{ + double conduct; + double average; + String classRank; + double classPercentage; + + Detail({ + this.conduct,this.average,this.classRank,this.classPercentage, + }); + + static Detail fromJson(Map json){ + return Detail( + conduct: json['conduct'], + average: json['average'], + classRank: json['class_rank'], + classPercentage: json['class_percentage'], + ); + } + + Map toJson() => { + 'conduct': conduct, + 'average': average, + 'class_rank': classRank, + 'class_percentage': classPercentage, + }; +} \ No newline at end of file diff --git a/lib/models/semester_data.dart b/lib/models/semester_data.dart new file mode 100644 index 00000000..9577794b --- /dev/null +++ b/lib/models/semester_data.dart @@ -0,0 +1,53 @@ +class SemesterData { + List semesters; + Semester defaultSemester; + + SemesterData({ + this.semesters, + this.defaultSemester, + }); + + static SemesterData fromJson(Map json) { + return SemesterData( + semesters: Semester.toList(json['semester']), + defaultSemester:Semester.fromJson(json['default']) , + ); + } + + Map toJson() => { + 'semester': semesters, + 'default': defaultSemester, + }; +} + +class Semester { + String value; + int selected; + String text; + + Semester({ + this.value, + this.selected, + this.text, + }); + + static List toList(List jsonArray) { + List list = []; + for (var item in (jsonArray ?? [])) list.add(Semester.fromJson(item)); + return list; + } + + static Semester fromJson(Map json) { + return Semester( + value: json['value'], + selected: json['selected'], + text: json['text'], + ); + } + + Map toJson() => { + 'value': value, + 'selected': selected, + 'text': text, + }; +} diff --git a/lib/models/state.dart b/lib/models/state.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/models/user_info.dart b/lib/models/user_info.dart new file mode 100644 index 00000000..24c48ac6 --- /dev/null +++ b/lib/models/user_info.dart @@ -0,0 +1,37 @@ +class UserInfo { + String educationSystem; + String department; + String className; + String id; + String nameCht; + String nameEng; + + UserInfo({ + this.educationSystem, + this.department, + this.className, + this.id, + this.nameCht, + this.nameEng, + }); + + static UserInfo fromJson(Map json) { + return UserInfo( + educationSystem: json['education_system'], + department: json['department'], + className: json['class'], + id: json['student_id'], + nameCht: json['student_name_cht'], + nameEng: json['student_name_eng'], + ); + } + + Map toJson() => { + 'education_system': educationSystem, + 'department': department, + 'class': className, + 'student_id': id, + 'student_name_cht': nameCht, + 'student_name_eng': nameEng, + }; +} diff --git a/lib/pages/home/about/about_us_page.dart b/lib/pages/home/about/about_us_page.dart new file mode 100644 index 00000000..fd237f0d --- /dev/null +++ b/lib/pages/home/about/about_us_page.dart @@ -0,0 +1,177 @@ +import 'package:flutter/material.dart'; +import 'package:nkust_ap/res/resource.dart' as Resource; +import 'package:nkust_ap/api/helper.dart'; +import 'package:nkust_ap/models/models.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:nkust_ap/pages/page.dart'; +import 'package:nkust_ap/utils/app_localizations.dart'; +import 'package:nkust_ap/utils/utils.dart'; + +class AboutUsPageRoute extends MaterialPageRoute { + AboutUsPageRoute() + : super(builder: (BuildContext context) => new AboutUsPage()); + + @override + Widget buildPage(BuildContext context, Animation animation, + Animation secondaryAnimation) { + return new FadeTransition(opacity: animation, child: new AboutUsPage()); + } +} + +class AboutUsPage extends StatefulWidget { + static const String routerName = "/aboutUs"; + + @override + AboutUsPageState createState() => new AboutUsPageState(); +} + +class AboutUsPageState extends State + with SingleTickerProviderStateMixin { + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + var app = AppLocalizations.of(context); + return new Scaffold( + body: NestedScrollView( + headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { + return [ + SliverAppBar( + expandedHeight: 200.0, + floating: false, + pinned: true, + title: new Text(AppLocalizations.of(context).about), + actions: [ + IconButton( + icon: Icon(Icons.code), + onPressed: () { + Navigator.of(context).pushNamed(MyLicencePage.routerName); + }) + ], + backgroundColor: Resource.Colors.blue, + flexibleSpace: FlexibleSpaceBar( + background: Image.asset( + "assets/images/kuasap3.png", + fit: BoxFit.cover, + ), + ), + ), + ]; + }, + body: ListView( + children: [ + _item(app.aboutAuthorTitle, app.aboutAuthorContent), + _item(app.about, app.aboutUsContent), + _item(app.aboutRecruitTitle, app.aboutRecruitContent), + Stack( + children: [ + _item(app.aboutItcTitle, app.aboutItcContent), + Padding( + padding: + EdgeInsets.symmetric(vertical: 20.0, horizontal: 26.0), + child: Align( + alignment: Alignment.topRight, + child: Image.asset( + "assets/images/kuas_itc.png", + width: 64.0, + fit: BoxFit.cover, + ), + ), + ) + ], + ), + Card( + margin: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), + elevation: 4.0, + child: Container( + padding: EdgeInsets.only( + top: 24.0, left: 16.0, bottom: 16.0, right: 16.0), + width: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + app.aboutContactUsTitle, + style: TextStyle(fontSize: 18.0), + ), + SizedBox( + height: 4.0, + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: Image.asset("assets/images/ic_fb.png"), + onPressed: () { + Utils.launchUrl('https://www.facebook.com/NKUST.ITC/'); + }, + iconSize: 48.0, + ), + IconButton( + icon: Image.asset("assets/images/ic_github.png"), + onPressed: () { + Utils.launchUrl('https://github.com/NKUST-ITC'); + }, + iconSize: 48.0, + ), + IconButton( + icon: Image.asset("assets/images/ic_email.png"), + onPressed: () { + Utils.launchUrl('mailto:abc873693@gmail.com'); + }, + iconSize: 48.0, + ), + ], + ), + ), + ], + ), + ), + ), + _item(app.aboutOpenSourceTitle, app.aboutOpenSourceContent), + ], + ), + ), + ); + } + + _item(String text, String subText) => Card( + margin: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), + elevation: 4.0, + child: Container( + padding: + EdgeInsets.only(top: 24.0, left: 16.0, bottom: 16.0, right: 16.0), + width: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + text, + style: TextStyle(fontSize: 18.0), + ), + SizedBox( + height: 4.0, + ), + RichText( + text: TextSpan( + style: TextStyle(fontSize: 14.0, color: Resource.Colors.grey), + text: subText, + ), + ), + ], + ), + ), + ); +} diff --git a/lib/pages/home/about/licence_page.dart b/lib/pages/home/about/licence_page.dart new file mode 100644 index 00000000..fa5e8f13 --- /dev/null +++ b/lib/pages/home/about/licence_page.dart @@ -0,0 +1,14 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter_calendar/flutter_calendar.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:nkust_ap/res/resource.dart' as Resource; +import 'package:nkust_ap/api/helper.dart'; +import 'package:nkust_ap/models/models.dart'; +import 'package:nkust_ap/utils/utils.dart'; + +class MyLicencePage extends LicensePage { + static const String routerName = "/bus/reservations"; + + +} \ No newline at end of file diff --git a/lib/pages/home/bus/bus_reservations_page.dart b/lib/pages/home/bus/bus_reservations_page.dart new file mode 100644 index 00000000..9b2bcb04 --- /dev/null +++ b/lib/pages/home/bus/bus_reservations_page.dart @@ -0,0 +1,229 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter_calendar/flutter_calendar.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:nkust_ap/res/resource.dart' as Resource; +import 'package:nkust_ap/api/helper.dart'; +import 'package:nkust_ap/models/models.dart'; +import 'package:nkust_ap/utils/utils.dart'; + +enum BusReservationsState { loading, finish, error, empty } + +class BusReservationsPageRoute extends MaterialPageRoute { + BusReservationsPageRoute() + : super(builder: (BuildContext context) => new BusReservationsPage()); + + @override + Widget buildPage(BuildContext context, Animation animation, + Animation secondaryAnimation) { + return new FadeTransition( + opacity: animation, child: new BusReservationsPage()); + } +} + +class BusReservationsPage extends StatefulWidget { + static const String routerName = "/bus/reservations"; + static const String title = "校車記錄"; + + @override + BusReservationsPageState createState() => new BusReservationsPageState(); +} + +class BusReservationsPageState extends State + with SingleTickerProviderStateMixin { + BusReservationsState state = BusReservationsState.loading; + BusReservationsData busReservationsData; + List busReservationWeights = []; + DateTime dateTime = DateTime.now(); + + @override + void initState() { + super.initState(); + _getBusReservations(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return _body(); + } + + Widget _body() { + switch (state) { + case BusReservationsState.loading: + return Container( + child: CircularProgressIndicator(), alignment: Alignment.center); + case BusReservationsState.error: + case BusReservationsState.empty: + return FlatButton( + onPressed: _getBusReservations, + child: Center( + child: Flex( + mainAxisAlignment: MainAxisAlignment.center, + direction: Axis.vertical, + children: [ + SizedBox( + child: Icon( + Icons.directions_bus, + size: 150.0, + ), + width: 200.0, + ), + Text( + state == BusReservationsState.error + ? "發生錯誤,點擊重試" + : "Oops!您還沒有預約任何校車喔~\n多多搭乘大眾運輸,節能減碳救地球\uD83D\uDE0B", + textAlign: TextAlign.center, + ) + ], + ), + )); + default: + return ListView( + children: busReservationWeights, + ); + } + } + + _textStyle(BusReservation busReservation) => new TextStyle( + color: busReservation.getColorState(), + fontSize: 18.0, + decorationColor: Colors.grey); + + _busReservationWidget(BusReservation busReservation) => Column( + children: [ + Container( + padding: EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + flex: 1, + child: Icon( + Icons.directions_bus, + size: 20.0, + color: Resource.Colors.blue, + ), + ), + Expanded( + flex: 2, + child: Text( + "${busReservation.getStart()}→${busReservation.end}", + textAlign: TextAlign.center, + style: _textStyle(busReservation), + ), + ), + Expanded( + flex: 3, + child: Text( + busReservation.time, + textAlign: TextAlign.center, + style: _textStyle(busReservation), + ), + ), + Expanded( + flex: 2, + child: busReservation.canCancel() + ? IconButton( + icon: Icon( + Icons.cancel, + size: 20.0, + color: Resource.Colors.red, + ), + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + title: Text("取消預約", + textAlign: TextAlign.center, + style: TextStyle( + color: Resource.Colors.blue)), + content: Text( + "要取消從${busReservation.getStart()}" + "到${busReservation.end}\n" + "${busReservation.getTime()} 的校車嗎?", + textAlign: TextAlign.center, + ), + actions: [ + FlatButton( + child: Text("返回"), + onPressed: () { + Navigator.of(context, + rootNavigator: true) + .pop('dialog'); + }, + ), + FlatButton( + child: Text("取消預定校車"), + onPressed: () { + Navigator.of(context, + rootNavigator: true) + .pop('dialog'); + _cancelBusReservation( + busReservation); + }, + ) + ], + )); + }, + ) + : Container(), + ) + ], + ), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 24.0), + child: Divider( + color: Colors.grey, + indent: 4.0, + ), + ) + ], + ); + + _getBusReservations() { + state = BusReservationsState.loading; + setState(() {}); + Helper.instance.getBusReservations().then((response) { + if (response.data == null) { + state = BusReservationsState.error; + setState(() {}); + } else { + busReservationsData = BusReservationsData.fromJson(response.data); + for (var i in busReservationsData.reservations) { + busReservationWeights.add(_busReservationWidget(i)); + } + if (busReservationsData.reservations.length != 0) + state = BusReservationsState.finish; + else + state = BusReservationsState.empty; + setState(() {}); + } + }); + } + + _cancelBusReservation(BusReservation busReservation) { + Helper.instance + .cancelBusReservation(busReservation.cancelKey) + .then((response) { + String title = "", message = ""; + print(response.data["success"].runtimeType); + if (!response.data["success"]) { + title = "錯誤"; + message = response.data["message"]; + } else { + title = "取消成功"; + message = "取消日期:${busReservation.getDate()}\n" + "上車地點:${busReservation.getStart()}上車\n" + "取消班次:${busReservation.getTime()}"; + _getBusReservations(); + } + Utils.showDefaultDialog(context, title, message, "我知道了", () {}); + }); + } +} diff --git a/lib/pages/home/bus/bus_reserve_page.dart b/lib/pages/home/bus/bus_reserve_page.dart new file mode 100644 index 00000000..0c82ab6b --- /dev/null +++ b/lib/pages/home/bus/bus_reserve_page.dart @@ -0,0 +1,287 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_calendar/flutter_calendar.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:nkust_ap/res/resource.dart' as Resource; +import 'package:nkust_ap/api/helper.dart'; +import 'package:nkust_ap/models/models.dart'; +import 'package:nkust_ap/utils/utils.dart'; + +enum BusReserveState { loading, finish, error, empty } +enum Station { janGong, yanchao } + +class BusReservePageRoute extends MaterialPageRoute { + BusReservePageRoute() + : super(builder: (BuildContext context) => new BusReservePage()); + + @override + Widget buildPage(BuildContext context, Animation animation, + Animation secondaryAnimation) { + return new FadeTransition(opacity: animation, child: new BusReservePage()); + } +} + +class BusReservePage extends StatefulWidget { + static const String routerName = "/bus/reserve"; + static const String title = "校車預約"; + + @override + BusReservePageState createState() => new BusReservePageState(); +} + +class BusReservePageState extends State + with SingleTickerProviderStateMixin { + BusReserveState state = BusReserveState.loading; + BusData busData; + List busTimeWeights = []; + + Station selectStartStation = Station.janGong; + DateTime dateTime = DateTime.now(); + + @override + void initState() { + super.initState(); + _getBusTimeTables(); + } + + @override + void dispose() { + super.dispose(); + } + + _textStyle(BusTime busTime) => new TextStyle( + color: busTime.getColorState(), + fontSize: 18.0, + decorationColor: Colors.grey); + + Widget _body() { + switch (state) { + case BusReserveState.loading: + return Container( + child: CircularProgressIndicator(), alignment: Alignment.center); + case BusReserveState.error: + case BusReserveState.empty: + return FlatButton( + onPressed: _getBusTimeTables, + child: Center( + child: Flex( + mainAxisAlignment: MainAxisAlignment.center, + direction: Axis.vertical, + children: [ + SizedBox( + child: Icon( + Icons.directions_bus, + size: 150.0, + ), + width: 200.0, + ), + Text( + state == BusReserveState.error + ? "發生錯誤,點擊重試" + : "Oops!本日校車沒有上班喔~\n請選擇其他日期\uD83D\uDE0B", + textAlign: TextAlign.center, + ) + ], + ), + )); + default: + return ListView( + children: busTimeWeights, + ); + } + } + + _busTimeWidget(BusTime busTime) => Column( + children: [ + FlatButton( + padding: EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0), + onPressed: busTime.hasReserve() + ? () { + showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + title: Text( + "${busTime.getSpecialTrainTitle()}" + "${busTime.specialTrain == "0" ? "預約" : ""}", + textAlign: TextAlign.center, + style: + TextStyle(color: Resource.Colors.blue)), + content: Text( + "${busTime.getSpecialTrainRemark()}確定要預定本次${busTime.time}校車?", + textAlign: TextAlign.center, + ), + actions: [ + FlatButton( + child: Text("取消"), + onPressed: () { + Navigator.of(context, rootNavigator: true) + .pop('dialog'); + }, + ), + FlatButton( + child: Text("預約"), + onPressed: () { + Navigator.of(context, rootNavigator: true) + .pop('dialog'); + _bookingBus(busTime); + }, + ) + ], + )); + } + : null, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + flex: 1, + child: Icon( + Icons.directions_bus, + size: 20.0, + color: busTime.getColorState(), + ), + ), + Expanded( + flex: 3, + child: Text( + busTime.time, + textAlign: TextAlign.center, + style: _textStyle(busTime), + ), + ), + Expanded( + flex: 2, + child: Text( + "${busTime.reserveCount}人", + textAlign: TextAlign.center, + style: _textStyle(busTime), + ), + ), + Expanded( + flex: 3, + child: Text( + busTime.getSpecialTrainTitle(), + textAlign: TextAlign.center, + style: _textStyle(busTime), + ), + ), + Expanded( + flex: 2, + child: Icon( + Icons.access_time, + size: 20.0, + color: busTime.getColorState(), + ), + ), + Expanded( + flex: 3, + child: Text( + busTime.getReserveState(), + textAlign: TextAlign.center, + style: _textStyle(busTime), + ), + ) + ], + ), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 24.0), + child: Divider( + color: Colors.grey, + indent: 4.0, + ), + ) + ], + ); + + @override + Widget build(BuildContext context) { + return Flex( + direction: Axis.vertical, + children: [ + Calendar( + onDateSelected: (DateTime datetime) { + dateTime = datetime; + _getBusTimeTables(); + }, + ), + Container( + margin: EdgeInsets.all(8.0), + child: CupertinoSegmentedControl( + groupValue: selectStartStation, + children: { + Station.janGong: Container( + padding: + EdgeInsets.symmetric(vertical: 4.0, horizontal: 48.0), + child: Text("建工上車"), + ), + Station.yanchao: Container( + padding: + EdgeInsets.symmetric(vertical: 4.0, horizontal: 48.0), + child: Text("燕巢上車"), + ) + }, + onValueChanged: (Station text) { + selectStartStation = text; + if (state == BusReserveState.finish) + _updateBusTimeTables(); + else + setState(() {}); + }, + )), + Expanded( + flex: 9, + child: _body(), + ), + ], + ); + } + + _getBusTimeTables() { + state = BusReserveState.loading; + setState(() {}); + Helper.instance.getBusTimeTables(dateTime).then((response) { + if (response.data == null) { + state = BusReserveState.error; + setState(() {}); + } else { + busData = BusData.fromJson(response.data); + _updateBusTimeTables(); + } + }); + } + + _bookingBus(BusTime busTime) { + Helper.instance.bookingBusReservation(busTime.busId).then((response) { + String title = "", message = ""; + print(response.data["success"].runtimeType); + if (!response.data["success"]) { + title = "錯誤"; + message = response.data["message"]; + } else { + title = "預約成功"; + message = "預約日期:${busTime.getDate()}\n" + "上車地點:${busTime.getStart()}上車\n" + "預約班次:${busTime.time}"; + _getBusTimeTables(); + } + Utils.showDefaultDialog(context, title, message, "我知道了", () {}); + }); + } + + _updateBusTimeTables() { + busTimeWeights = []; + if (busData != null) { + for (var i in busData.timetable) { + if (selectStartStation == Station.janGong && i.endStation == "燕巢") + busTimeWeights.add(_busTimeWidget(i)); + else if (selectStartStation == Station.yanchao && i.endStation == "建工") + busTimeWeights.add(_busTimeWidget(i)); + } + if (busData.timetable.length != 0) + state = BusReserveState.finish; + else + state = BusReserveState.empty; + setState(() {}); + } + } +} diff --git a/lib/pages/home/bus_page.dart b/lib/pages/home/bus_page.dart new file mode 100644 index 00000000..ead52b39 --- /dev/null +++ b/lib/pages/home/bus_page.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:nkust_ap/res/resource.dart' as Resource; +import 'package:nkust_ap/api/helper.dart'; +import 'package:nkust_ap/models/models.dart'; +import 'package:flutter_calendar/flutter_calendar.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:nkust_ap/pages/page.dart'; + +class BusPageRoute extends MaterialPageRoute { + BusPageRoute() : super(builder: (BuildContext context) => new BusPage()); + + @override + Widget buildPage(BuildContext context, Animation animation, + Animation secondaryAnimation) { + return new FadeTransition(opacity: animation, child: new BusPage()); + } +} + +class BusPage extends StatefulWidget { + static const String routerName = "/bus"; + + @override + BusPageState createState() => new BusPageState(); +} + +class BusPageState extends State with SingleTickerProviderStateMixin { + int _currentIndex = 0; + final List _children = [new BusReservePage(), new BusReservationsPage()]; + final List _title = [BusReservePage.title, BusReservationsPage.title]; + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return new Scaffold( + // Appbar + appBar: new AppBar( + // Title + title: new Text(_title[_currentIndex]), + backgroundColor: Resource.Colors.blue, + ), + body: _children[_currentIndex], + bottomNavigationBar: new BottomNavigationBar( + currentIndex: _currentIndex, + onTap: onTabTapped, + fixedColor: Resource.Colors.yellow, + items: [ + BottomNavigationBarItem( + icon: Icon(Icons.date_range), + title: Text(BusReservePage.title), + ), + BottomNavigationBarItem( + icon: Icon(Icons.assignment), + title: Text(BusReservationsPage.title), + ), + ], + ), + ); + } + + void onTabTapped(int index) { + setState(() { + _currentIndex = index; + }); + } +} diff --git a/lib/pages/home/course_page.dart b/lib/pages/home/course_page.dart new file mode 100644 index 00000000..49c20331 --- /dev/null +++ b/lib/pages/home/course_page.dart @@ -0,0 +1,302 @@ +import 'package:flutter/material.dart'; +import 'package:nkust_ap/res/resource.dart' as Resource; +import 'package:nkust_ap/api/helper.dart'; +import 'package:nkust_ap/models/models.dart'; +import 'package:nkust_ap/utils/app_localizations.dart'; + +class CoursePageRoute extends MaterialPageRoute { + CoursePageRoute() + : super(builder: (BuildContext context) => new CoursePage()); + + @override + Widget buildPage(BuildContext context, Animation animation, + Animation secondaryAnimation) { + return new FadeTransition(opacity: animation, child: new CoursePage()); + } +} + +class CoursePage extends StatefulWidget { + static const String routerName = "/course"; + + @override + CoursePageState createState() => new CoursePageState(); +} + +// SingleTickerProviderStateMixin is used for animation +class CoursePageState extends State + with SingleTickerProviderStateMixin { + List courseWeightList = []; + var selectSemesterIndex; + Semester selectSemester; + + SemesterData semesterData; + + int base = 6; + + bool isLoading = true; + bool isError = false; + + var childAspectRatio = 0.8; + + @override + void initState() { + super.initState(); + _getSemester(); + } + + @override + void dispose() { + super.dispose(); + } + + _textBlueStyle() { + return TextStyle(color: Resource.Colors.blue, fontSize: 16.0); + } + + _textStyle() { + return TextStyle(color: Colors.black, fontSize: 14.0); + } + + Widget _textBorder(String text) { + return new Container( + decoration: new BoxDecoration( + border: new Border.all(color: Colors.grey, width: 0.5)), + child: FlatButton( + onPressed: () {}, + child: Text( + text ?? "", + textAlign: TextAlign.center, + style: _textBlueStyle(), + ), + ), + ); + } + + Widget _courseBorder(var data) { + Course course = Course.fromJson(data); + String content = "課程名稱:${course.title}\n" + "授課老師:${course.instructors[0] ?? ""}\n" + "教室位置:${course.building}${course.room}\n" + "上課時間:${course.startTime}-${course.endTime}"; + return new Container( + decoration: new BoxDecoration( + border: new Border.all(color: Colors.grey, width: 0.5)), + child: FlatButton( + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + title: const Text('課程資訊'), + content: Text(content), + actions: [ + FlatButton( + child: Text("OK"), + onPressed: () { + Navigator.of(context, rootNavigator: true) + .pop('dialog'); + }, + ) + ], + )); + }, + child: Text( + (data["title"][0] + data["title"][1]) ?? "", + style: _textStyle(), + )), + ); + } + + @override + Widget build(BuildContext context) { + return new Scaffold( + // Appbar + appBar: new AppBar( + // Title + title: new Text(Resource.Strings.course), + backgroundColor: Resource.Colors.blue, + ), + body: Container( + padding: EdgeInsets.symmetric(horizontal: 16.0), + child: Flex( + direction: Axis.vertical, + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SizedBox(height: 8.0), + Expanded( + flex: 1, + child: FlatButton( + onPressed: _selectSemester, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + selectSemester == null ? "" : selectSemester.text, + style: _textBlueStyle(), + ), + Icon( + Icons.keyboard_arrow_down, + color: Resource.Colors.blue, + ) + ], + ), + ), + ), + SizedBox(height: 8.0), + Expanded( + flex: 19, + child: RefreshIndicator( + onRefresh: () => _getCourseTables(), + child: isLoading + ? Container( + child: CircularProgressIndicator(), + alignment: Alignment.center) + : courseWeightList.length == 0 + ? FlatButton( + onPressed: + isError ? _getCourseTables : _selectSemester, + child: Center( + child: Flex( + mainAxisAlignment: MainAxisAlignment.center, + direction: Axis.vertical, + children: [ + SizedBox( + child: Icon( + Icons.class_, + size: 150.0, + ), + width: 200.0, + ), + Text( + isError + ? AppLocalizations.of(context) + .clickToRetry + : "Oops!本學期沒有任何課哦~\n請選擇其他學期\uD83D\uDE0B", + textAlign: TextAlign.center, + ) + ], + ), + )) + : GridView.count( + mainAxisSpacing: 0.0, + shrinkWrap: true, + childAspectRatio: childAspectRatio, + crossAxisCount: base, + children: courseWeightList ?? [], + ), + ), + ), + ], + ), + ), + ); + } + + void _getSemester() { + Helper.instance.getSemester().then((response) { + if (response.data == null) { + } else { + semesterData = SemesterData.fromJson(response.data); + selectSemester = semesterData.defaultSemester; + selectSemesterIndex = 0; + _getCourseTables(); + setState(() {}); + } + }); + } + + void _selectSemester() { + var semesters = []; + for (var semester in semesterData.semesters) { + semesters.add(_dialogItem(semesters.length, semester.text)); + } + showDialog( + context: context, + builder: (BuildContext context) => + SimpleDialog(title: const Text('請選擇學期'), children: semesters)) + .then((int position) { + if (position != null) { + selectSemesterIndex = position; + selectSemester = semesterData.semesters[selectSemesterIndex]; + _getCourseTables(); + setState(() {}); + } + }); + } + + SimpleDialogOption _dialogItem(int index, String text) { + return SimpleDialogOption( + child: Text(text), + onPressed: () { + Navigator.pop(context, index); + }); + } + + _getCourseTables() async { + courseWeightList.clear(); + isLoading = true; + setState(() {}); + var textList = semesterData.semesters[selectSemesterIndex].value.split(","); + if (textList.length == 2) { + Helper.instance + .getCourseTables(textList[0], textList[1]) + .then((response) { + //var semesterData = SemesterData.fromJson(response.data); + if (response.data["status"] == 200) { + var weeks = [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday" + ]; + courseWeightList = [ + _textBorder(""), + _textBorder("一"), + _textBorder("二"), + _textBorder("三"), + _textBorder("四"), + _textBorder("五") + ]; + if (response.data["coursetables"]["Saturday"] != null || + response.data["coursetables"]["Sunday"] != null) { + courseWeightList.add(_textBorder("六")); + courseWeightList.add(_textBorder("日")); + weeks.add("Saturday"); + weeks.add("Sunday"); + base = 8; + childAspectRatio = 0.5; + } else { + base = 6; + childAspectRatio = 0.9; + } + for (String text in response.data["coursetables"]["timecode"]) { + text = text.replaceAll(' ', ''); + courseWeightList.add(_textBorder(text)); + for (var i = 0; i < base - 1; i++) + courseWeightList.add(_textBorder("")); + } + var timeCodes = response.data["coursetables"]["timecode"]; + for (int i = 0; i < weeks.length; i++) { + if (response.data["coursetables"][weeks[i]] != null) + for (var data in response.data["coursetables"][weeks[i]]) { + for (int j = 0; j < timeCodes.length; j++) { + if (timeCodes[j] == data["date"]["section"]) { + courseWeightList[(j + 1) * base + i] = _courseBorder(data); + } + } + } + } + } + isLoading = false; + isError = false; + setState(() {}); + }); + } else { + isLoading = false; + isError = true; + setState(() {}); + } + } +} diff --git a/lib/pages/home/info/notification_page.dart b/lib/pages/home/info/notification_page.dart new file mode 100644 index 00000000..59b36277 --- /dev/null +++ b/lib/pages/home/info/notification_page.dart @@ -0,0 +1,184 @@ +import 'package:flutter/material.dart'; +import 'package:nkust_ap/res/resource.dart' as Resource; +import 'package:nkust_ap/api/helper.dart'; +import 'package:nkust_ap/models/models.dart'; +import 'package:nkust_ap/utils/utils.dart'; + +enum NotificationState { loading, finish, loadingMore, error, empty } + +class NotificationPageRoute extends MaterialPageRoute { + NotificationPageRoute() + : super(builder: (BuildContext context) => new NotificationPage()); + + @override + Widget buildPage(BuildContext context, Animation animation, + Animation secondaryAnimation) { + return new FadeTransition( + opacity: animation, child: new NotificationPage()); + } +} + +class NotificationPage extends StatefulWidget { + static const String routerName = "/info/notification"; + + @override + NotificationPageState createState() => new NotificationPageState(); +} + +class NotificationPageState extends State + with SingleTickerProviderStateMixin { + ScrollController controller; + List notificationList = []; + int page = 1; + + NotificationState state = NotificationState.loading; + + @override + void initState() { + controller = new ScrollController()..addListener(_scrollListener); + state = NotificationState.loading; + setState(() {}); + _getNotifications(); + super.initState(); + } + + @override + void dispose() { + controller.removeListener(_scrollListener); + super.dispose(); + } + + _textGreyStyle() { + return TextStyle(color: Resource.Colors.grey, fontSize: 14.0); + } + + _textStyle() { + return TextStyle( + color: Colors.black, fontSize: 18.0, fontWeight: FontWeight.bold); + } + + Widget _notificationItem(NotificationModel notification) { + return FlatButton( + padding: EdgeInsets.all(0.0), + onPressed: () { + Utils.launchUrl(notification.link); + }, + child: Container( + width: double.infinity, + padding: EdgeInsets.all(16.0), + decoration: new BoxDecoration( + border: new Border( + top: BorderSide(color: Colors.grey, width: 0.5), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + notification.info.title ?? "", + style: _textStyle(), + textAlign: TextAlign.left, + ), + SizedBox(height: 8.0), + Row( + children: [ + Expanded( + child: Text( + notification.info.department ?? "", + style: _textGreyStyle(), + textAlign: TextAlign.left, + ), + ), + Expanded( + child: Text( + notification.info.date ?? "", + style: _textGreyStyle(), + textAlign: TextAlign.right, + ), + ), + ], + ) + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return _body(); + } + + Widget _body() { + switch (state) { + case NotificationState.loading: + return Container( + child: CircularProgressIndicator(), alignment: Alignment.center); + case NotificationState.error: + case NotificationState.empty: + return FlatButton( + onPressed: () {}, + child: Center( + child: Flex( + mainAxisAlignment: MainAxisAlignment.center, + direction: Axis.vertical, + children: [ + SizedBox( + child: Icon( + Icons.directions_bus, + size: 150.0, + ), + width: 200.0, + ), + Text( + state == NotificationState.error + ? "發生錯誤,點擊重試" + : "Oops!本學期沒有任何成績資料哦~\n請選擇其他學期\uD83D\uDE0B", + textAlign: TextAlign.center, + ) + ], + ), + ), + ); + default: + return RefreshIndicator( + onRefresh: () { + state = NotificationState.loading; + setState(() {}); + notificationList.clear(); + _getNotifications(); + }, + child: ListView.builder( + controller: controller, + itemBuilder: (context, index) { + return _notificationItem(notificationList[index]); + }, + itemCount: notificationList.length, + ), + ); + } + } + + void _scrollListener() { + if (controller.position.extentAfter < 500) { + if (state == NotificationState.finish) { + setState(() { + page++; + state = NotificationState.loadingMore; + _getNotifications(); + }); + } + } + } + + _getNotifications() async { + Helper.instance.getNotifications(page).then((response) { + var notificationData = NotificationData.fromJson(response.data); + for (var notification in notificationData.notifications) { + notificationList.add(notification); + } + state = NotificationState.finish; + setState(() {}); + }); + } +} diff --git a/lib/pages/home/info/phone_page.dart b/lib/pages/home/info/phone_page.dart new file mode 100644 index 00000000..c13355ab --- /dev/null +++ b/lib/pages/home/info/phone_page.dart @@ -0,0 +1,197 @@ +import 'package:flutter/material.dart'; +import 'package:nkust_ap/res/resource.dart' as Resource; +import 'package:nkust_ap/api/helper.dart'; +import 'package:nkust_ap/models/models.dart'; +import 'package:nkust_ap/utils/app_localizations.dart'; +import 'package:nkust_ap/utils/utils.dart'; + +enum PhoneState { loading, finish, error, empty } + +class PhonePageRoute extends MaterialPageRoute { + PhonePageRoute() : super(builder: (BuildContext context) => new PhonePage()); + + @override + Widget buildPage(BuildContext context, Animation animation, + Animation secondaryAnimation) { + return new FadeTransition(opacity: animation, child: new PhonePage()); + } +} + +class PhonePage extends StatefulWidget { + static const String routerName = "/info/phone"; + + @override + PhonePageState createState() => new PhonePageState(); +} + +class PhonePageState extends State + with SingleTickerProviderStateMixin { + List phoneWeights = []; + + List phoneList = []; + + PhoneState state = PhoneState.loading; + + int page = 1; + + @override + void initState() { + super.initState(); + _getPhones(); + } + + @override + void dispose() { + super.dispose(); + } + + _textBlueStyle() { + return TextStyle( + color: Resource.Colors.blue, + fontSize: 18.0, + fontWeight: FontWeight.bold); + } + + _textGreyStyle() { + return TextStyle(color: Resource.Colors.grey, fontSize: 14.0); + } + + _textStyle() { + return TextStyle( + color: Colors.black, fontSize: 18.0, fontWeight: FontWeight.bold); + } + + Widget _phoneItem(PhoneModel phone) { + return FlatButton( + padding: EdgeInsets.all(0.0), + child: Container( + padding: EdgeInsets.all(16.0), + width: double.infinity, + decoration: new BoxDecoration( + border: new Border( + bottom: BorderSide(color: Colors.grey, width: 0.5), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + phone.name ?? "", + style: _textStyle(), + textAlign: TextAlign.left, + ), + SizedBox(height: 8.0), + Row( + children: [ + Expanded( + child: Text( + phone.number ?? "", + style: _textGreyStyle(), + textAlign: TextAlign.left, + ), + ), + ], + ) + ], + ), + ), + onPressed: () { + Utils.callPhone(phone.number); + }, + ); + } + + @override + Widget build(BuildContext context) { + return _body(); + } + + Widget _body() { + switch (state) { + case PhoneState.loading: + return Container( + child: CircularProgressIndicator(), alignment: Alignment.center); + case PhoneState.error: + case PhoneState.empty: + return FlatButton( + onPressed: () {}, + child: Center( + child: Flex( + mainAxisAlignment: MainAxisAlignment.center, + direction: Axis.vertical, + children: [ + SizedBox( + child: Icon( + Icons.directions_bus, + size: 150.0, + ), + width: 200.0, + ), + Text( + state == PhoneState.error + ? AppLocalizations.of(context).clickToRetry + : "Oops!本學期沒有任何成績資料哦~\n請選擇其他學期\uD83D\uDE0B", + textAlign: TextAlign.center, + ) + ], + ), + ), + ); + default: + return ListView( + children: phoneWeights, + ); + } + } + + _getPhones() async { + phoneWeights.clear(); + state = PhoneState.loading; + setState(() {}); + phoneList.add(PhoneModel("校安中心\n分機號碼:建工1 楠梓2 第一3 燕巢4 旗津5", "0800-550995")); + phoneList.add(PhoneModel("建工校區", "")); + phoneList.add(PhoneModel("校安專線", "0916-507-506")); + phoneList.add(PhoneModel("事務組", "(07) 381-4526 #2650")); + phoneList.add(PhoneModel("營繕組", "(07) 381-4526 #2630")); + phoneList.add(PhoneModel("課外活動組", "(07) 381-4526 #2525")); + phoneList.add(PhoneModel("諮商輔導中心", "(07) 381-4526 #2541")); + phoneList.add(PhoneModel("圖書館", "(07) 381-4526 #3100")); + phoneList.add(PhoneModel("校外賃居服務中心", "(07) 381-4526 #3420")); + phoneList.add(PhoneModel("燕巢校區", "")); + phoneList.add(PhoneModel("校安專線", "0925-350-995")); + phoneList.add(PhoneModel("校外賃居服務中心", "(07) 381-4526 #8615")); + phoneList.add(PhoneModel("第一校區", "")); + phoneList.add(PhoneModel("生輔組", "(07)601-1000 #31212")); + phoneList.add(PhoneModel("總務處 總機", "(07)601-1000 #31316")); + phoneList.add(PhoneModel("總務處 場地租借", "(07)601-1000 #31312")); + phoneList.add(PhoneModel("總務處 高科大會館", "(07)601-1000 #31306")); + phoneList.add(PhoneModel("總務處 學雜費相關(原事務組)", "(07)601-1000 #31340")); + phoneList.add(PhoneModel("課外活動組", "(07)601-1000 #31211")); + phoneList.add(PhoneModel("諮輔組", "(07)601-1000 #31241")); + phoneList.add(PhoneModel("圖書館", "(07)6011000 #1599")); + phoneList.add(PhoneModel("生輔組", "(07)6011000 #31212")); + phoneList.add(PhoneModel("楠梓校區", "")); + phoneList.add(PhoneModel("總機", "07-3617141")); + phoneList.add(PhoneModel("課外活動組", "07-3617141 #22070")); + phoneList.add(PhoneModel("海洋校區", "")); + phoneList.add(PhoneModel("海洋校區", "07-8100888")); + phoneList.add(PhoneModel("學生事務處", "07-3617141 #2052")); + phoneList.add(PhoneModel("課外活動組", "07-8100888 #25065")); + phoneList.add(PhoneModel("生活輔導組", "07-3617141 #23967")); + for (var i in phoneList) { + if (i.number.isEmpty) { + phoneWeights.add(Container( + padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), + child: Text( + i.name, + style: _textBlueStyle(), + textAlign: TextAlign.left, + ), + )); + } else + phoneWeights.add(_phoneItem(i)); + } + state = PhoneState.finish; + setState(() {}); + } +} diff --git a/lib/pages/home/info/schedule_page.dart b/lib/pages/home/info/schedule_page.dart new file mode 100644 index 00000000..bb2bb4cf --- /dev/null +++ b/lib/pages/home/info/schedule_page.dart @@ -0,0 +1,164 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:nkust_ap/res/resource.dart' as Resource; +import 'package:nkust_ap/api/helper.dart'; +import 'package:nkust_ap/models/models.dart'; +import 'package:firebase_remote_config/firebase_remote_config.dart'; +import 'package:nkust_ap/config/constants.dart'; +import 'package:nkust_ap/utils/app_localizations.dart'; + +enum ScheduleState { loading, finish, error, empty } + +class SchedulePageRoute extends MaterialPageRoute { + SchedulePageRoute() + : super(builder: (BuildContext context) => new SchedulePage()); + + @override + Widget buildPage(BuildContext context, Animation animation, + Animation secondaryAnimation) { + return new FadeTransition(opacity: animation, child: new SchedulePage()); + } +} + +class SchedulePage extends StatefulWidget { + static const String routerName = "/info/schedule"; + + @override + SchedulePageState createState() => new SchedulePageState(); +} + +class SchedulePageState extends State + with SingleTickerProviderStateMixin { + List scheduleWeights = []; + + List scheduleList = []; + + ScheduleState state = ScheduleState.loading; + + int page = 1; + + @override + void initState() { + super.initState(); + _getSchedules(); + } + + @override + void dispose() { + super.dispose(); + } + + _textBlueStyle() { + return TextStyle( + color: Resource.Colors.blue, + fontSize: 18.0, + fontWeight: FontWeight.bold); + } + + _textStyle() { + return TextStyle(color: Colors.black, fontSize: 16.0); + } + + Widget _scheduleItem(ScheduleData schedule) { + List items = []; + for (var i in schedule.events) { + items.add(Text( + i, + style: _textStyle(), + textAlign: TextAlign.left, + )); + items.add(SizedBox(height: 2.0)); + } + return Container( + width: double.infinity, + padding: EdgeInsets.all(16.0), + decoration: new BoxDecoration( + border: new Border( + top: BorderSide(color: Colors.grey, width: 0.5), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + schedule.week ?? "", + style: _textBlueStyle(), + textAlign: TextAlign.left, + ), + SizedBox(height: 8.0), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: items, + ) + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return _body(); + } + + Widget _body() { + switch (state) { + case ScheduleState.loading: + return Container( + child: CircularProgressIndicator(), alignment: Alignment.center); + case ScheduleState.error: + case ScheduleState.empty: + return FlatButton( + onPressed: _getSchedules, + child: Center( + child: Flex( + mainAxisAlignment: MainAxisAlignment.center, + direction: Axis.vertical, + children: [ + SizedBox( + child: Icon( + Icons.info, + size: 150.0, + ), + width: 200.0, + ), + Text( + state == ScheduleState.error + ? AppLocalizations.of(context).clickToRetry + : "Oops!本學期沒有任何行事曆資料哦~\n請點選重試\uD83D\uDE0B", + textAlign: TextAlign.center, + ) + ], + ), + ), + ); + default: + return ListView( + children: scheduleWeights, + ); + } + } + + _getSchedules() async { + state = ScheduleState.loading; + setState(() {}); + final RemoteConfig remoteConfig = await RemoteConfig.instance; + await remoteConfig.fetch(expiration: const Duration(hours: 5)); + await remoteConfig.activateFetched(); + JsonCodec jsonCodec = JsonCodec(); + var data = remoteConfig.getString(Constants.SCHEDULE_DATA); + if (data.isEmpty) + state = ScheduleState.error; + else { + print(data); + var jsonArray = jsonCodec.decode(data); + scheduleList = ScheduleData.toList(jsonArray); + scheduleWeights.clear(); + for (var i in scheduleList) scheduleWeights.add(_scheduleItem(i)); + if (scheduleList.length == 0) + state = ScheduleState.empty; + else + state = ScheduleState.finish; + } + setState(() {}); + } +} diff --git a/lib/pages/home/score_page.dart b/lib/pages/home/score_page.dart new file mode 100644 index 00000000..c013b8aa --- /dev/null +++ b/lib/pages/home/score_page.dart @@ -0,0 +1,323 @@ +import 'package:flutter/material.dart'; +import 'package:nkust_ap/res/resource.dart' as Resource; +import 'package:nkust_ap/api/helper.dart'; +import 'package:nkust_ap/models/models.dart'; +import 'package:nkust_ap/utils/app_localizations.dart'; + +enum ScoreState { loading, finish, error, empty } + +class ScorePageRoute extends MaterialPageRoute { + ScorePageRoute() : super(builder: (BuildContext context) => new ScorePage()); + + @override + Widget buildPage(BuildContext context, Animation animation, + Animation secondaryAnimation) { + return new FadeTransition(opacity: animation, child: new ScorePage()); + } +} + +class ScorePage extends StatefulWidget { + static const String routerName = "/score"; + + @override + ScorePageState createState() => new ScorePageState(); +} + +class ScorePageState extends State + with SingleTickerProviderStateMixin { + List scoreWeightList = []; + + var selectSemesterIndex; + Semester selectSemester; + + SemesterData semesterData; + ScoreData scoreData; + + ScoreState state = ScoreState.loading; + + @override + void initState() { + super.initState(); + _getSemester(); + } + + @override + void dispose() { + super.dispose(); + } + + _textBlueStyle() { + return TextStyle(color: Resource.Colors.blue, fontSize: 16.0); + } + + _textStyle() { + return TextStyle(color: Colors.black, fontSize: 14.0); + } + + _scoreTitle() => Container( + width: double.infinity, + child: Row( + children: [ + Expanded(child: _scoreTextBorder("課程名稱", false, true)), + Expanded(child: _scoreTextBorder("期中成績", false, true)), + Expanded(child: _scoreTextBorder("期末成績", true, true)), + ], + ), + ); + + Widget _textBorder(String text, bool isTop) { + return new Container( + width: double.infinity, + padding: EdgeInsets.all(2.0), + decoration: new BoxDecoration( + border: new Border( + top: isTop + ? BorderSide.none + : BorderSide(color: Colors.grey, width: 0.5), + ), + ), + child: Text( + text ?? "", + textAlign: TextAlign.center, + style: _textBlueStyle(), + ), + ); + } + + Widget _scoreTextBorder(String text, bool isEnd, bool isTitle) { + return Container( + width: double.maxFinite, + padding: EdgeInsets.symmetric(vertical: 2.0), + decoration: new BoxDecoration( + border: new Border( + right: isEnd + ? BorderSide.none + : BorderSide(color: Colors.grey, width: 0.5), + ), + ), + alignment: Alignment.center, + child: Text( + text ?? "", + textAlign: TextAlign.center, + style: isTitle ? _textBlueStyle() : _textStyle(), + ), + ); + } + + Widget _scoreBorder(Score score) { + return Container( + width: double.infinity, + decoration: new BoxDecoration( + border: new Border( + top: BorderSide(color: Colors.grey, width: 0.5), + ), + ), + child: Row( + children: [ + Expanded(child: _scoreTextBorder(score.title, false, false)), + Expanded(child: _scoreTextBorder(score.middleScore, false, false)), + Expanded(child: _scoreTextBorder(score.finalScore, true, false)), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return new Scaffold( + // Appbar + appBar: new AppBar( + // Title + title: new Text(Resource.Strings.score), + backgroundColor: Resource.Colors.blue, + ), + body: Container( + padding: EdgeInsets.all(16.0), + child: Flex( + direction: Axis.vertical, + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + flex: 1, + child: FlatButton( + onPressed: _selectSemester, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + selectSemester == null ? "" : selectSemester.text, + style: _textBlueStyle(), + ), + Icon( + Icons.keyboard_arrow_down, + color: Resource.Colors.blue, + ) + ], + ), + ), + ), + Expanded( + flex: 19, + child: RefreshIndicator( + onRefresh: () => _getSemesterScore(), + child: _body(), + ), + ), + ], + ), + ), + ); + } + + Widget _body() { + switch (state) { + case ScoreState.loading: + return Container( + child: CircularProgressIndicator(), alignment: Alignment.center); + case ScoreState.error: + case ScoreState.empty: + return FlatButton( + onPressed: + state == ScoreState.error ? _getSemesterScore : _selectSemester, + child: Center( + child: Flex( + mainAxisAlignment: MainAxisAlignment.center, + direction: Axis.vertical, + children: [ + SizedBox( + child: Icon( + Icons.assignment, + size: 150.0, + ), + width: 200.0, + ), + Text( + state == ScoreState.error + ? AppLocalizations.of(context).clickToRetry + : "Oops!本學期沒有任何成績資料哦~\n請選擇其他學期\uD83D\uDE0B", + textAlign: TextAlign.center, + ) + ], + ), + ), + ); + default: + return SingleChildScrollView( + child: Column( + children: [ + SizedBox(height: 8.0), + Container( + decoration: new BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular( + 10.0, + ), + ), + border: new Border.all(color: Colors.grey, width: 1.0), + ), + child: Flex( + direction: Axis.vertical, + children: scoreWeightList, + ), + ), + SizedBox( + height: 8.0, + ), + Container( + decoration: new BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular( + 10.0, + ), + ), + border: new Border.all(color: Colors.grey, width: 1.0), + ), + child: Column( + children: [ + _textBorder( + "操行成績:${scoreData.content.detail.conduct}", true), + _textBorder( + "總平均:${scoreData.content.detail.average}", false), + _textBorder( + "班名次/班人數:${scoreData.content.detail.classRank}", false), + _textBorder( + "班名次百分比:${scoreData.content.detail.classPercentage}", + false), + ], + ), + ), + ], + ), + ); + } + } + + void _selectSemester() { + var semesters = []; + for (var semester in semesterData.semesters) { + semesters.add(_dialogItem(semesters.length, semester.text)); + } + showDialog( + context: context, + builder: (BuildContext context) => + SimpleDialog(title: const Text('請選擇學期'), children: semesters)) + .then((int position) { + if (position != null) { + selectSemesterIndex = position; + selectSemester = semesterData.semesters[selectSemesterIndex]; + _getSemesterScore(); + setState(() {}); + } + }); + } + + void _getSemester() { + Helper.instance.getSemester().then((response) { + if (response.data == null) { + } else { + semesterData = SemesterData.fromJson(response.data); + selectSemester = semesterData.defaultSemester; + selectSemesterIndex = 0; + _getSemesterScore(); + setState(() {}); + } + }); + } + + _getSemesterScore() async { + scoreWeightList.clear(); + state = ScoreState.loading; + setState(() {}); + var textList = semesterData.semesters[selectSemesterIndex].value.split(","); + if (textList.length == 2) { + Helper.instance.getScore(textList[0], textList[1]).then((response) { + if (response.data["status"] == 200) { + scoreData = ScoreData.fromJson(response.data); + scoreWeightList.add(_scoreTitle()); + for (var score in scoreData.content.scores) { + scoreWeightList.add(_scoreBorder(score)); + } + if (scoreData.content.scores.length == 0) + state = ScoreState.empty; + else + state = ScoreState.finish; + } else + state = ScoreState.empty; + setState(() {}); + }); + } else { + state = ScoreState.error; + setState(() {}); + } + } + + SimpleDialogOption _dialogItem(int index, String text) { + return SimpleDialogOption( + child: Text(text), + onPressed: () { + Navigator.pop(context, index); + }, + ); + } +} diff --git a/lib/pages/home/setting_page.dart b/lib/pages/home/setting_page.dart new file mode 100644 index 00000000..86c50512 --- /dev/null +++ b/lib/pages/home/setting_page.dart @@ -0,0 +1,171 @@ +import 'package:flutter/material.dart'; +import 'package:nkust_ap/res/resource.dart' as Resource; +import 'package:nkust_ap/api/helper.dart'; +import 'package:nkust_ap/models/models.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:nkust_ap/pages/page.dart'; +import 'package:nkust_ap/utils/app_localizations.dart'; +import 'package:nkust_ap/utils/utils.dart'; +import 'package:nkust_ap/config/constants.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class SettingPageRoute extends MaterialPageRoute { + SettingPageRoute() + : super(builder: (BuildContext context) => new SettingPage()); + + @override + Widget buildPage(BuildContext context, Animation animation, + Animation secondaryAnimation) { + return new FadeTransition(opacity: animation, child: new SettingPage()); + } +} + +class SettingPage extends StatefulWidget { + static const String routerName = "/setting"; + + @override + SettingPageState createState() => new SettingPageState(); +} + +class SettingPageState extends State + with SingleTickerProviderStateMixin { + SharedPreferences prefs; + + var notifyBus = false, + notifyCourse = false, + displayPicture = true, + vibrateCourse = false; + + @override + void initState() { + super.initState(); + _getPreference(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return new Scaffold( + appBar: new AppBar( + title: new Text(AppLocalizations + .of(context) + .settings), + backgroundColor: Resource.Colors.blue, + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _titleItem("通知設定"), + _itemSwitch("上課體醒", notifyCourse, () { + notifyCourse = !notifyCourse; + prefs.setBool(Constants.PREF_NOTIFY_COURSE, notifyCourse); + Utils.showToast("功能尚未開放\n私密粉絲團 小編會告訴你何時開放"); + //setState(() {}); + }), + _itemSwitch("校車提醒", notifyBus, () { + notifyBus = !notifyBus; + prefs.setBool(Constants.PREF_NOTIFY_BUS, notifyBus); + Utils.showToast("功能尚未開放\n私密粉絲團 小編會告訴你何時開放"); + //setState(() {}); + }), + Container( + color: Colors.grey, + height: 0.5, + ), + _titleItem("一般設定"), + _itemSwitch("顯示大頭貼", displayPicture, () { + displayPicture = !displayPicture; + prefs.setBool(Constants.PREF_DISPLAY_PICTURE, displayPicture); + setState(() {}); + }), + _itemSwitch("上課震動", vibrateCourse, () { + vibrateCourse = !vibrateCourse; + prefs.setBool(Constants.PREF_VIBRATE_COURSE, vibrateCourse); + Utils.showToast("功能尚未開放\n私密粉絲團 小編會告訴你何時開放"); + //setState(() {}); + }), + Container( + color: Colors.grey, + height: 0.5, + ), + _titleItem("其他資訊"), + _item("回饋意見", "私訊給粉絲專頁", () { + Utils.launchUrl('https://www.facebook.com/954175941266264/'); + }), + _item("Donate", "貢獻一點心力支持作者,\n可以提早使用未開放功能!", () { + Utils.launchUrl( + "https://payment.ecpay.com.tw/QuickCollect/PayData?mLM7iy8RpUGk%2fyBotSDMdvI0qGI5ToToqBW%2bOQbOE80%3d"); + }), + _item("App版本", Constants.APP_VERSION, () {}), + ]), + ), + ); + } + + _titleItem(String text) => + Container( + padding: EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 8.0), + child: Text( + text, + style: TextStyle(color: Resource.Colors.blue, fontSize: 14.0), + textAlign: TextAlign.start, + ), + ); + + _itemSwitch(String text, bool value, Function function) => + FlatButton( + padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + text, + style: TextStyle(fontSize: 16.0), + ), + Switch( + value: value, + activeColor: Resource.Colors.blue, + activeTrackColor: Resource.Colors.blue, + onChanged: (b) { + function(); + }, + ), + ], + ), + onPressed: function, + ); + + _getPreference() async { + prefs = await SharedPreferences.getInstance(); + displayPicture = prefs.getBool(Constants.PREF_DISPLAY_PICTURE) ?? true; + setState(() {}); + } + + _item(String text, String subText, Function function) => + FlatButton( + padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), + child: Container( + width: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + text, + style: TextStyle(fontSize: 16.0), + ), + Text( + subText, + style: TextStyle(fontSize: 14.0, color: Resource.Colors.grey), + ), + ], + ), + ), + onPressed: function, + ); +} diff --git a/lib/pages/home/shcool_info_page.dart b/lib/pages/home/shcool_info_page.dart new file mode 100644 index 00000000..84f900ff --- /dev/null +++ b/lib/pages/home/shcool_info_page.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.dart'; +import 'package:nkust_ap/res/resource.dart' as Resource; +import 'package:nkust_ap/api/helper.dart'; +import 'package:nkust_ap/models/models.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:nkust_ap/pages/page.dart'; +import 'package:nkust_ap/utils/app_localizations.dart'; + +class SchoolInfoPageRoute extends MaterialPageRoute { + SchoolInfoPageRoute() + : super(builder: (BuildContext context) => new SchoolInfoPage()); + + @override + Widget buildPage(BuildContext context, Animation animation, + Animation secondaryAnimation) { + return new FadeTransition(opacity: animation, child: new SchoolInfoPage()); + } +} + +class SchoolInfoPage extends StatefulWidget { + static const String routerName = "/ShcoolInfo"; + + @override + SchoolInfoPageState createState() => new SchoolInfoPageState(); +} + +class SchoolInfoPageState extends State + with SingleTickerProviderStateMixin { + int _currentIndex = 0; + final List _children = [ + new NotificationPage(), + new PhonePage(), + new SchedulePage() + ]; + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return new Scaffold( + appBar: new AppBar( + title: new Text(AppLocalizations.of(context).messages[_currentIndex]), + backgroundColor: Resource.Colors.blue, + ), + body: _children[_currentIndex], + bottomNavigationBar: new BottomNavigationBar( + currentIndex: _currentIndex, + onTap: onTabTapped, + fixedColor: Resource.Colors.yellow, + items: [ + BottomNavigationBarItem( + icon: Icon(Icons.fiber_new), + title: Text(AppLocalizations.of(context).notifications), + ), + BottomNavigationBarItem( + icon: Icon(Icons.phone), + title: Text(AppLocalizations.of(context).phones), + ), + BottomNavigationBarItem( + icon: Icon(Icons.date_range), + title: Text(AppLocalizations.of(context).events), + ), + ], + ), + ); + } + + void onTabTapped(int index) { + setState(() { + _currentIndex = index; + }); + } +} diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart new file mode 100644 index 00000000..397a7285 --- /dev/null +++ b/lib/pages/home_page.dart @@ -0,0 +1,201 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:nkust_ap/pages/page.dart'; +import 'package:nkust_ap/res/resource.dart' as Resource; +import 'package:nkust_ap/api/helper.dart'; +import 'package:nkust_ap/models/models.dart'; +import 'package:nkust_ap/utils/app_localizations.dart'; +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:nkust_ap/widgets/drawer_body.dart'; +import 'package:nkust_ap/utils/utils.dart'; + +enum HomeStatus { loading, finish, error, empty } + +class HomePageRoute extends MaterialPageRoute { + HomePageRoute() : super(builder: (BuildContext context) => new HomePage()); + + @override + Widget buildPage(BuildContext context, Animation animation, + Animation secondaryAnimation) { + return new FadeTransition(opacity: animation, child: new HomePage()); + } +} + +class HomePage extends StatefulWidget { + static const String routerName = "/home"; + + @override + HomePageState createState() => new HomePageState(); +} + +// SingleTickerProviderStateMixin is used for animation +class HomePageState extends State + with SingleTickerProviderStateMixin { + HomeStatus state = HomeStatus.loading; + AppLocalizations app; + + TabController controller; + int _currentTabIndex = 0; + int _currentNewsIndex = 0; + + List newsWidgets = []; + List news = []; + + @override + void initState() { + super.initState(); + controller = new TabController(length: 2, vsync: this); + _getAllNews(); + } + + @override + void dispose() { + // Dispose of the Tab Controller + controller.dispose(); + super.dispose(); + } + + Widget _newImage(News news) { + return Container( + margin: EdgeInsets.all(5.0), + child: FlatButton( + onPressed: () { + Utils.launchUrl(news.url); + }, + padding: EdgeInsets.all(0.0), + child: Image.network(news.image)), + ); + } + + Widget _homebody() { + switch (state) { + case HomeStatus.loading: + return Center( + child: CircularProgressIndicator(), + ); + case HomeStatus.finish: + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + flex: 1, + child: Text( + news[_currentNewsIndex].title, + textAlign: TextAlign.center, + style: TextStyle(fontSize: 20.0), + ), + ), + Expanded( + flex: 4, + child: CarouselSlider( + items: newsWidgets, + viewportFraction: 0.7, + height: 400.0, + autoPlay: false, + updateCallback: (int current) { + _currentNewsIndex = current; + setState(() {}); + }, + ), + ), + Expanded( + flex: 1, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "${_currentNewsIndex + 1}", + textAlign: TextAlign.center, + style: + TextStyle(color: Resource.Colors.red, fontSize: 24.0), + ), + Text( + "/${newsWidgets.length}", + textAlign: TextAlign.center, + style: + TextStyle(color: Resource.Colors.grey, fontSize: 24.0), + ) + ], + ), + ), + ], + ); + default: + return Container(); + } + } + + @override + Widget build(BuildContext context) { + return new Scaffold( + // Appbar + appBar: new AppBar( + // Title + title: new Text(AppLocalizations.of(context).title), + // Set the background color of the App Bar + backgroundColor: Resource.Colors.blue, + ), + endDrawer: DrawerBody(), + // Set the TabBar view as the body of the Scaffold + body: Container( + padding: EdgeInsets.symmetric(vertical: 32.0), + child: Center( + child: _homebody(), + ), + ), + // Set the bottom navigation bar + bottomNavigationBar: new BottomNavigationBar( + currentIndex: _currentTabIndex, + onTap: onTabTapped, + items: [ + BottomNavigationBarItem( + // set icon to the tab + icon: Icon(Icons.directions_bus), + title: Text(AppLocalizations.of(context).bus), + ), + BottomNavigationBarItem( + icon: Icon(Icons.class_), + title: Text(AppLocalizations.of(context).course), + ), + BottomNavigationBarItem( + icon: Icon(Icons.assignment), + title: Text(AppLocalizations.of(context).score), + ), + ], + ), + ); + } + + void onTabTapped(int index) { + setState(() { + _currentTabIndex = index; + switch (_currentTabIndex) { + case 0: + Navigator.of(context).push(BusPageRoute()); + break; + case 1: + Navigator.of(context).push(CoursePageRoute()); + break; + case 2: + Navigator.of(context).push(ScorePageRoute()); + break; + } + }); + } + + _getAllNews() { + state = HomeStatus.loading; + Helper.instance.getAllNews().then((response) { + if (response == null) { + state = HomeStatus.error; + return; + } + JsonCodec jsonCodec = JsonCodec(); + var jsonArray = jsonCodec.decode(response.data); + news = News.toList(jsonArray); + for (var i in news) newsWidgets.add(_newImage(i)); + state = news.length == 0 ? HomeStatus.empty : HomeStatus.finish; + setState(() {}); + }); + } +} diff --git a/lib/pages/login_page.dart b/lib/pages/login_page.dart new file mode 100644 index 00000000..65df2237 --- /dev/null +++ b/lib/pages/login_page.dart @@ -0,0 +1,189 @@ +import 'package:flutter/material.dart'; +import 'package:nkust_ap/res/colors.dart' as Resource; +import 'package:nkust_ap/utils/utils.dart'; +import 'package:nkust_ap/pages/page.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:nkust_ap/config/constants.dart'; +import 'package:nkust_ap/api/helper.dart'; +import 'package:nkust_ap/utils/app_localizations.dart'; + +class LoginPage extends StatefulWidget { + static const String routerName = "/login"; + + @override + LoginPageState createState() => new LoginPageState(); +} + +class LoginPageState extends State + with SingleTickerProviderStateMixin { + SharedPreferences prefs; + + final TextEditingController _username = new TextEditingController(); + final TextEditingController _password = new TextEditingController(); + bool isRememberPassword = false; + + @override + void initState() { + super.initState(); + _isRememberPassword(); + _showDialog(); + } + + @override + void dispose() { + super.dispose(); + } + + _editTextStyle() => new TextStyle( + color: Colors.white, fontSize: 18.0, decorationColor: Colors.white); + + @override + Widget build(BuildContext context) { + return new Scaffold( + backgroundColor: Resource.Colors.blue, + resizeToAvoidBottomPadding: false, + body: Center( + child: Container( + padding: EdgeInsets.symmetric(vertical: 16.0, horizontal: 30.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.spaceAround, + mainAxisSize: MainAxisSize.min, + children: [ + Center( + child: Text( + "K", + style: TextStyle(color: Colors.white, fontSize: 128.0), + ), + ), + TextField( + maxLines: 1, + controller: _username, + decoration: InputDecoration( + labelText: AppLocalizations.of(context).username, + ), + style: _editTextStyle(), + ), + TextField( + obscureText: true, + maxLines: 1, + controller: _password, + decoration: InputDecoration( + labelText: AppLocalizations.of(context).password, + ), + style: _editTextStyle(), + ), + SizedBox( + height: 15.0, + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Checkbox( + value: isRememberPassword, + onChanged: _onChanged, + ), + Text(AppLocalizations.of(context).remember) + ], + ), + Material( + child: RaisedButton( + padding: EdgeInsets.all(12.0), + onPressed: _login, + color: Colors.grey[300], + child: new Text( + AppLocalizations.of(context).login, + style: new TextStyle( + color: Resource.Colors.blue, fontSize: 18.0), + ), + ), + borderRadius: BorderRadius.circular(20.0), + shadowColor: Colors.grey, + elevation: 5.0, + ), + ], + ), + ), + )); + } + + _showDialog() async { + prefs = await SharedPreferences.getInstance(); + await Future.delayed(Duration(milliseconds: 50)); + if (prefs.getBool(Constants.PREF_FIRST_ENTER_APP) ?? true) + Utils.showDefaultDialog( + context, + AppLocalizations.of(context).updateNoteTitle, + "${Constants.APP_VERSION}\n" + "${AppLocalizations.of(context).updateNoteContent}", + AppLocalizations.of(context).ok, () { + prefs.setBool(Constants.PREF_FIRST_ENTER_APP, false); + }); + } + + _onChanged(bool value) async { + setState(() { + isRememberPassword = value; + prefs.setBool(Constants.PREF_REMEMBER_PASSWORD, isRememberPassword); + }); + } + + _isRememberPassword() async { + prefs = await SharedPreferences.getInstance(); + isRememberPassword = + prefs.getBool(Constants.PREF_REMEMBER_PASSWORD) ?? false; + _username.text = prefs.getString(Constants.PREF_USERNAME) ?? ""; + if (isRememberPassword) + _password.text = prefs.getString(Constants.PREF_PASSWORD) ?? ""; + setState(() {}); + } + + _login() async { + if (_username.text.isEmpty || _password.text.isEmpty) { + Utils.showToast(AppLocalizations.of(context).doNotEmpty); + } else { + showDialog( + context: context, + builder: (BuildContext context) => new AlertDialog( + content: bodyProgress(context), + contentPadding: EdgeInsets.all(0.0), + )); + var data = await Helper.instance.login(_username.text, _password.text); + Navigator.of(context, rootNavigator: true).pop('dialog'); + SharedPreferences prefs = await SharedPreferences.getInstance(); + prefs.setString(Constants.PREF_USERNAME, _username.text); + if (isRememberPassword) + prefs.setString(Constants.PREF_PASSWORD, _password.text); + if (data != null) + Navigator.of(context).push(HomePageRoute()); + else + Utils.showToast(AppLocalizations.of(context).loginFail); + } + } + + Widget bodyProgress(BuildContext context) => new Container( + width: 150.0, + height: 150.0, + alignment: AlignmentDirectional.center, + child: new Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + new Center( + child: CircularProgressIndicator( + value: null, + ), + ), + new Container( + margin: const EdgeInsets.only(top: 25.0), + child: new Center( + child: new Text( + AppLocalizations.of(context).logining, + style: new TextStyle(color: Colors.blue), + ), + ), + ), + ], + ), + ); +} diff --git a/lib/pages/main_page.dart b/lib/pages/main_page.dart new file mode 100644 index 00000000..41fb21b6 --- /dev/null +++ b/lib/pages/main_page.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:nkust_ap/pages/login_page.dart'; +import 'package:nkust_ap/pages/home_page.dart'; + +class MainPage extends StatefulWidget { + @override + MainPageState createState() => new MainPageState(); +} + +// SingleTickerProviderStateMixin is used for animation +class MainPageState extends State + with SingleTickerProviderStateMixin { + var isLogin = false; + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return new Scaffold( + body: isLogin ? new HomePage() : new LoginPage(), + ); + } +} diff --git a/lib/pages/page.dart b/lib/pages/page.dart new file mode 100644 index 00000000..d0d09315 --- /dev/null +++ b/lib/pages/page.dart @@ -0,0 +1,17 @@ + + +export 'home_page.dart'; +export 'login_page.dart'; +export 'main_page.dart'; +export 'home/bus_page.dart'; +export 'home/bus/bus_reserve_page.dart'; +export 'home/bus/bus_reservations_page.dart'; +export 'home/course_page.dart'; +export 'home/score_page.dart'; +export 'home/shcool_info_page.dart'; +export 'home/info/notification_page.dart'; +export 'home/info/phone_page.dart'; +export 'home/info/schedule_page.dart'; +export 'home/setting_page.dart'; +export 'home/about/about_us_page.dart'; +export 'home/about/licence_page.dart'; \ No newline at end of file diff --git a/lib/res/colors.dart b/lib/res/colors.dart new file mode 100644 index 00000000..6724d1a9 --- /dev/null +++ b/lib/res/colors.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class Colors { + Colors._(); + + static const Color blue = const Color(0xff2574ff); + static const Color grey = const Color(0xff7c7c7c); + static const Color yellow = const Color(0xffffba00); + static const Color red = const Color(0xffff4a5a); +} diff --git a/lib/res/resource.dart b/lib/res/resource.dart new file mode 100644 index 00000000..47218346 --- /dev/null +++ b/lib/res/resource.dart @@ -0,0 +1,3 @@ + +export 'string.dart'; +export 'colors.dart'; \ No newline at end of file diff --git a/lib/res/string.dart b/lib/res/string.dart new file mode 100644 index 00000000..56bd4748 --- /dev/null +++ b/lib/res/string.dart @@ -0,0 +1,16 @@ +class Strings { + + + Strings._(); + static const String app_name = "高科校務通"; + static const String username = "學號"; + static const String password = "密碼"; + static const String remember_password = "記住密碼"; + static const String login = "登入"; + static const String do_not_empty = "請勿留空"; + static const String login_fail = "帳號或密碼錯誤"; + static const String bus = "校車系統"; + static const String course = "學期課表"; + static const String score = "學期成績"; + static const String login_ing ="登入中..."; +} diff --git a/lib/utils/app_localizations.dart b/lib/utils/app_localizations.dart new file mode 100644 index 00000000..0cb5f6bb --- /dev/null +++ b/lib/utils/app_localizations.dart @@ -0,0 +1,194 @@ +import 'package:flutter/material.dart'; + +class AppLocalizations { + AppLocalizations(Locale locale) { + init(locale); + } + + static init(Locale locale) { + AppLocalizations.locale = locale; + } + + static Locale locale; + + static AppLocalizations of(BuildContext context) { + return Localizations.of(context, AppLocalizations); + } + + static Map> _localizedValues = { + 'en': { + 'title': 'NKUST AP', + 'update_note_title': 'Update Notes', + 'update_note_content': 'Welcome to NKUST AP\n' + 'This app backend comes from the backend application of KUAS AP\n' + 'In theory, it can only be used by Jan Gong and Yanchao Campus\n' + 'However, the recent school system data integration\n' + 'Students from other campuses are also welcome to try\n' + 'Welcome to contact the fans page if there is a problem with the use\n' + 'NKUST AP Authors', + 'ok': 'OK', + 'username': 'Student ID', + 'password': 'Password', + 'remember_password': 'remember password', + 'login': 'Login', + 'do_not_empty': 'Don\'t Empty', + 'login_fail': 'student id or password error', + 'bus': 'Bus', + 'course': 'Class Schedule', + 'score': 'Report Card', + 'login_ing': 'logining...', + 'school_info': 'School Info', + 'about': 'About Us', + 'settings': 'Settings', + 'notifications': 'News', + 'phones': 'Tel no.', + 'events': 'Events', + 'click_to_retry': 'An error occurred, click to retry', + 'about_detail': '', + 'about_author_title': 'Made by', + 'about_author_content': + '呂紹榕(Louie Lu), 姜尚德(JohnThunder), \nregisterAutumn, 詹濬鍵(Evans), \n陳建霖(HearSilent)', + 'about_us': + '“Ask not why nobody is doing this. You are \'nobody\'.”\n\nWe did this cause no one did it.\nWe created KUAS Wifi Login, KUASAP and KUAS Gourmet, Course Selection Sim, etc…\nTo bring convenience to everyone\'s on campus!', + 'about_recruit_title': 'We Need You !', + 'about_recruit_content': + 'If you\'re experienced in Objective-C, Swift, Java or you\'re interested in Coding!\n\nMessage us at our Facebook fanpage!\nYour code might one day be operating in everyone\'s hands~', + 'about_itc_content': + 'In year 2014,\nwe founded KUAS Information Technology Club!\n\nIf you\'re enthusiastic or drawn to our projects, join our classes and talks or come by to chat!', + 'about_itc_title': 'KUAS IT Club', + 'about_contact_us': 'Contact Us', + 'about_open_source_title': 'Open Source License', + 'about_open_source_content': + 'https://github.com/abc873693/NKUST-AP-Flutter\n\nThis project is licensed under the terms of the MIT license:\nThe MIT License (MIT)\n\nCopyright © 2018 Rainvisitor\n\nThis project is Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.' + }, + 'zh': { + 'title': '高科校務通', + 'update_note_title': '更新日誌', + 'update_note_content': + '\n歡迎使用高科校務通\n本APP後端來自於高應校務通的後端應用\n理論上僅可供建工與燕巢校區使用\n但近期校務系統資料陸續整合\n也歡迎其他校區的同學嘗試使用\n有任何問題歡迎私密粉專\n高科校務通作者敬上', + 'ok': '好', + 'username': '學號', + 'password': '密碼', + 'remember_password': '記住密碼', + 'login': '登入', + 'do_not_empty': '請勿留空', + 'login_fail': '帳號或密碼錯誤', + 'bus': '校車系統', + 'course': '學期課表', + 'score': '學期成績', + 'login_ing': '登入中...', + 'school_info': '校園資訊', + 'about': '關於我們', + 'settings': '設定', + 'notifications': '最新消息', + 'phones': '常用電話', + 'events': '行事曆', + 'click_to_retry': '發生錯誤,點擊重試', + 'about_author_title': '作者群', + 'about_author_content': + '呂紹榕(Louie Lu), 姜尚德(JohnThunder), \nregisterAutumn, 詹濬鍵(Evans), \n陳建霖(HearSilent)', + 'about_us': + '「不要問為何沒有人做這個,\n先承認你就是『沒有人』」。\n因為,「沒有人」是萬能的。\n\n因為沒有人做這些,所以我們跳下來做。\n先後完成了高應無線通、高應校務通,到後來的高應美食通、模擬選課等等.......\n無非是希望帶給大家更便利的校園生活!', + 'about_recruit_title': 'We Need You !', + 'about_recruit_content': + '如果你是 Objective-C、Swift 高手,或是 Java 神手,又或是對 Coding充滿著熱誠!\n\n歡迎私訊我們粉絲專頁!\n你的程式碼將有機會出現在周遭同學的手中~', + 'about_itc_content': + '在103學年度,\n我們也成立了高應大資訊研習社!\n\n如果你對資訊有熱誠或是對我們作品有興趣,歡迎來社課或是講座,也可以來找我們聊聊天。', + 'about_itc_title': '高應資研社', + 'about_contact_us': '聯繫我們', + 'about_open_source_title': '開放原始碼授權', + 'about_open_source_content': + 'https://github.com/abc873693/NKUST-AP-Flutter\n\n本專案採MIT 開放原始碼授權:\nThe MIT License (MIT)\n\nCopyright © 2018 Rainvisitor\n\nThis project is Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.' + }, + }; + + Map get _vocabularies => _localizedValues[locale.languageCode]; + + Map get messages => { + 0: notifications, + 1: phones, + 2: events, + }; + + String get title => _vocabularies['title']; + + String get updateNoteTitle => _vocabularies['update_note_title']; + + String get updateNoteContent => _vocabularies['update_note_content']; + + String get ok => _vocabularies['ok']; + + String get username => _vocabularies['username']; + + String get password => _vocabularies['password']; + + String get remember => _vocabularies['remember_password']; + + String get login => _vocabularies['login']; + + String get doNotEmpty => _vocabularies['do_not_empty']; + + String get loginFail => _vocabularies['login_fail']; + + String get bus => _vocabularies['bus']; + + String get course => _vocabularies['course']; + + String get score => _vocabularies['score']; + + String get logining => _vocabularies['login_ing']; + + String get schoolInfo => _vocabularies['school_info']; + + String get about => _vocabularies['about']; + + String get settings => _vocabularies['settings']; + + String get notifications => _vocabularies['notifications']; + + String get phones => _vocabularies['phones']; + + String get events => _vocabularies['events']; + + String get clickToRetry => _vocabularies['click_to_retry']; + + String get aboutAuthorTitle => _vocabularies['about_author_title']; + + String get aboutAuthorContent => _vocabularies['about_author_content']; + + String get aboutUsContent => _vocabularies['about_us']; + + String get aboutRecruitTitle => _vocabularies['about_recruit_title']; + + String get aboutRecruitContent => _vocabularies['about_recruit_content']; + + String get aboutItcTitle => _vocabularies['about_itc_title']; + + String get aboutItcContent => _vocabularies['about_itc_content']; + + String get aboutContactUsTitle => _vocabularies['about_contact_us']; + + String get aboutOpenSourceTitle => _vocabularies['about_open_source_title']; + + String get aboutOpenSourceContent => + _vocabularies['about_open_source_content']; +} + +class AppLocalizationsDelegate extends LocalizationsDelegate { + const AppLocalizationsDelegate(); + + @override + bool isSupported(Locale locale) => true; + + @override + Future load(Locale locale) async { + AppLocalizations localizations = new AppLocalizations(locale); + + print("Load ${locale.languageCode}"); + + return localizations; + } + + @override + bool shouldReload(AppLocalizationsDelegate old) => false; +} diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart new file mode 100644 index 00000000..3e35a9d9 --- /dev/null +++ b/lib/utils/utils.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:nkust_ap/res/resource.dart' as Resource; +import 'package:url_launcher/url_launcher.dart'; + +class Utils { + static void showToast(String message) { + Fluttertoast.showToast( + msg: message, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.BOTTOM, + timeInSecForIos: 1, + bgcolor: "#434c61", + textcolor: '#ffffff'); + } + + static void showDefaultDialog(BuildContext context, String title, + String content, String actionText, Function function) { + showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + title: Text(title, + textAlign: TextAlign.center, + style: TextStyle(color: Resource.Colors.blue)), + content: Text(content, + textAlign: TextAlign.left, + style: TextStyle(color: Resource.Colors.grey)), + actions: [ + FlatButton( + child: Text(actionText, + style: TextStyle(color: Resource.Colors.grey)), + onPressed: () { + Navigator.of(context, rootNavigator: true).pop('dialog'); + function(); + }, + ) + ], + )); + } + + static launchUrl(var url) async { + if (await canLaunch(url)) { + await launch(url); + } else { + throw 'Could not launch $url'; + } + } + + static callPhone(String url) async { + url = url.replaceAll('#', ','); + url = url.replaceAll('(', ''); + url = url.replaceAll(')', ''); + url = url.replaceAll('-', ''); + url = url.replaceAll(' ', ''); + url = "tel:$url"; + if (await canLaunch(url)) { + await launch(url); + } else { + throw 'Could not launch $url'; + } + } +} diff --git a/lib/widgets/carousel.dart b/lib/widgets/carousel.dart new file mode 100644 index 00000000..6cd26201 --- /dev/null +++ b/lib/widgets/carousel.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; +import 'dart:async'; + +class Carousel extends StatefulWidget { + ///All the [Widget] on this Carousel. + final List children; + + ///Returns [children]`s [lenght]. + int get childrenCount => children.length; + + ///The transition animation timing curve. Default is [Curves.ease] + final Curve animationCurve; + + ///The transition animation duration. Default is 250ms. + final Duration animationDuration; + + ///The amount of time each frame is displayed. Default is 2s. + final Duration displayDuration; + + Carousel( + {this.children, + this.animationCurve = Curves.ease, + this.animationDuration = const Duration(milliseconds: 250), + this.displayDuration = const Duration(seconds: 2)}) + : assert(children != null), + assert(children.length > 1), + assert(animationCurve != null), + assert(animationDuration != null), + assert(displayDuration != null); + + @override + State createState() => new _CarouselState(); +} + +class _CarouselState extends State + with SingleTickerProviderStateMixin { + TabController _controller; + Timer _timer; + + ///Actual index of the displaying Widget + int get actualIndex => _controller.index; + + ///Returns the calculated value of the next index. + int get nextIndex { + var nextIndexValue = actualIndex; + + if (nextIndexValue < _controller.length - 1) + nextIndexValue++; + else + nextIndexValue = 0; + + return nextIndexValue; + } + + @override + void initState() { + super.initState(); + + _controller = new TabController(length: widget.childrenCount, vsync: this); + + startAnimating(); + } + + @override + void dispose() { + _controller.dispose(); + _timer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return new TabBarView( + children: widget.children + .map((widget) => new Center( + child: widget, + )) + .toList(), + controller: this._controller, + ); + } + + void startAnimating() { + _timer?.cancel(); + + //Every widget.displayDuration (time) the tabbar controller will animate to the next index. + _timer = new Timer.periodic( + widget.displayDuration, + (_) => this._controller.animateTo(this.nextIndex, + curve: widget.animationCurve, duration: widget.animationDuration)); + } +} diff --git a/lib/widgets/drawer_body.dart b/lib/widgets/drawer_body.dart new file mode 100644 index 00000000..b545f962 --- /dev/null +++ b/lib/widgets/drawer_body.dart @@ -0,0 +1,162 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:nkust_ap/pages/page.dart'; +import 'package:nkust_ap/res/resource.dart' as Resource; +import 'package:nkust_ap/api/helper.dart'; +import 'package:nkust_ap/models/models.dart'; +import 'package:nkust_ap/utils/app_localizations.dart'; +import 'package:nkust_ap/config/constants.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +var pictureUrl = ""; +UserInfo userInfo; + +class DrawerBody extends StatefulWidget { + @override + DrawerBodyState createState() => new DrawerBodyState(); +} + +class DrawerBodyState extends State { + SharedPreferences prefs; + bool displayPicture = true; + + @override + void initState() { + super.initState(); + _getPreference(); + _getUserPicture(); + _getUserInfo(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Drawer( + semanticLabel: "測試", + child: Column( + children: [ + Container( + decoration: new BoxDecoration( + image: new DecorationImage( + image: new AssetImage("assets/images/kuasap3.png"), + fit: BoxFit.fill, + ), + ), + width: double.infinity, + child: Container( + padding: EdgeInsets.all(20.0), + child: Flex( + direction: Axis.vertical, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 40.0), + pictureUrl != "" && displayPicture + ? Container( + width: 72.0, + height: 72.0, + decoration: BoxDecoration( + shape: BoxShape.circle, + image: new DecorationImage( + fit: BoxFit.fill, + image: NetworkImage(pictureUrl), + ), + ), + ) + : Container( + width: 72.0, + height: 72.0, + decoration: BoxDecoration( + shape: BoxShape.circle, + ), + child: Icon( + Icons.account_circle, + color: Colors.white, + size: 72.0, + ), + ), + SizedBox(height: 16.0), + Text( + userInfo == null + ? "" + : "${userInfo.nameCht}\n" + "${userInfo.id}", + style: TextStyle(color: Colors.white), + ) + ], + ), + ), + ), + _item(Icons.class_, AppLocalizations.of(context).course, + CoursePageRoute()), + _item(Icons.assignment, AppLocalizations.of(context).score, + ScorePageRoute()), + _item(Icons.directions_bus, AppLocalizations.of(context).bus, + BusPageRoute()), + _item(Icons.info, AppLocalizations.of(context).schoolInfo, + SchoolInfoPageRoute()), + _item(Icons.face, AppLocalizations.of(context).about, + AboutUsPageRoute()), + _item(Icons.settings, AppLocalizations.of(context).settings, + SettingPageRoute()), + ], + ), + ); + } + + _item(IconData icon, String title, MaterialPageRoute route) => FlatButton( + onPressed: () { + Navigator.of(context).pop(); + Navigator.of(context).push(route); + }, + child: Row( + children: [ + Container( + padding: EdgeInsets.all(18.0), + child: Icon( + icon, + size: 24.0, + color: Resource.Colors.grey, + ), + ), + SizedBox(width: 4.0), + Text( + title, + textAlign: TextAlign.center, + style: TextStyle(color: Resource.Colors.grey, fontSize: 16.0), + ) + ], + )); + + _getUserPicture() { + Helper.instance.getUsersPicture().then((response) { + if (response == null) { + return; + } + pictureUrl = response.data; + setState(() {}); + }); + } + + _getUserInfo() { + Helper.instance.getUsersInfo().then((response) { + if (response == null) { + return; + } + JsonCodec jsonCodec = JsonCodec(); + var json = jsonCodec.decode(response.data); + userInfo = UserInfo.fromJson(json); + setState(() {}); + }); + } + _getPreference() async { + prefs = await SharedPreferences.getInstance(); + displayPicture = prefs.getBool(Constants.PREF_DISPLAY_PICTURE) ?? true; + setState(() {}); + } + +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 00000000..a6c7240b --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,73 @@ +name: nkust_ap +description: A new Flutter application. + +dependencies: + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + shared_preferences: ^0.4.2 + cupertino_icons: ^0.1.2 + dio: 1.0.4 + fluttertoast: ^2.0.9 + firebase_core: ^0.2.5+1 + firebase_analytics: 1.0.4 + firebase_remote_config: ^0.0.6+1 + firebase_messaging: ^2.0.2 + flutter_calendar: ^0.0.8 + url_launcher: ^4.0.1 + carousel_slider: ^0.0.7 +dev_dependencies: + flutter_test: + sdk: flutter + + +# For information on the generic Dart part of this file, see the +# following page: https://www.dartlang.org/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + assets: + - assets/images/ + - assets/images/kuasap.png + - assets/images/kuasap2.png + - assets/images/kuasap3.png + - assets/images/kuas_itc.png + - assets/images/ic_fb.png + - assets/images/ic_github.png + - assets/images/ic_email.png + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.io/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.io/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.io/custom-fonts/#from-packages diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 00000000..88c888e9 --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,28 @@ +// This is a basic Flutter widget test. +// To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter +// provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to +// find child widgets in the widget tree, read text, and verify that the values of widget properties +// are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:nkust_ap/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +}