├── android ├── settings_aar.gradle ├── gradle.properties ├── .gitignore ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values │ │ │ │ │ ├── colors.xml │ │ │ │ │ └── styles.xml │ │ │ │ ├── drawable-hdpi │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── drawable-mdpi │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── drawable-xhdpi │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── drawable-xxhdpi │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── drawable-xxxhdpi │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ │ └── ic_launcher.xml │ │ │ │ └── drawable │ │ │ │ │ └── launch_background.xml │ │ │ ├── kotlin │ │ │ │ └── org │ │ │ │ │ └── amfoss │ │ │ │ │ └── cms_android │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── build.gradle ├── ios ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Runner.xcworkspace │ └── contents.xcworkspacedata ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme └── .gitignore ├── assets ├── images │ ├── cms.jpg │ ├── amfoss.png │ └── amfoss_dark.png └── launcher │ ├── icon.png │ ├── background.png │ └── foreground.png ├── .gitlab ├── issue_templates │ ├── chore.md │ ├── feature_request.md │ └── bug_report.md └── merge_request_templates │ └── merge_request.md ├── lib ├── utilities │ ├── ColorGenerator.dart │ ├── image_address.dart │ ├── theme_data.dart │ ├── theme_provider.dart │ ├── shared_preferences.dart │ ├── sizeconfig.dart │ ├── indicator.dart │ ├── constants.dart │ └── drawer.dart ├── main.dart ├── appInit.dart ├── data │ ├── user_database.dart │ └── user_database.g.dart ├── screens │ ├── home.dart │ ├── attendance │ │ ├── absent.dart │ │ ├── present.dart │ │ ├── attendance.dart │ │ └── statistics │ │ │ └── attendance_stats.dart │ ├── statusUpdate │ │ ├── members_didnot_sent.dart │ │ ├── members_sent.dart │ │ ├── status_update.dart │ │ ├── userUpdates.dart │ │ ├── messages.dart │ │ └── statistics │ │ │ ├── status_update_stats.dart │ │ │ └── status_update_graphs.dart │ ├── profile │ │ ├── about.dart │ │ ├── profile.dart │ │ └── update_profile.dart │ └── login_screen.dart └── app.dart ├── .metadata ├── .gitlab-ci.yml ├── .gitignore ├── test └── widget_test.dart ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md ├── pubspec.yaml ├── README.md └── pubspec.lock /android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /assets/images/cms.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/assets/images/cms.jpg -------------------------------------------------------------------------------- /assets/images/amfoss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/assets/images/amfoss.png -------------------------------------------------------------------------------- /assets/launcher/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/assets/launcher/icon.png -------------------------------------------------------------------------------- /assets/images/amfoss_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/assets/images/amfoss_dark.png -------------------------------------------------------------------------------- /assets/launcher/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/assets/launcher/background.png -------------------------------------------------------------------------------- /assets/launcher/foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/assets/launcher/foreground.png -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #000000 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /.gitlab/issue_templates/chore.md: -------------------------------------------------------------------------------- 1 | **Describe the chore** 2 | 3 | 4 | **Would you like to work on the issue?** (Yes/No) 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amfoss/cms-mobile/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /lib/utilities/ColorGenerator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class ColorGenerator { 5 | static Color getColor() { 6 | Random _random = new Random(); 7 | return Color.fromARGB(255, _random.nextInt(255), _random.nextInt(255), _random.nextInt(255)); 8 | } 9 | } -------------------------------------------------------------------------------- /lib/utilities/image_address.dart: -------------------------------------------------------------------------------- 1 | class ImageAddressProvider { 2 | static String imageAddress(String url, String amfossPic) { 3 | if (amfossPic.isNotEmpty) { 4 | return "https://api.amfoss.in/" + amfossPic; 5 | } else { 6 | return "https://avatars.githubusercontent.com/" + url; 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 27321ebbad34b0a3fafe99fac037102196d655ff 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:cms_mobile/appInit.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | 5 | void main() { 6 | WidgetsFlutterBinding.ensureInitialized(); 7 | SystemChrome.setPreferredOrientations( 8 | [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); 9 | runApp(MyApp()); 10 | } -------------------------------------------------------------------------------- /.gitlab/merge_request_templates/merge_request.md: -------------------------------------------------------------------------------- 1 | Closes #[Add issue number here. If you do not solve the issue entirely, please change the message e.g. "First steps for issues #IssueNumber] 2 | 3 | Changes: [Mention the files changed in the PR. Add here what changes were made in this issue and if possible provide links(Deploy/Preview Link).] 4 | 5 | Screenshots of the change: 6 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.gitlab/issue_templates/feature_request.md: -------------------------------------------------------------------------------- 1 | **Describe the feature you'd like** 2 | 3 | 4 | **Screenshots** 5 | 6 | 7 | **Additional context** 8 | 9 | 10 | **Would you like to work on the issue?** (Yes/No) 11 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | 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. -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/utilities/theme_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Styles { 4 | static ThemeData themeData(bool isDarkTheme, BuildContext context) { 5 | if (isDarkTheme) { 6 | return ThemeData( 7 | brightness: Brightness.dark, 8 | accentColor: Colors.amber 9 | ); 10 | } else { 11 | return ThemeData( 12 | brightness: Brightness.light, 13 | accentColor: Colors.amberAccent 14 | ); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/utilities/theme_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:cms_mobile/utilities/shared_preferences.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | 4 | class DarkThemeProvider with ChangeNotifier { 5 | DarkThemePreference darkThemePreference = DarkThemePreference(); 6 | bool _darkTheme = false; 7 | 8 | bool get darkTheme => _darkTheme; 9 | 10 | set darkTheme(bool value) { 11 | _darkTheme = value; 12 | darkThemePreference.setDarkTheme(value); 13 | notifyListeners(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: cirrusci/flutter:stable 2 | 3 | stages: 4 | - build 5 | 6 | before_script: 7 | - flutter pub get 8 | - flutter clean 9 | 10 | build:apk: 11 | stage: build 12 | script: 13 | - flutter build apk 14 | artifacts: 15 | paths: 16 | - build/app/outputs/apk 17 | expire_in: 1 days 18 | 19 | build:bundle: 20 | stage: build 21 | script: 22 | - flutter build appbundle 23 | artifacts: 24 | paths: 25 | - build/app/outputs/bundle 26 | expire_in: 1 days 27 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/org/amfoss/cms_android/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package org.amfoss.cms_mobile 2 | 3 | import androidx.annotation.NonNull; 4 | import io.flutter.embedding.android.FlutterActivity 5 | import io.flutter.embedding.engine.FlutterEngine 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { 10 | GeneratedPluginRegistrant.registerWith(flutterEngine); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /lib/utilities/shared_preferences.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | 3 | class DarkThemePreference { 4 | static const THEME_STATUS = "THEMESTATUS"; 5 | 6 | setDarkTheme(bool value) async { 7 | SharedPreferences prefs = await SharedPreferences.getInstance(); 8 | prefs.setBool(THEME_STATUS, value); 9 | } 10 | 11 | Future getTheme() async { 12 | SharedPreferences prefs = await SharedPreferences.getInstance(); 13 | return prefs.getBool(THEME_STATUS) ?? false; 14 | } 15 | } -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /.gitlab/issue_templates/bug_report.md: -------------------------------------------------------------------------------- 1 | **Describe the bug** 2 | A clear and concise description of what the bug is. 3 | 4 | **To Reproduce** 5 | Steps to reproduce the behavior: 6 | 1. Go to '...' 7 | 2. Click on '....' 8 | 3. Scroll down to '....' 9 | 4. See error 10 | 11 | **Logs** 12 | 13 | **Expected behavior** 14 | A clear and concise description of what you expected to happen. 15 | 16 | **Screenshots** 17 | If applicable, add screenshots to help explain your problem. 18 | 19 | **Smartphone (please complete the following information):** 20 | - Device: [e.g. Nokia 7 Plus] 21 | - OS: [e.g. Android 9 Pie] 22 | 23 | **Additional context** 24 | Add any other context about the problem here. 25 | 26 | **Would you like to work on the issue?** (Yes/No) 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/utilities/sizeconfig.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | class SizeConfig { 4 | static MediaQueryData _mediaQueryData; 5 | static double screenWidth; 6 | static double screenHeight; 7 | double _safeAreaHorizontal; 8 | double _safeAreaVertical; 9 | static double widthFactor; 10 | static double heightFactor; 11 | static double aspectRation; 12 | 13 | void init(BuildContext context) { 14 | _mediaQueryData = MediaQuery.of(context); 15 | screenWidth = _mediaQueryData.size.width; 16 | screenHeight = _mediaQueryData.size.height; 17 | _safeAreaHorizontal = 18 | _mediaQueryData.padding.left + _mediaQueryData.padding.right; 19 | _safeAreaVertical = 20 | _mediaQueryData.padding.top + _mediaQueryData.padding.bottom; 21 | widthFactor = (screenWidth - _safeAreaHorizontal) / 360; 22 | heightFactor = (screenHeight - _safeAreaVertical) / 740; 23 | aspectRation = widthFactor / heightFactor; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/utilities/indicator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Indicator extends StatelessWidget { 4 | final Color color; 5 | final String text; 6 | final bool isSquare; 7 | final double size; 8 | final Color textColor; 9 | 10 | const Indicator({ 11 | Key key, 12 | this.color, 13 | this.text, 14 | this.isSquare, 15 | this.size = 16, 16 | this.textColor = const Color(0xff505050), 17 | }) : super(key: key); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return Row( 22 | children: [ 23 | Container( 24 | width: size, 25 | height: size, 26 | decoration: BoxDecoration( 27 | shape: isSquare ? BoxShape.rectangle : BoxShape.circle, 28 | color: color, 29 | ), 30 | ), 31 | const SizedBox( 32 | width: 4, 33 | ), 34 | Text( 35 | text, 36 | style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: textColor), 37 | ) 38 | ], 39 | ); 40 | } 41 | } -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:cms_mobile/app.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:flutter_test/flutter_test.dart'; 11 | 12 | void main() { 13 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 14 | // Build our app and trigger a frame. 15 | await tester.pumpWidget(CMS()); 16 | 17 | // Verify that our counter starts at 0. 18 | expect(find.text('0'), findsOneWidget); 19 | expect(find.text('1'), findsNothing); 20 | 21 | // Tap the '+' icon and trigger a frame. 22 | await tester.tap(find.byIcon(Icons.add)); 23 | await tester.pump(); 24 | 25 | // Verify that our counter has incremented. 26 | expect(find.text('0'), findsNothing); 27 | expect(find.text('1'), findsOneWidget); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /lib/appInit.dart: -------------------------------------------------------------------------------- 1 | import 'package:cms_mobile/utilities/theme_data.dart'; 2 | import 'package:cms_mobile/utilities/theme_provider.dart'; 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:provider/provider.dart'; 6 | 7 | import 'app.dart'; 8 | import 'data/user_database.dart'; 9 | 10 | class MyApp extends StatefulWidget { 11 | @override 12 | _MyAppState createState() => _MyAppState(); 13 | } 14 | 15 | class _MyAppState extends State { 16 | DarkThemeProvider themeChangeProvider = new DarkThemeProvider(); 17 | 18 | @override 19 | void initState() { 20 | super.initState(); 21 | getCurrentAppTheme(); 22 | } 23 | 24 | void getCurrentAppTheme() async { 25 | themeChangeProvider.darkTheme = 26 | await themeChangeProvider.darkThemePreference.getTheme(); 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return ChangeNotifierProvider( 32 | create: (_) { 33 | return themeChangeProvider; 34 | }, 35 | child: Consumer( 36 | builder: (BuildContext context, value, Widget child) { 37 | return Provider( 38 | create: (BuildContext context) => AppDatabase(), 39 | child: MaterialApp( 40 | theme: Styles.themeData(themeChangeProvider.darkTheme, context), 41 | home: CMS(), 42 | debugShowCheckedModeBanner: false, 43 | )); 44 | }, 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | amFOSS CMS 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 9 | 10 | 14 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 31 | 32 | -------------------------------------------------------------------------------- /lib/data/user_database.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:ui'; 3 | 4 | import 'package:moor/moor.dart'; 5 | import 'package:moor_flutter/moor_flutter.dart'; 6 | 7 | // Generated file that we don't need to bother about. While making changes to the database, `flutter packages pub run build_runner watch` must be run 8 | // to detect any changes and update the generated file immediately 9 | part 'user_database.g.dart'; 10 | 11 | class Users extends Table { 12 | // Columns of the database. Nullable means that a null value can be assigned to them 13 | TextColumn get username => text().nullable()(); 14 | TextColumn get authToken => text().nullable()(); 15 | TextColumn get refreshToken => text().nullable()(); 16 | 17 | @override 18 | Set get primaryKey => {username}; 19 | } 20 | 21 | @UseMoor(tables: [Users]) 22 | class AppDatabase extends _$AppDatabase { 23 | AppDatabase() 24 | : super((FlutterQueryExecutor.inDatabaseFolder( 25 | path: 'db.sqlite', 26 | // Good for debugging - prints SQL in the console 27 | logStatements: true, 28 | ))); 29 | 30 | // Has to be bumped up in the event of a migration 31 | @override 32 | int get schemaVersion => 1; 33 | 34 | // Gets single user. getSingle is used since at any point of time only one user should be present in the database 35 | Future getSingleUser() => select(users).getSingle(); 36 | 37 | // Inserts user details into database 38 | Future insertUser(User user) => into(users).insert(user); 39 | 40 | // Updates user details in the database 41 | Future updateUser(User user) => update(users).replace(user); 42 | 43 | // Deletes user details from the database 44 | Future deleteUser(User user) => delete(users).delete(user); 45 | } -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "org.amfoss.cms_mobile" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | testImplementation 'junit:junit:4.12' 65 | androidTestImplementation 'androidx.test:runner:1.1.1' 66 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 67 | } 68 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to Contribute 2 | 3 | ### Raising an issue: 4 | This is an Open Source project and we would be happy to see contributors who report bugs and file feature requests submitting pull requests as well. 5 | This project adheres to the Contributor Covenant code of conduct. 6 | By participating, you are expected to uphold this code style. 7 | Please report issues here [Issues - amfoss/cms-mobile](https://gitlab.com/amfoss/cms-mobile/-/issues) 8 | 9 | ### Branch Policy 10 | 11 | #### Sending pull requests: 12 | 13 | Go to the repository on GitLab at https://gitlab.com/amfoss/cms-mobile . 14 | 15 | Click the “Fork” button at the top right. 16 | 17 | You’ll now have your own copy of the original cms-mobile repository in your GitLab account. 18 | 19 | Open a terminal/shell. 20 | 21 | Type 22 | 23 | `$ git clone https://gitlab.com/username/cms-mobile` 24 | 25 | where 'username' is your username. 26 | 27 | You’ll now have a local copy of your version of the original cms-mobile repository. 28 | 29 | #### Change into that project directory (cms-mobile): 30 | 31 | `$ cd cms-mobile` 32 | 33 | #### Add a connection to the original cms-mobile repository. 34 | 35 | `$ git remote add upstream https://gitlab.com/amfoss/cms-mobile` 36 | 37 | #### To check this remote add set up: 38 | 39 | `$ git remote -v` 40 | 41 | #### Make changes to files. 42 | 43 | `git add` and `git commit` those changes 44 | 45 | `git push` them back to GitLab. These will go to your version of the repository. 46 | 47 | 48 | #### Now Create a PR (Pull Request) 49 | 50 | Go to your version of the repository on GitLab. 51 | 52 | Click the “New pull Request” button at the top. 53 | 54 | Click the green button “Create pull request”. Give a succinct and informative title, in the comment field give a short explanation of the changes and click the blue button “Create pull request” again. 55 | 56 | #### Pulling others’ changes 57 | 58 | Before you make further changes to the repository, you should check that your version is up to date relative to original version. 59 | 60 | Go into the directory for the project and type: 61 | 62 | `$ git checkout ` 63 | 64 | `$ git pull upstream --rebase` 65 | 66 | This will pull down and merge all of the changes that have been made in the original cms-mobile repository. 67 | 68 | Now push them back to your GitLab repository. 69 | 70 | `$ git push origin ` 71 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lib/screens/home.dart: -------------------------------------------------------------------------------- 1 | import 'package:cms_mobile/screens/profile/profile.dart'; 2 | import 'package:cms_mobile/utilities/constants.dart'; 3 | import 'package:cms_mobile/utilities/sizeconfig.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_icons/flutter_icons.dart'; 6 | import 'package:graphql_flutter/graphql_flutter.dart'; 7 | import 'statusUpdate/status_update.dart'; 8 | import 'attendance/attendance.dart'; 9 | 10 | class HomePage extends StatefulWidget { 11 | final Link url; 12 | final String username; 13 | 14 | const HomePage({Key key, this.username, this.url}) : super(key: key); 15 | 16 | @override 17 | HomePageScreen createState() => HomePageScreen(); 18 | } 19 | 20 | class HomePageScreen extends State { 21 | static Link url; 22 | static String username; 23 | 24 | int _currentIndex = 0; 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | SizeConfig().init(context); 29 | final List _children = [ 30 | Attendance(), 31 | StatusUpdate( 32 | appUsername: widget.username, 33 | ), 34 | Profile( 35 | username: widget.username, 36 | ) 37 | ]; 38 | 39 | final ValueNotifier client = ValueNotifier( 40 | GraphQLClient(link: widget.url, cache: InMemoryCache()), 41 | ); 42 | 43 | url = widget.url; 44 | username = widget.username; 45 | return GraphQLProvider( 46 | client: client, 47 | child: Scaffold( 48 | body: _children[_currentIndex], 49 | bottomNavigationBar: StreamBuilder( 50 | stream: null, 51 | builder: (context, snapshot) { 52 | return BottomNavigationBar( 53 | fixedColor: appPrimaryColor, 54 | currentIndex: _currentIndex, 55 | items: [ 56 | BottomNavigationBarItem( 57 | icon: Icon(Icons.playlist_add_check), 58 | title: Text("Attendance"), 59 | ), 60 | BottomNavigationBarItem( 61 | icon: Icon(FlutterIcons.gmail_mco), 62 | title: Text("Status Update")), 63 | BottomNavigationBarItem( 64 | icon: Icon(Icons.person), title: Text("Profile")) 65 | ], 66 | onTap: onTabTapped, 67 | ); 68 | }), 69 | ), 70 | ); 71 | } 72 | 73 | void onTabTapped(int index) { 74 | setState(() { 75 | _currentIndex = index; 76 | }); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:cms_mobile/screens/login_screen.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:graphql_flutter/graphql_flutter.dart'; 4 | import 'package:provider/provider.dart'; 5 | 6 | import 'data/user_database.dart'; 7 | import 'screens/home.dart'; 8 | 9 | class CMS extends StatefulWidget { 10 | @override 11 | _CMS createState() => _CMS(); 12 | } 13 | 14 | class _CMS extends State { 15 | Link link; 16 | String username; 17 | String token; 18 | final HttpLink httpLink = HttpLink( 19 | uri: 'https://api.amfoss.in/', 20 | ); 21 | @override 22 | Widget build(BuildContext context) { 23 | final db = Provider.of(context, listen: false); 24 | return FutureBuilder( 25 | future: db.getSingleUser(), 26 | builder: (BuildContext context, AsyncSnapshot snapshot) { 27 | if (snapshot.connectionState == ConnectionState.waiting) 28 | return Container(color: Colors.white); 29 | else if (snapshot.data == null) 30 | return LoginScreen(); 31 | else if (snapshot.hasData) { 32 | final refreshCred = snapshot.data.refreshToken; 33 | if(refreshCred == null){ 34 | return LoginScreen(); 35 | } 36 | final split = refreshCred.split(' '); 37 | final username = snapshot.data.username; 38 | final password = split[1]; 39 | setToken(username, password).then((value){ 40 | token = value; 41 | }); 42 | final AuthLink authLink = AuthLink( 43 | getToken: () async => 'JWT $token', 44 | ); 45 | link = authLink.concat(httpLink); 46 | return HomePage( 47 | username: username, 48 | url: link, 49 | ); 50 | } else 51 | return Container(color: Colors.white,); 52 | }, 53 | ); 54 | } 55 | 56 | setToken(String username, String password) async{ 57 | final String authMutation = ''' 58 | mutation{ 59 | tokenAuth(username:"$username", password:"$password") { 60 | token 61 | } 62 | } 63 | '''; 64 | 65 | GraphQLClient _client = GraphQLClient( 66 | link: httpLink, 67 | cache: OptimisticCache(dataIdFromObject: typenameDataIdFromObject)); 68 | QueryResult result = 69 | await _client.mutate(MutationOptions(document: authMutation)); 70 | 71 | String refreshedToken = result.data['tokenAuth']['token']; 72 | return refreshedToken; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/utilities/constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:cms_mobile/utilities/sizeconfig.dart'; 2 | import 'package:cms_mobile/utilities/theme_provider.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:provider/provider.dart'; 5 | 6 | final kHintTextStyle = TextStyle( 7 | color: getCurrentAppTheme() != null ? Colors.white54 : Colors.black54, 8 | fontFamily: 'OpenSans', 9 | ); 10 | 11 | final appPrimaryColor = Color(0xFFFFA538); 12 | 13 | final kLabelStyle = TextStyle( 14 | fontWeight: FontWeight.bold, 15 | fontFamily: 'OpenSans', 16 | ); 17 | 18 | final messageLabelStyle = TextStyle( 19 | fontSize: 20, 20 | fontWeight: FontWeight.bold, 21 | fontFamily: 'OpenSans', 22 | ); 23 | 24 | final userUpdateHeadings = TextStyle( 25 | fontSize: 16, 26 | fontWeight: FontWeight.bold, 27 | fontFamily: 'OpenSans', 28 | ); 29 | 30 | final CMSLabelStyle = TextStyle( 31 | color: getCurrentAppTheme() != null ? Colors.white : Colors.black, 32 | fontWeight: FontWeight.bold, 33 | fontStyle: FontStyle.italic, 34 | fontFamily: 'OpenSans', 35 | ); 36 | 37 | final List choices = ["Select Date", "Messages List"]; 38 | 39 | final kBoxDecorationStyle = BoxDecoration( 40 | color: getCurrentAppTheme() != null ? Colors.white12 : Colors.black12, 41 | borderRadius: BorderRadius.circular(SizeConfig.aspectRation * 10.0), 42 | boxShadow: [ 43 | BoxShadow( 44 | color: Colors.white, 45 | blurRadius: SizeConfig.aspectRation * 6.0, 46 | offset: Offset(0, 2), 47 | ), 48 | ], 49 | ); 50 | 51 | Future getCurrentAppTheme() async { 52 | DarkThemeProvider themeChangeProvider = new DarkThemeProvider(); 53 | return await themeChangeProvider.darkThemePreference.getTheme(); 54 | } 55 | 56 | //Login screen constants 57 | 58 | final loginLabelStyle = TextStyle( 59 | color: Colors.white, 60 | fontWeight: FontWeight.bold, 61 | fontFamily: 'OpenSans', 62 | ); 63 | 64 | final loginBoxDecorationStyle = BoxDecoration( 65 | color: Colors.white12, 66 | borderRadius: BorderRadius.circular(SizeConfig.aspectRation * 10.0), 67 | boxShadow: [ 68 | BoxShadow( 69 | color: Colors.black, 70 | blurRadius: SizeConfig.aspectRation * 6.0, 71 | offset: Offset(0, 2), 72 | ), 73 | ], 74 | ); 75 | 76 | final loginHintTextStyle = TextStyle( 77 | color: Colors.white54, 78 | fontFamily: 'OpenSans', 79 | ); 80 | 81 | final loginCMSLabelStyle = TextStyle( 82 | color: Colors.white, 83 | fontWeight: FontWeight.bold, 84 | fontStyle: FontStyle.italic, 85 | fontFamily: 'OpenSans', 86 | ); 87 | 88 | bool getTheme(BuildContext context){ 89 | return Provider.of(context).darkTheme; 90 | } -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at amritapurifoss@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /lib/screens/attendance/absent.dart: -------------------------------------------------------------------------------- 1 | import 'package:cms_mobile/utilities/image_address.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:graphql_flutter/graphql_flutter.dart'; 4 | import 'package:intl/intl.dart'; 5 | 6 | // ignore: must_be_immutable 7 | class MembersAbsent extends StatefulWidget { 8 | DateTime selectedDate; 9 | 10 | MembersAbsent(DateTime selectedDate) { 11 | this.selectedDate = selectedDate; 12 | } 13 | 14 | @override 15 | AttendanceAbsent createState() => AttendanceAbsent(selectedDate); 16 | } 17 | 18 | class AttendanceAbsent extends State { 19 | DateTime selectedDate; 20 | 21 | AttendanceAbsent(DateTime selectedDate) { 22 | this.selectedDate = selectedDate; 23 | } 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Scaffold( 28 | body: Query( 29 | options: QueryOptions(documentNode: gql(_buildQuery())), 30 | builder: (QueryResult result, 31 | {VoidCallback refetch, FetchMore fetchMore}) { 32 | if (result.loading) { 33 | return Center( 34 | child: CircularProgressIndicator(), 35 | ); 36 | } 37 | if (result.data == null) { 38 | return Center( 39 | child: Text('Attendance not found'), 40 | ); 41 | } 42 | if (result.data['dailyAttendance']['membersAbsent'].length == 0) { 43 | return Center( 44 | child: Text('Woohoo!\nLooks like every one is present'), 45 | ); 46 | } 47 | print(result.data['dailyAttendance']['membersAbsent'][0]); 48 | return _attendanceList(result); 49 | }, 50 | ), 51 | ); 52 | } 53 | 54 | Widget _attendanceList(QueryResult result) { 55 | final attendance = result.data['dailyAttendance']; 56 | final membersAbsent = attendance['membersAbsent']; 57 | return ListView.separated( 58 | padding: EdgeInsets.symmetric(vertical: 16), 59 | itemCount: membersAbsent.length, 60 | itemBuilder: (context, index) { 61 | String url = attendance['membersAbsent'][index]['member']['avatar'] 62 | ['githubUsername']; 63 | if (url == null) { 64 | url = 'github'; 65 | } 66 | return ListTile( 67 | leading: new CircleAvatar( 68 | radius: 30, 69 | backgroundColor: Colors.grey, 70 | backgroundImage: NetworkImage(ImageAddressProvider.imageAddress( 71 | url, 72 | attendance['membersAbsent'][index]['member']['profile'] 73 | ['profilePic'])), 74 | ), 75 | title: 76 | Text(attendance['membersAbsent'][index]['member']['fullName']), 77 | ); 78 | }, 79 | separatorBuilder: (context, index) => Divider()); 80 | } 81 | 82 | String _buildQuery() { 83 | String query = ''' 84 | query { 85 | dailyAttendance(date: "${DateFormat("yyyy-MM-dd").format(selectedDate)}") { 86 | date 87 | membersAbsent { 88 | member { 89 | username 90 | fullName 91 | avatar { 92 | githubUsername 93 | } 94 | profile { 95 | profilePic 96 | } 97 | } 98 | } 99 | } 100 | }'''; 101 | return query; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: cms_mobile 2 | description: A new Flutter application for amfoss cms 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 0.1.0+1 15 | 16 | environment: 17 | sdk: ">=2.2.0 <3.0.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | 23 | # The following adds the Cupertino Icons font to your application. 24 | # Use with the CupertinoIcons class for iOS style icons. 25 | cupertino_icons: ^0.1.2 26 | graphql_flutter: ^3.0.1-beta.1 27 | intl: ^0.16.1 28 | moor_flutter: ^2.1.1 29 | url_launcher: ^5.4.2 30 | flutter_markdown: ^0.3.4 31 | html2md: ^0.5.1 32 | fl_chart: ^0.9.0 33 | flutter_icons: ^1.1.0 34 | flutter_offline: ^0.3.0 35 | toast: ^0.1.5 36 | provider: ^4.0.5 37 | shared_preferences: 0.5.7 38 | 39 | dev_dependencies: 40 | flutter_test: 41 | sdk: flutter 42 | moor_generator: ^2.4.0 43 | build_runner: 44 | 45 | flutter_launcher_icons: "^0.6.1" 46 | 47 | dependency_overrides: 48 | image: 2.0.7 49 | 50 | flutter_icons: 51 | #replace with the path to the icon whenever updating it 52 | android: true 53 | ios: true 54 | image_path_ios: "assets/launcher/icon.png" 55 | image_path_android: "assets/launcher/icon.png" 56 | adaptive_icon_background: "assets/launcher/background.png" 57 | adaptive_icon_foreground: "assets/launcher/foreground.png" 58 | # For information on the generic Dart part of this file, see the 59 | # following page: https://dart.dev/tools/pub/pubspec 60 | 61 | # The following section is specific to Flutter. 62 | flutter: 63 | 64 | # The following line ensures that the Material Icons font is 65 | # included with your application, so that you can use the icons in 66 | # the material Icons class. 67 | uses-material-design: true 68 | 69 | # To add assets to your application, add an assets section, like this: 70 | # - images/a_dot_burr.jpeg 71 | # - images/a_dot_ham.jpeg 72 | assets: 73 | - assets/images/amfoss.png 74 | - assets/images/amfoss_dark.png 75 | - assets/images/cms.jpg 76 | - assets/launcher/icon.png 77 | 78 | # An image asset can refer to one or more resolution-specific "variants", see 79 | # https://flutter.dev/assets-and-images/#resolution-aware. 80 | 81 | # For details regarding adding assets from package dependencies, see 82 | # https://flutter.dev/assets-and-images/#from-packages 83 | 84 | # To add custom fonts to your application, add a fonts section here, 85 | # in this "flutter" section. Each entry in this list should have a 86 | # "family" key with the font family name, and a "fonts" key with a 87 | # list giving the asset and other descriptors for the font. For 88 | # example: 89 | # fonts: 90 | # - family: Schyler 91 | # fonts: 92 | # - asset: fonts/Schyler-Regular.ttf 93 | # - asset: fonts/Schyler-Italic.ttf 94 | # style: italic 95 | # - family: Trajan Pro 96 | # fonts: 97 | # - asset: fonts/TrajanPro.ttf 98 | # - asset: fonts/TrajanPro_Bold.ttf 99 | # weight: 700 100 | # 101 | # For details regarding fonts from package dependencies, 102 | # see https://flutter.dev/custom-fonts/#from-packages 103 | -------------------------------------------------------------------------------- /lib/screens/attendance/present.dart: -------------------------------------------------------------------------------- 1 | import 'package:cms_mobile/utilities/image_address.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:graphql_flutter/graphql_flutter.dart'; 4 | import 'package:intl/intl.dart'; 5 | 6 | // ignore: must_be_immutable 7 | class MembersPresent extends StatefulWidget { 8 | DateTime selectedDate; 9 | 10 | MembersPresent(DateTime selectedDate) { 11 | this.selectedDate = selectedDate; 12 | } 13 | 14 | @override 15 | AttendancePresent createState() => AttendancePresent(selectedDate); 16 | } 17 | 18 | class AttendancePresent extends State { 19 | DateTime selectedDate; 20 | 21 | AttendancePresent(DateTime selectedDate) { 22 | this.selectedDate = selectedDate; 23 | } 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Scaffold( 28 | body: Query( 29 | options: QueryOptions(documentNode: gql(_buildQuery())), 30 | builder: (QueryResult result, 31 | {VoidCallback refetch, FetchMore fetchMore}) { 32 | if (result.loading) { 33 | return Center( 34 | child: CircularProgressIndicator(), 35 | ); 36 | } 37 | if (result.data == null) { 38 | return Center( 39 | child: Text('Attendance not found'), 40 | ); 41 | } 42 | if (result.data['dailyAttendance']['membersPresent'].length == 0) { 43 | return Center( 44 | child: Text('Oops!\nLooks like no one is present'), 45 | ); 46 | } 47 | print(result.data['dailyAttendance']['membersPresent'][0]); 48 | return _attendanceList(result); 49 | }, 50 | ), 51 | ); 52 | } 53 | 54 | Widget _attendanceList(QueryResult result) { 55 | final attendance = result.data['dailyAttendance']; 56 | final membersPresent = attendance['membersPresent']; 57 | return ListView.separated( 58 | padding: EdgeInsets.symmetric(vertical: 16), 59 | itemCount: membersPresent.length, 60 | itemBuilder: (context, index) { 61 | String url = attendance['membersPresent'][index]['member']['avatar'] 62 | ['githubUsername']; 63 | if (url == null) { 64 | url = 'github'; 65 | } 66 | return ListTile( 67 | leading: new CircleAvatar( 68 | radius: 30, 69 | backgroundColor: Colors.grey, 70 | backgroundImage: NetworkImage(ImageAddressProvider.imageAddress( 71 | url, 72 | attendance['membersPresent'][index]['member']['profile'] 73 | ['profilePic']))), 74 | title: 75 | Text(attendance['membersPresent'][index]['member']['fullName']), 76 | subtitle: Text( 77 | "duration: " + attendance['membersPresent'][index]['duration']), 78 | ); 79 | }, 80 | separatorBuilder: (context, index) => Divider()); 81 | } 82 | 83 | String _buildQuery() { 84 | String query = ''' 85 | query { 86 | dailyAttendance(date: "${DateFormat("yyyy-MM-dd").format(selectedDate)}") { 87 | date 88 | membersPresent { 89 | member { 90 | username 91 | fullName 92 | avatar { 93 | githubUsername 94 | } 95 | profile { 96 | profilePic 97 | } 98 | } 99 | duration 100 | } 101 | } 102 | }'''; 103 | return query; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/screens/attendance/attendance.dart: -------------------------------------------------------------------------------- 1 | import 'package:cms_mobile/utilities/constants.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/scheduler.dart'; 5 | import 'package:flutter_offline/flutter_offline.dart'; 6 | import 'package:intl/intl.dart'; 7 | 8 | import 'absent.dart' as absent; 9 | import 'present.dart' as present; 10 | 11 | class Attendance extends StatefulWidget { 12 | @override 13 | _Attendance createState() => _Attendance(); 14 | } 15 | 16 | class _Attendance extends State 17 | with SingleTickerProviderStateMixin { 18 | TabController tabController; 19 | bool isConnection = true; 20 | final GlobalKey _scaffoldKey = new GlobalKey(); 21 | 22 | DateTime selectedDate = DateTime.now(); 23 | 24 | @override 25 | void initState() { 26 | tabController = new TabController(vsync: this, length: 2); 27 | super.initState(); 28 | } 29 | 30 | @override 31 | void dispose() { 32 | tabController.dispose(); 33 | super.dispose(); 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | return OfflineBuilder( 39 | debounceDuration: Duration.zero, 40 | connectivityBuilder: (BuildContext context, 41 | ConnectivityResult connectivity, 42 | Widget child,) { 43 | if (connectivity == ConnectivityResult.none) { 44 | if (isConnection == true) { 45 | SchedulerBinding.instance.addPostFrameCallback((_) { 46 | final snackBar = 47 | SnackBar(content: Text('You dont have internet Connection')); 48 | _scaffoldKey.currentState.showSnackBar(snackBar); 49 | }); 50 | } 51 | 52 | isConnection = false; 53 | } else { 54 | if (isConnection == false) { 55 | final snackBar = 56 | SnackBar(content: Text('Your internet is live again')); 57 | _scaffoldKey.currentState.showSnackBar(snackBar); 58 | SchedulerBinding.instance.addPostFrameCallback((_) => 59 | setState(() { 60 | isConnection = true; 61 | })); 62 | } 63 | 64 | isConnection = true; 65 | } 66 | return child; 67 | }, 68 | child: Scaffold( 69 | key: _scaffoldKey, 70 | appBar: new AppBar( 71 | automaticallyImplyLeading: false, 72 | backgroundColor: appPrimaryColor, 73 | title: Text( 74 | "Attendance: ${DateFormat("yyyy-MM-dd").format(selectedDate)}"), 75 | leading: new IconButton( 76 | icon: new Icon(Icons.calendar_today), 77 | onPressed: () { 78 | setState(() { 79 | _selectDate(context); 80 | }); 81 | }), 82 | bottom: new TabBar( 83 | controller: tabController, 84 | tabs: [ 85 | new Tab( 86 | icon: new Icon(Icons.assignment_turned_in), 87 | text: "Present", 88 | ), 89 | new Tab( 90 | icon: new Icon(Icons.report), 91 | text: "Absent", 92 | ), 93 | ], 94 | ), 95 | ), 96 | body: new TabBarView( 97 | controller: tabController, 98 | children: [ 99 | new present.AttendancePresent(selectedDate).build(context), 100 | new absent.AttendanceAbsent(selectedDate).build(context), 101 | ], 102 | ), 103 | ), 104 | ); 105 | } 106 | 107 | Future _selectDate(BuildContext context) async { 108 | final DateTime picked = await showDatePicker( 109 | context: context, 110 | initialDate: selectedDate, 111 | firstDate: DateTime(2018, 1), 112 | lastDate: DateTime.now()); 113 | if (picked != null && picked != selectedDate) 114 | setState(() { 115 | selectedDate = picked; 116 | }); 117 | build(context); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /lib/screens/statusUpdate/members_didnot_sent.dart: -------------------------------------------------------------------------------- 1 | import 'package:cms_mobile/utilities/image_address.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:graphql_flutter/graphql_flutter.dart'; 4 | 5 | class MembersDidNotSend extends StatefulWidget { 6 | String selectedDate; 7 | 8 | MembersDidNotSend(this.selectedDate); 9 | 10 | @override 11 | MemberDidNotSendTab createState() => MemberDidNotSendTab(selectedDate); 12 | } 13 | 14 | class MemberDidNotSendTab extends State { 15 | String selectedDate; 16 | 17 | MemberDidNotSendTab(this.selectedDate); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | selectedDate = selectedDate.substring(0, 10); 22 | return Scaffold( 23 | body: Query( 24 | options: QueryOptions(documentNode: gql(_buildQuery())), 25 | builder: (QueryResult result, 26 | {VoidCallback refetch, FetchMore fetchMore}) { 27 | if (result.loading) { 28 | return Center( 29 | child: CircularProgressIndicator(), 30 | ); 31 | } 32 | if (result.data == null) { 33 | return Center( 34 | child: Text('Status Update not found'), 35 | ); 36 | } 37 | if (result.data['dailyStatusUpdates']['memberDidNotSend'].length == 38 | 0) { 39 | return Center( 40 | child: Text('Everyone Sent their Status Update'), 41 | ); 42 | } 43 | if (result.data['dailyStatusUpdates']['memberDidNotSend'].length == 44 | 0) { 45 | return Center( 46 | child: Text('Woohoo!\nEveryone sent an update today.'), 47 | ); 48 | } 49 | print(result.data['dailyStatusUpdates']['memberDidNotSend'][0]); 50 | return _membersSentList(result); 51 | }, 52 | ), 53 | ); 54 | } 55 | 56 | Widget _membersSentList(QueryResult result) { 57 | final membersDidNotSentList = result.data['dailyStatusUpdates']; 58 | final membersPresent = membersDidNotSentList['memberDidNotSend']; 59 | return ListView.separated( 60 | padding: EdgeInsets.symmetric(vertical: 10), 61 | itemCount: membersPresent.length, 62 | itemBuilder: (context, index) { 63 | String url = membersDidNotSentList['memberDidNotSend'][index] 64 | ['member']['avatar']['githubUsername']; 65 | if (url == null) { 66 | url = 'github'; 67 | } 68 | return ListTile( 69 | leading: new CircleAvatar( 70 | radius: 30, 71 | backgroundColor: Colors.grey, 72 | backgroundImage: NetworkImage(ImageAddressProvider.imageAddress( 73 | url, 74 | membersDidNotSentList['memberDidNotSend'][index]['member'] 75 | ['profile']['profilePic'])), 76 | ), 77 | title: Text(membersDidNotSentList['memberDidNotSend'][index] 78 | ['member']['fullName']), 79 | subtitle: Text("@" + 80 | membersDidNotSentList['memberDidNotSend'][index]['member'] 81 | ['username']), 82 | ); 83 | }, 84 | separatorBuilder: (context, index) => Divider()); 85 | } 86 | 87 | String _buildQuery() { 88 | return ''' 89 | query { 90 | dailyStatusUpdates(date: "$selectedDate") { 91 | date 92 | memberDidNotSend { 93 | member { 94 | username 95 | fullName 96 | avatar{ 97 | githubUsername 98 | } 99 | profile { 100 | profilePic 101 | } 102 | } 103 | } 104 | } 105 | }'''; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /lib/screens/profile/about.dart: -------------------------------------------------------------------------------- 1 | import 'package:cms_mobile/utilities/constants.dart'; 2 | import 'package:cms_mobile/utilities/theme_provider.dart'; 3 | import 'package:flutter/gestures.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:provider/provider.dart'; 6 | import 'package:url_launcher/url_launcher.dart'; 7 | 8 | import '../../utilities/sizeconfig.dart'; 9 | 10 | class About extends StatefulWidget { 11 | @override 12 | _AboutScreen createState() => _AboutScreen(); 13 | } 14 | 15 | class _AboutScreen extends State { 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | final currentTheme = Provider.of(context).darkTheme; 20 | return Scaffold( 21 | appBar: AppBar(backgroundColor: appPrimaryColor, title: Text("About")), 22 | body: Column( 23 | children: [ 24 | _appIcon(), 25 | _amFOSSLogo(currentTheme), 26 | _aboutText( 27 | "This is a flutter application for the amFOSS CMS. Club members can login into the CMS using the app and view club attendence, their profile and status updates.", 28 | null, 29 | null,currentTheme), 30 | _aboutText( 31 | "amFOSS is a student-run community with over 50+ members from Amrita Vishwa Vidyapeetham, Amritapuri. Know more about us ", 32 | "here", 33 | "https://amfoss.in",currentTheme), 34 | _links(Icons.code, 'The app is open source, with the code ', "here", 35 | "https://gitlab.com/amfoss/cms-mobile", currentTheme), 36 | _links(Icons.error, "Issues can be reported ", "here", 37 | "https://gitlab.com/amfoss/cms-mobile/-/issues",currentTheme), 38 | _links(Icons.person_outline, "App developers can be found ", "here", 39 | "https://gitlab.com/amfoss/cms-mobile/-/graphs/master",currentTheme) 40 | ], 41 | ), 42 | ); 43 | } 44 | 45 | Widget _appIcon() { 46 | return Container( 47 | padding: EdgeInsets.only(top: 10), 48 | child: Image.asset('assets/launcher/icon.png'), 49 | height: SizeConfig.heightFactor * 150, 50 | ); 51 | } 52 | 53 | Widget _amFOSSLogo(bool theme) { 54 | return Container( 55 | padding: EdgeInsets.only(top: 10, bottom: 20), 56 | child: theme ? Image.asset('assets/images/amfoss.png'):Image.asset('assets/images/amfoss_dark.png') , 57 | width: SizeConfig.screenWidth / 2.5, 58 | ); 59 | } 60 | 61 | Widget _aboutText(String normalText, String richText, String url, bool theme) { 62 | return ListTile( 63 | title: new RichText( 64 | text: new TextSpan( 65 | children: [ 66 | new TextSpan( 67 | text: normalText, 68 | style: new TextStyle(color: theme? Colors.white : Colors.black, fontSize: 14), 69 | ), 70 | new TextSpan( 71 | text: richText, 72 | style: new TextStyle(color: Colors.blue, fontSize: 14), 73 | recognizer: new TapGestureRecognizer() 74 | ..onTap = () { 75 | _launchURL(url); 76 | }, 77 | ), 78 | ], 79 | ), 80 | ), 81 | ); 82 | } 83 | 84 | Widget _links(IconData icon, String normalText, String richText, String url, bool theme) { 85 | return ListTile( 86 | leading: Icon(icon), 87 | title: new RichText( 88 | text: new TextSpan( 89 | children: [ 90 | new TextSpan( 91 | text: normalText, 92 | style: new TextStyle(color: theme ? Colors.white : Colors.black, fontSize: 14), 93 | ), 94 | new TextSpan( 95 | text: richText, 96 | style: new TextStyle(color: Colors.blue, fontSize: 14), 97 | recognizer: new TapGestureRecognizer() 98 | ..onTap = () { 99 | _launchURL(url); 100 | }, 101 | ), 102 | ], 103 | ), 104 | ), 105 | ); 106 | } 107 | 108 | _launchURL(String url) async { 109 | if (await canLaunch(url)) { 110 | await launch(url); 111 | } else { 112 | throw 'Could not launch $url'; 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /lib/screens/statusUpdate/members_sent.dart: -------------------------------------------------------------------------------- 1 | import 'package:cms_mobile/screens/statusUpdate/messages.dart'; 2 | import 'package:cms_mobile/utilities/image_address.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:graphql_flutter/graphql_flutter.dart'; 5 | 6 | class MembersSent extends StatefulWidget { 7 | final String slectedDate; 8 | 9 | const MembersSent(this.slectedDate); 10 | 11 | @override 12 | MembersSentTab createState() => MembersSentTab(slectedDate); 13 | } 14 | 15 | class MembersSentTab extends State { 16 | String selctedDate; 17 | 18 | MembersSentTab(this.selctedDate); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | selctedDate = selctedDate.substring(0, 10); 23 | return Scaffold( 24 | body: Query( 25 | options: QueryOptions(documentNode: gql(_buildQuery())), 26 | builder: (QueryResult result, 27 | {VoidCallback refetch, FetchMore fetchMore}) { 28 | if (result.loading) { 29 | return Center( 30 | child: CircularProgressIndicator(), 31 | ); 32 | } 33 | if (result.data == null) { 34 | return Center( 35 | child: Text('Status Update not found'), 36 | ); 37 | } 38 | if (result.data['dailyStatusUpdates']['membersSent'].length == 0) { 39 | return Center( 40 | child: Text('No one a sent status update yet'), 41 | ); 42 | } 43 | print(selctedDate); 44 | print(result.data['dailyStatusUpdates']['membersSent'][0]); 45 | return _membersSentList(result); 46 | }, 47 | ), 48 | ); 49 | } 50 | 51 | Widget _membersSentList(QueryResult result) { 52 | final membersSentList = result.data['dailyStatusUpdates']; 53 | final membersPresent = membersSentList['membersSent']; 54 | return ListView.separated( 55 | padding: EdgeInsets.symmetric(vertical: 10), 56 | itemCount: membersPresent.length, 57 | itemBuilder: (context, index) { 58 | String url = membersSentList['membersSent'][index]['member']['avatar'] 59 | ['githubUsername']; 60 | if (url == null) { 61 | url = 'github'; 62 | } 63 | return ListTile( 64 | leading: new CircleAvatar( 65 | radius: 30, 66 | backgroundColor: Colors.grey, 67 | backgroundImage: NetworkImage(ImageAddressProvider.imageAddress( 68 | url, 69 | membersSentList['membersSent'][index]['member']['profile'] 70 | ['profilePic'])), 71 | ), 72 | title: Text( 73 | membersSentList['membersSent'][index]['member']['fullName']), 74 | subtitle: Text("@" + 75 | membersSentList['membersSent'][index]['member']['username']), 76 | onTap: () { 77 | Navigator.push( 78 | context, 79 | MaterialPageRoute( 80 | builder: (context) => Messages( 81 | selctedDate, 82 | membersSentList['membersSent'][index]['member'] 83 | ['username']))); 84 | }, 85 | ); 86 | }, 87 | separatorBuilder: (context, index) => Divider()); 88 | } 89 | 90 | @override 91 | void initState() { 92 | super.initState(); 93 | } 94 | 95 | String _buildQuery() { 96 | return ''' 97 | query { 98 | dailyStatusUpdates(date: "$selctedDate") { 99 | date 100 | membersSent { 101 | member { 102 | username 103 | fullName 104 | avatar{ 105 | githubUsername 106 | } 107 | profile { 108 | profilePic 109 | } 110 | } 111 | } 112 | } 113 | }'''; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cms_mobile 2 | 3 | [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) 4 | [![Code of Conduct](https://img.shields.io/badge/%E2%9D%A4-code%20of%20conduct-e04545.svg?style=flat)](https://gitlab.com/amfoss/cms-mobile/-/blob/master/CODE_OF_CONDUCT.md) 5 | 6 | cms-mobile is a flutter application for the amFOSS CMS. Using the application, club members can login into the CMS and view club attendence, their profile and status updates. 7 | 8 | ## Getting Started 9 | 10 | These instructions will get you a copy of the project up and be running on your local machine for development and testing purposes. 11 | 12 | ### Prerequisites 13 | 14 | [Android Studio](https://developer.android.com/studio), with a recent version of the Android SDK. 15 | 16 | ### Setting up your development environment 17 | 18 | - Download and install Git. 19 | 20 | - Fork the [cms-mobile project](https://gitlab.com/amfoss/cms-mobile) 21 | 22 | - Clone your fork of the project locally. At the command line: 23 | ``` 24 | $ git clone https://gitlab.com/YOUR-GITLAB-USERNAME/cms-mobile 25 | ``` 26 | 27 | If you prefer not to use the command line, you can use Android Studio to create a new project from version control using 28 | ``` 29 | https://gitlab.com/YOUR-GITLAB-USERNAME/cms-mobile 30 | ``` 31 | 32 | Open the project in the folder of your clone from Android Studio and build the project. If there are any missing dependencies, install them first by clicking on the links provided by Android studio. Once the project is built successfully, run the project by clicking on the green arrow at the top of the screen. 33 | 34 | In order to run the project, you will need the Flutter SKD setup up. If this is your first Flutter project, below are a few useful resources: 35 | 36 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 37 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 38 | 39 | For help getting started with Flutter, view the Flutter 40 | [online documentation](https://flutter.dev/docs), which offers tutorials, 41 | samples, guidance on mobile development, and a full API reference. 42 | 43 | ## Screenshots 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 |
57 | 58 | ## Dependencies: 59 | 60 | [cupertino_icons](https://pub.dev/packages/cupertino_icons): 0.1.2 61 | 62 | [graphql_flutter](https://pub.dev/packages/graphql_flutter): 3.0.1-beta.1 63 | 64 | [intl](https://pub.dev/packages/intl): 0.16.1 65 | 66 | [moor_flutter](https://pub.dev/packages/moor_flutter): 2.1.1 67 | 68 | [url_launcher](https://pub.dev/packages/url_launcher): 5.4.2 69 | 70 | [flutter_markdown](https://pub.dev/packages/flutter_markdown): 0.3.4 71 | 72 | [html2md](https://pub.dev/packages/html2md): 0.5.1 73 | 74 | [date_range_picker](https://pub.dev/packages/date_range_picker): 1.0.6 75 | 76 | [fl_chart](https://pub.dev/packages/fl_chart): 0.9.0 77 | 78 | [flutter_icons](https://pub.dev/packages/flutter_icons): 1.1.0 79 | 80 | [toast](https://pub.dev/packages/toast): 0.1.5 81 | 82 | [provider](https://pub.dev/packages/provider): 4.0.5 83 | 84 | ## Get in Touch 85 | 86 | To contact us, you can create an issue over [here](https://gitlab.com/amfoss/cms-mobile/-/issues/). 87 | You can also participate in technical discussions and ask your doubts on the [IRC Freenode](https://webchat.freenode.net/). Just join in and participate in the discussions at the #amfoss channel. 88 | 89 | ## License 90 | This project is licensed under the [GNU General Public License v3.0](https://gitlab.com/amfoss/cms-mobile/blob/master/LICENSE). 91 | -------------------------------------------------------------------------------- /lib/screens/statusUpdate/status_update.dart: -------------------------------------------------------------------------------- 1 | import 'package:cms_mobile/utilities/constants.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/scheduler.dart'; 4 | import 'package:flutter_offline/flutter_offline.dart'; 5 | import 'package:intl/intl.dart'; 6 | 7 | import 'members_didnot_sent.dart' as memberDidNotSend; 8 | import 'members_sent.dart' as membersSent; 9 | 10 | class StatusUpdate extends StatefulWidget { 11 | final String appUsername; 12 | 13 | const StatusUpdate({this.appUsername}); 14 | 15 | @override 16 | _StatusUpdateScreen createState() => _StatusUpdateScreen(appUsername); 17 | } 18 | 19 | class _StatusUpdateScreen extends State 20 | with SingleTickerProviderStateMixin { 21 | String appUsername; 22 | bool isConnection = true; 23 | final GlobalKey _scaffoldKey = new GlobalKey(); 24 | 25 | _StatusUpdateScreen(String appUsername) { 26 | this.appUsername = appUsername; 27 | } 28 | 29 | TabController tabController; 30 | DateTime selectedDate = DateTime.now().subtract(Duration(hours: 29)); 31 | 32 | @override 33 | void initState() { 34 | tabController = new TabController(vsync: this, length: 2); 35 | super.initState(); 36 | } 37 | 38 | @override 39 | void dispose() { 40 | tabController.dispose(); 41 | super.dispose(); 42 | } 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | return OfflineBuilder( 47 | debounceDuration: Duration.zero, 48 | connectivityBuilder: (BuildContext context, 49 | ConnectivityResult connectivity, 50 | Widget child,) { 51 | if (connectivity == ConnectivityResult.none) { 52 | if (isConnection == true) { 53 | SchedulerBinding.instance.addPostFrameCallback((_) { 54 | final snackBar = SnackBar( 55 | content: Text('You dont have internet Connection')); 56 | _scaffoldKey.currentState.showSnackBar(snackBar); 57 | }); 58 | } 59 | 60 | isConnection = false; 61 | } else { 62 | if (isConnection == false) { 63 | final snackBar = 64 | SnackBar(content: Text('Your internet is live again')); 65 | _scaffoldKey.currentState.showSnackBar(snackBar); 66 | SchedulerBinding.instance.addPostFrameCallback((_) => 67 | setState(() { 68 | isConnection = true; 69 | })); 70 | } 71 | 72 | isConnection = true; 73 | } 74 | return child; 75 | }, 76 | child: Scaffold( 77 | key: _scaffoldKey, 78 | appBar: new AppBar( 79 | automaticallyImplyLeading: false, 80 | backgroundColor: appPrimaryColor, 81 | title: new Text( 82 | "Status update: ${DateFormat("yyyy-MM-dd").format( 83 | selectedDate)}"), 84 | leading: IconButton( 85 | icon: new Icon(Icons.calendar_today), 86 | onPressed: () => _selectDate(context), 87 | ), 88 | bottom: new TabBar( 89 | controller: tabController, 90 | tabs: [ 91 | new Tab( 92 | icon: new Icon(Icons.assignment_turned_in), 93 | text: "sent", 94 | ), 95 | new Tab( 96 | icon: new Icon(Icons.report), 97 | text: "Not sent", 98 | ), 99 | ], 100 | ), 101 | ), 102 | body: new TabBarView( 103 | controller: tabController, 104 | children: [ 105 | new membersSent.MembersSentTab(_getDate()).build(context), 106 | new memberDidNotSend.MemberDidNotSendTab(_getDate()).build(context), 107 | ], 108 | ), 109 | ), 110 | ); 111 | } 112 | 113 | String _getDate() { 114 | return selectedDate.toIso8601String(); 115 | } 116 | 117 | Future _selectDate(BuildContext context) async { 118 | final DateTime picked = await showDatePicker( 119 | context: context, 120 | initialDate: selectedDate, 121 | firstDate: DateTime(2018, 1), 122 | lastDate: DateTime.now().subtract(Duration(hours: 29))); 123 | if (picked != null && picked != selectedDate) 124 | setState(() { 125 | selectedDate = picked; 126 | }); 127 | build(context); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /lib/utilities/drawer.dart: -------------------------------------------------------------------------------- 1 | import 'package:cms_mobile/data/user_database.dart'; 2 | import 'package:cms_mobile/screens/attendance/statistics/attendance_stats.dart'; 3 | import 'package:cms_mobile/screens/home.dart'; 4 | import 'package:cms_mobile/screens/login_screen.dart'; 5 | import 'package:cms_mobile/screens/profile/about.dart'; 6 | import 'package:cms_mobile/screens/profile/update_profile.dart'; 7 | import 'package:cms_mobile/screens/statusUpdate/statistics/status_update_graphs.dart'; 8 | import 'package:cms_mobile/screens/statusUpdate/statistics/status_update_stats.dart'; 9 | import 'package:cms_mobile/screens/statusUpdate/userUpdates.dart'; 10 | import 'package:cms_mobile/utilities/constants.dart'; 11 | import 'package:cms_mobile/utilities/theme_provider.dart'; 12 | import 'package:flutter/material.dart'; 13 | import 'package:flutter_icons/flutter_icons.dart'; 14 | import 'package:provider/provider.dart'; 15 | 16 | class AppDrawer extends StatefulWidget { 17 | @override 18 | _AppDrawerState createState() => _AppDrawerState(); 19 | } 20 | 21 | class _AppDrawerState extends State { 22 | @override 23 | Widget build(BuildContext context) { 24 | final themeChange = Provider.of(context); 25 | return Drawer( 26 | child: ListView( 27 | padding: new EdgeInsets.only(top: 50), 28 | children: [ 29 | Padding( 30 | padding: EdgeInsets.only(left: 20), 31 | child: Text( 32 | HomePageScreen.username, 33 | style: messageLabelStyle, 34 | )), 35 | Divider(), 36 | _createDrawerItem( 37 | icon: Icons.account_circle, 38 | text: 'Update Profile', 39 | onTap: () => Navigator.push(context, 40 | MaterialPageRoute(builder: (context) => UpdateProfile()))), 41 | _createDrawerItem( 42 | icon: Icons.info, 43 | text: 'About', 44 | onTap: () => Navigator.push( 45 | context, MaterialPageRoute(builder: (context) => About()))), 46 | Divider(), 47 | _createDrawerItem( 48 | icon: FlutterIcons.graph_trend_fou, 49 | text: 'Status Update Stats', 50 | onTap: () => Navigator.push( 51 | context, 52 | MaterialPageRoute( 53 | builder: (context) => StatusUpdateStats()))), 54 | _createDrawerItem( 55 | icon: FlutterIcons.graph_trend_fou, 56 | text: 'Attendance Stats', 57 | onTap: () => Navigator.push(context, 58 | MaterialPageRoute(builder: (context) => AttendanceStats()))), 59 | _createDrawerItem( 60 | icon: FlutterIcons.graph_oct, 61 | text: 'Status Updates Overview', 62 | onTap: () => Navigator.push( 63 | context, 64 | MaterialPageRoute( 65 | builder: (context) => StatusUpdateGraphs()))), 66 | _createDrawerItem( 67 | icon: FlutterIcons.list_alt_faw5, 68 | text: 'Messages List', 69 | onTap: () => Navigator.push( 70 | context, 71 | MaterialPageRoute( 72 | builder: (context) => 73 | UserUpdates(HomePageScreen.username)))), 74 | Divider(), 75 | _createDrawerItem( 76 | icon: themeChange.darkTheme 77 | ? FlutterIcons.weather_sunny_mco 78 | : FlutterIcons.weather_night_mco, 79 | text: themeChange.darkTheme ? 'Light Mode' : 'Dark Mode', 80 | onTap: () => darkMode(themeChange)), 81 | Divider(), 82 | _createDrawerItem( 83 | icon: Icons.exit_to_app, text: 'Logout', onTap: () => logout()), 84 | Divider(), 85 | _createDrawerItem(icon: Icons.bug_report, text: 'Report an issue'), 86 | ListTile( 87 | title: Text('0.0.1'), 88 | onTap: () {}, 89 | ), 90 | ], 91 | ), 92 | ); 93 | } 94 | 95 | void darkMode(DarkThemeProvider themeChange) { 96 | if (themeChange.darkTheme) { 97 | themeChange.darkTheme = false; 98 | } else { 99 | themeChange.darkTheme = true; 100 | } 101 | } 102 | 103 | void logout() { 104 | final db = Provider.of(context, listen: false); 105 | db.getSingleUser().then((userFromDb) { 106 | db.deleteUser(userFromDb).then((onValue) { 107 | Navigator.pushReplacement( 108 | context, 109 | MaterialPageRoute(builder: (context) => LoginScreen()), 110 | ); 111 | }); 112 | }); 113 | } 114 | 115 | Widget _createDrawerItem( 116 | {IconData icon, String text, GestureTapCallback onTap}) { 117 | return ListTile( 118 | title: Row( 119 | children: [ 120 | Icon(icon), 121 | Padding( 122 | padding: EdgeInsets.only(left: 8.0), 123 | child: Text(text), 124 | ) 125 | ], 126 | ), 127 | onTap: onTap, 128 | ); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /lib/screens/statusUpdate/userUpdates.dart: -------------------------------------------------------------------------------- 1 | import 'package:cms_mobile/screens/home.dart'; 2 | import 'package:cms_mobile/utilities/constants.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_markdown/flutter_markdown.dart'; 5 | import 'package:graphql_flutter/graphql_flutter.dart'; 6 | import 'package:html2md/html2md.dart' as html2md; 7 | import 'package:intl/intl.dart'; 8 | 9 | class UserUpdates extends StatefulWidget { 10 | String username; 11 | 12 | UserUpdates(String username) { 13 | this.username = username; 14 | } 15 | 16 | @override 17 | _MessagesList createState() => _MessagesList(username); 18 | } 19 | 20 | class _MessagesList extends State { 21 | String username; 22 | static String _searchText = ""; 23 | Icon _searchIcon = new Icon(Icons.search); 24 | Widget _appBarTitle = new Text('Updates'); 25 | final TextEditingController _controller = new TextEditingController(); 26 | 27 | _MessagesList(String username) { 28 | this.username = username; 29 | _searchText = username; 30 | } 31 | 32 | @override 33 | void initState() { 34 | super.initState(); 35 | _controller.addListener(onChange); 36 | } 37 | 38 | void onChange(){ 39 | _searchText = _controller.text; 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | final ValueNotifier client = ValueNotifier( 45 | GraphQLClient(link: HomePageScreen.url, cache: InMemoryCache()), 46 | ); 47 | 48 | return GraphQLProvider( 49 | client: client, 50 | child: Scaffold( 51 | appBar: AppBar( 52 | backgroundColor: appPrimaryColor, 53 | title: _appBarTitle, 54 | actions: [ 55 | IconButton( 56 | icon: _searchIcon, 57 | onPressed: () { 58 | _searchPressed(); 59 | }, 60 | ), 61 | ], 62 | ), 63 | body: Query( 64 | options: QueryOptions(documentNode: gql(_buildQuery())), 65 | builder: (QueryResult result, 66 | {VoidCallback refetch, FetchMore fetchMore}) { 67 | if (result.loading) { 68 | return Center( 69 | child: CircularProgressIndicator(), 70 | ); 71 | } 72 | if (result.data == null) { 73 | return Center( 74 | child: Text('Status Update not found'), 75 | ); 76 | } 77 | if (result.data['getMemberStatusUpdates'].length == 0) { 78 | return Center( 79 | child: Text('Please enter a valid username'), 80 | ); 81 | } 82 | print(result.data['getMemberStatusUpdates'][0]['member']); 83 | return _membersSentList(result); 84 | }, 85 | ), 86 | ), 87 | ); 88 | } 89 | 90 | void _searchPressed() { 91 | setState(() { 92 | if (_searchIcon.icon == Icons.search) { 93 | _searchText = ""; 94 | _searchIcon = new Icon(Icons.done); 95 | _appBarTitle = new TextField( 96 | controller: _controller, 97 | decoration: new InputDecoration( 98 | prefixIcon: new Icon(Icons.search), hintText: 'Enter Username'), 99 | autofocus: true, 100 | ); 101 | } else { 102 | _searchIcon = new Icon(Icons.search); 103 | _appBarTitle = new Text('Updates'); 104 | build(context); 105 | } 106 | }); 107 | } 108 | 109 | Widget _membersSentList(QueryResult result) { 110 | final messagesList = result.data['getMemberStatusUpdates']; 111 | return ListView.separated( 112 | shrinkWrap: true, 113 | itemCount: messagesList.length, 114 | itemBuilder: (context, index) { 115 | return ListTile( 116 | title: Text( 117 | "Name: " + 118 | messagesList[index]['member']['fullName'].toString() + 119 | "\nDate: " + 120 | messagesList[index]['date'] + 121 | "\nTime: " + 122 | getDate(messagesList[index]['timestamp']) + 123 | "\nMessage:\n", 124 | style: userUpdateHeadings, 125 | ), 126 | subtitle: 127 | MarkdownBody(data: html2md.convert(messagesList[index]['message'])), 128 | ); 129 | }, 130 | separatorBuilder: (context, index) => Divider()); 131 | } 132 | 133 | String getDate(String date) { 134 | var dateTime = DateFormat("HH:mm:ss").parse(date.substring(11, 19), true); 135 | var dateLocal = dateTime.toLocal(); 136 | return dateLocal.toIso8601String().substring(11, 19); 137 | } 138 | 139 | String _buildQuery() { 140 | return ''' 141 | query { 142 | getMemberStatusUpdates(username:"$_searchText"){ 143 | message 144 | member{ 145 | avatar{ 146 | githubUsername 147 | } 148 | fullName 149 | } 150 | date 151 | timestamp 152 | } 153 | } 154 | '''; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /lib/screens/statusUpdate/messages.dart: -------------------------------------------------------------------------------- 1 | import 'package:cms_mobile/screens/home.dart'; 2 | import 'package:cms_mobile/utilities/constants.dart'; 3 | import 'package:cms_mobile/utilities/image_address.dart'; 4 | import 'package:cms_mobile/utilities/sizeconfig.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter/scheduler.dart'; 7 | import 'package:flutter_markdown/flutter_markdown.dart'; 8 | import 'package:flutter_offline/flutter_offline.dart'; 9 | import 'package:graphql_flutter/graphql_flutter.dart'; 10 | import 'package:html2md/html2md.dart' as html2md; 11 | import 'package:intl/intl.dart'; 12 | 13 | class Messages extends StatefulWidget { 14 | String selectedDate; 15 | String username; 16 | 17 | Messages(String selectedDate, String username) { 18 | this.selectedDate = selectedDate; 19 | this.username = username; 20 | } 21 | 22 | @override 23 | MessagesTab createState() => MessagesTab(selectedDate, username); 24 | } 25 | 26 | class MessagesTab extends State { 27 | String selectedDate; 28 | String username; 29 | bool isConnection = true; 30 | final GlobalKey _scaffoldKey = new GlobalKey(); 31 | 32 | MessagesTab(String selectedDate, String username) { 33 | this.selectedDate = selectedDate; 34 | this.username = username; 35 | } 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | final ValueNotifier client = ValueNotifier( 40 | GraphQLClient(link: HomePageScreen.url, cache: InMemoryCache()), 41 | ); 42 | 43 | return GraphQLProvider( 44 | client: client, 45 | child: OfflineBuilder( 46 | debounceDuration: Duration.zero, 47 | connectivityBuilder: (BuildContext context, 48 | ConnectivityResult connectivity, 49 | Widget child,) { 50 | if (connectivity == ConnectivityResult.none) { 51 | if (isConnection == true) { 52 | SchedulerBinding.instance.addPostFrameCallback((_) { 53 | final snackBar = SnackBar( 54 | content: Text('You dont have internet Connection')); 55 | _scaffoldKey.currentState.showSnackBar(snackBar); 56 | }); 57 | } 58 | 59 | isConnection = false; 60 | } else { 61 | if (isConnection == false) { 62 | final snackBar = 63 | SnackBar(content: Text('Your internet is live again')); 64 | _scaffoldKey.currentState.showSnackBar(snackBar); 65 | SchedulerBinding.instance 66 | .addPostFrameCallback((_) => 67 | setState(() { 68 | isConnection = true; 69 | })); 70 | } 71 | 72 | isConnection = true; 73 | } 74 | return child; 75 | }, 76 | child: Scaffold( 77 | key: _scaffoldKey, 78 | appBar: AppBar( 79 | backgroundColor: appPrimaryColor, 80 | title: Text("Status update message"), 81 | ), 82 | body: Query( 83 | options: QueryOptions(documentNode: gql(_buildQuery())), 84 | builder: (QueryResult result, 85 | {VoidCallback refetch, FetchMore fetchMore}) { 86 | if (result.loading) { 87 | return Center( 88 | child: CircularProgressIndicator(), 89 | ); 90 | } 91 | if (result.data == null) { 92 | return Center( 93 | child: Text('Status Update not found'), 94 | ); 95 | } 96 | if (result.data['getMemberStatusUpdates'].length == 0) { 97 | return Center( 98 | child: Text('No status updates for this date'), 99 | ); 100 | } 101 | print(selectedDate); 102 | print(result.data['getMemberStatusUpdates'][0]['member']); 103 | return _membersSentList(result); 104 | }, 105 | ), 106 | ), 107 | ), 108 | ); 109 | } 110 | 111 | Widget _membersSentList(QueryResult result) { 112 | final messagesList = result.data['getMemberStatusUpdates']; 113 | return Column( 114 | children: [ 115 | SizedBox(height: SizeConfig.heightFactor * 20), 116 | ListView.builder( 117 | shrinkWrap: true, 118 | itemCount: messagesList.length, 119 | itemBuilder: (context, index) { 120 | String url = 121 | messagesList[index]['member']['avatar']['githubUsername']; 122 | if (url == null) { 123 | url = 'github'; 124 | } 125 | return ListTile( 126 | leading: new CircleAvatar( 127 | radius: 30, 128 | backgroundColor: Colors.grey, 129 | backgroundImage: NetworkImage( 130 | ImageAddressProvider.imageAddress( 131 | url, 132 | messagesList[index]['member']['profile'] 133 | ['profilePic'])), 134 | ), 135 | title: 136 | Text(messagesList[index]['member']['fullName'].toString()), 137 | subtitle: Text("Date: " + 138 | selectedDate + 139 | "\n" + 140 | "Time: " + 141 | getDate(messagesList[index]['timestamp'])), 142 | ); 143 | }), 144 | SizedBox( 145 | height: SizeConfig.heightFactor * 40, 146 | ), 147 | Expanded( 148 | child: Padding( 149 | padding: EdgeInsets.only(left: 20), 150 | child: Align( 151 | alignment: Alignment.topLeft, 152 | child: Column( 153 | children: [ 154 | Align( 155 | alignment: Alignment.topLeft, 156 | child: Text( 157 | "Message: ", 158 | style: messageLabelStyle, 159 | )), 160 | SizedBox( 161 | height: SizeConfig.heightFactor * 15, 162 | ), 163 | MarkdownBody( 164 | data: html2md.convert(messagesList[0]['message'])) 165 | ], 166 | )), 167 | ), 168 | ), 169 | ], 170 | ); 171 | } 172 | 173 | @override 174 | void initState() { 175 | super.initState(); 176 | } 177 | 178 | String getDate(String date) { 179 | var dateTime = DateFormat("HH:mm:ss").parse(date.substring(11, 19), true); 180 | var dateLocal = dateTime.toLocal(); 181 | return dateLocal.toIso8601String().substring(11, 19); 182 | } 183 | 184 | String _buildQuery() { 185 | return ''' 186 | query { 187 | getMemberStatusUpdates(username:"$username", date:"$selectedDate"){ 188 | message 189 | member{ 190 | avatar{ 191 | githubUsername 192 | } 193 | fullName 194 | profile { 195 | profilePic 196 | } 197 | } 198 | timestamp 199 | } 200 | } 201 | '''; 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /lib/data/user_database.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'user_database.dart'; 4 | 5 | // ************************************************************************** 6 | // MoorGenerator 7 | // ************************************************************************** 8 | 9 | // ignore_for_file: unnecessary_brace_in_string_interps, unnecessary_this 10 | class User extends DataClass implements Insertable { 11 | final String username; 12 | final String authToken; 13 | final String refreshToken; 14 | User({this.username, this.authToken, this.refreshToken}); 15 | factory User.fromData(Map data, GeneratedDatabase db, 16 | {String prefix}) { 17 | final effectivePrefix = prefix ?? ''; 18 | final stringType = db.typeSystem.forDartType(); 19 | return User( 20 | username: stringType 21 | .mapFromDatabaseResponse(data['${effectivePrefix}username']), 22 | authToken: stringType 23 | .mapFromDatabaseResponse(data['${effectivePrefix}auth_token']), 24 | refreshToken: stringType 25 | .mapFromDatabaseResponse(data['${effectivePrefix}refresh_token']), 26 | ); 27 | } 28 | factory User.fromJson(Map json, 29 | {ValueSerializer serializer}) { 30 | serializer ??= moorRuntimeOptions.defaultSerializer; 31 | return User( 32 | username: serializer.fromJson(json['username']), 33 | authToken: serializer.fromJson(json['authToken']), 34 | refreshToken: serializer.fromJson(json['refreshToken']), 35 | ); 36 | } 37 | @override 38 | Map toJson({ValueSerializer serializer}) { 39 | serializer ??= moorRuntimeOptions.defaultSerializer; 40 | return { 41 | 'username': serializer.toJson(username), 42 | 'authToken': serializer.toJson(authToken), 43 | 'refreshToken': serializer.toJson(refreshToken), 44 | }; 45 | } 46 | 47 | @override 48 | UsersCompanion createCompanion(bool nullToAbsent) { 49 | return UsersCompanion( 50 | username: username == null && nullToAbsent 51 | ? const Value.absent() 52 | : Value(username), 53 | authToken: authToken == null && nullToAbsent 54 | ? const Value.absent() 55 | : Value(authToken), 56 | refreshToken: refreshToken == null && nullToAbsent 57 | ? const Value.absent() 58 | : Value(refreshToken), 59 | ); 60 | } 61 | 62 | User copyWith({String username, String authToken, String refreshToken}) => 63 | User( 64 | username: username ?? this.username, 65 | authToken: authToken ?? this.authToken, 66 | refreshToken: refreshToken ?? this.refreshToken, 67 | ); 68 | @override 69 | String toString() { 70 | return (StringBuffer('User(') 71 | ..write('username: $username, ') 72 | ..write('authToken: $authToken, ') 73 | ..write('refreshToken: $refreshToken') 74 | ..write(')')) 75 | .toString(); 76 | } 77 | 78 | @override 79 | int get hashCode => $mrjf($mrjc( 80 | username.hashCode, $mrjc(authToken.hashCode, refreshToken.hashCode))); 81 | @override 82 | bool operator ==(dynamic other) => 83 | identical(this, other) || 84 | (other is User && 85 | other.username == this.username && 86 | other.authToken == this.authToken && 87 | other.refreshToken == this.refreshToken); 88 | } 89 | 90 | class UsersCompanion extends UpdateCompanion { 91 | final Value username; 92 | final Value authToken; 93 | final Value refreshToken; 94 | const UsersCompanion({ 95 | this.username = const Value.absent(), 96 | this.authToken = const Value.absent(), 97 | this.refreshToken = const Value.absent(), 98 | }); 99 | UsersCompanion.insert({ 100 | this.username = const Value.absent(), 101 | this.authToken = const Value.absent(), 102 | this.refreshToken = const Value.absent(), 103 | }); 104 | UsersCompanion copyWith( 105 | {Value username, 106 | Value authToken, 107 | Value refreshToken}) { 108 | return UsersCompanion( 109 | username: username ?? this.username, 110 | authToken: authToken ?? this.authToken, 111 | refreshToken: refreshToken ?? this.refreshToken, 112 | ); 113 | } 114 | } 115 | 116 | class $UsersTable extends Users with TableInfo<$UsersTable, User> { 117 | final GeneratedDatabase _db; 118 | final String _alias; 119 | $UsersTable(this._db, [this._alias]); 120 | final VerificationMeta _usernameMeta = const VerificationMeta('username'); 121 | GeneratedTextColumn _username; 122 | @override 123 | GeneratedTextColumn get username => _username ??= _constructUsername(); 124 | GeneratedTextColumn _constructUsername() { 125 | return GeneratedTextColumn( 126 | 'username', 127 | $tableName, 128 | true, 129 | ); 130 | } 131 | 132 | final VerificationMeta _authTokenMeta = const VerificationMeta('authToken'); 133 | GeneratedTextColumn _authToken; 134 | @override 135 | GeneratedTextColumn get authToken => _authToken ??= _constructAuthToken(); 136 | GeneratedTextColumn _constructAuthToken() { 137 | return GeneratedTextColumn( 138 | 'auth_token', 139 | $tableName, 140 | true, 141 | ); 142 | } 143 | 144 | final VerificationMeta _refreshTokenMeta = 145 | const VerificationMeta('refreshToken'); 146 | GeneratedTextColumn _refreshToken; 147 | @override 148 | GeneratedTextColumn get refreshToken => 149 | _refreshToken ??= _constructRefreshToken(); 150 | GeneratedTextColumn _constructRefreshToken() { 151 | return GeneratedTextColumn( 152 | 'refresh_token', 153 | $tableName, 154 | true, 155 | ); 156 | } 157 | 158 | @override 159 | List get $columns => [username, authToken, refreshToken]; 160 | @override 161 | $UsersTable get asDslTable => this; 162 | @override 163 | String get $tableName => _alias ?? 'users'; 164 | @override 165 | final String actualTableName = 'users'; 166 | @override 167 | VerificationContext validateIntegrity(UsersCompanion d, 168 | {bool isInserting = false}) { 169 | final context = VerificationContext(); 170 | if (d.username.present) { 171 | context.handle(_usernameMeta, 172 | username.isAcceptableValue(d.username.value, _usernameMeta)); 173 | } 174 | if (d.authToken.present) { 175 | context.handle(_authTokenMeta, 176 | authToken.isAcceptableValue(d.authToken.value, _authTokenMeta)); 177 | } 178 | if (d.refreshToken.present) { 179 | context.handle( 180 | _refreshTokenMeta, 181 | refreshToken.isAcceptableValue( 182 | d.refreshToken.value, _refreshTokenMeta)); 183 | } 184 | return context; 185 | } 186 | 187 | @override 188 | Set get $primaryKey => {username}; 189 | @override 190 | User map(Map data, {String tablePrefix}) { 191 | final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : null; 192 | return User.fromData(data, _db, prefix: effectivePrefix); 193 | } 194 | 195 | @override 196 | Map entityToSql(UsersCompanion d) { 197 | final map = {}; 198 | if (d.username.present) { 199 | map['username'] = Variable(d.username.value); 200 | } 201 | if (d.authToken.present) { 202 | map['auth_token'] = Variable(d.authToken.value); 203 | } 204 | if (d.refreshToken.present) { 205 | map['refresh_token'] = Variable(d.refreshToken.value); 206 | } 207 | return map; 208 | } 209 | 210 | @override 211 | $UsersTable createAlias(String alias) { 212 | return $UsersTable(_db, alias); 213 | } 214 | } 215 | 216 | abstract class _$AppDatabase extends GeneratedDatabase { 217 | _$AppDatabase(QueryExecutor e) : super(SqlTypeSystem.defaultInstance, e); 218 | $UsersTable _users; 219 | $UsersTable get users => _users ??= $UsersTable(this); 220 | @override 221 | Iterable get allTables => allSchemaEntities.whereType(); 222 | @override 223 | List get allSchemaEntities => [users]; 224 | } 225 | -------------------------------------------------------------------------------- /lib/screens/statusUpdate/statistics/status_update_stats.dart: -------------------------------------------------------------------------------- 1 | import 'package:cms_mobile/screens/home.dart'; 2 | import 'package:cms_mobile/utilities/constants.dart'; 3 | import 'package:cms_mobile/utilities/image_address.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/scheduler.dart'; 6 | import 'package:flutter_offline/flutter_offline.dart'; 7 | import 'package:graphql_flutter/graphql_flutter.dart'; 8 | import 'package:intl/intl.dart'; 9 | 10 | class StatusUpdateStats extends StatefulWidget { 11 | @override 12 | _StatusUpdateStats createState() => _StatusUpdateStats(); 13 | } 14 | 15 | class _StatusUpdateStats extends State 16 | with SingleTickerProviderStateMixin { 17 | TabController tabController; 18 | bool isConnection = true; 19 | final GlobalKey _scaffoldKey = new GlobalKey(); 20 | 21 | DateTime initialDate = DateTime.now().subtract(Duration(days: 7)); 22 | DateTime lastDate = DateTime.now().subtract(Duration(hours: 29)); 23 | 24 | @override 25 | void initState() { 26 | tabController = new TabController(vsync: this, length: 2); 27 | super.initState(); 28 | } 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | final ValueNotifier client = ValueNotifier( 33 | GraphQLClient(link: HomePageScreen.url, cache: InMemoryCache()), 34 | ); 35 | 36 | return GraphQLProvider( 37 | client: client, 38 | child: OfflineBuilder( 39 | debounceDuration: Duration.zero, 40 | connectivityBuilder: (BuildContext context, 41 | ConnectivityResult connectivity, 42 | Widget child,) { 43 | if (connectivity == ConnectivityResult.none) { 44 | if (isConnection == true) { 45 | SchedulerBinding.instance.addPostFrameCallback((_) { 46 | final snackBar = SnackBar( 47 | content: Text('You dont have internet Connection')); 48 | _scaffoldKey.currentState.showSnackBar(snackBar); 49 | }); 50 | } 51 | 52 | isConnection = false; 53 | } else { 54 | if (isConnection == false) { 55 | final snackBar = 56 | SnackBar(content: Text('Your internet is live again')); 57 | _scaffoldKey.currentState.showSnackBar(snackBar); 58 | SchedulerBinding.instance 59 | .addPostFrameCallback((_) => 60 | setState(() { 61 | isConnection = true; 62 | })); 63 | } 64 | 65 | isConnection = true; 66 | } 67 | return child; 68 | }, 69 | child: Scaffold( 70 | key: _scaffoldKey, 71 | appBar: AppBar( 72 | backgroundColor: appPrimaryColor, 73 | title: Text("Status Update stats" + 74 | "\n" + 75 | _getFormatedDate(initialDate) + 76 | " - " + 77 | _getFormatedDate(lastDate)), 78 | bottom: new TabBar( 79 | controller: tabController, 80 | tabs: [ 81 | new Tab( 82 | icon: new Icon(Icons.assignment_turned_in), 83 | text: "Top 5", 84 | ), 85 | new Tab( 86 | icon: new Icon(Icons.report), 87 | text: "Worst 5", 88 | ), 89 | ], 90 | ), 91 | leading: IconButton( 92 | icon: new Icon(Icons.calendar_today), 93 | onPressed: () { 94 | setState(() { 95 | dateTimeRangePicker(); 96 | });} 97 | ), 98 | ), 99 | body: Query( 100 | options: QueryOptions(documentNode: gql(_buildQuery())), 101 | builder: (QueryResult result, 102 | {VoidCallback refetch, FetchMore fetchMore}) { 103 | if (result.loading) { 104 | return Center( 105 | child: CircularProgressIndicator(), 106 | ); 107 | } 108 | if (result.data == null) { 109 | return Center( 110 | child: Text('Status Update not found'), 111 | ); 112 | } 113 | if (result.data['clubStatusUpdate']['memberStats'].length == 0) { 114 | return Center( 115 | child: Text('No one a sent status update yet'), 116 | ); 117 | } 118 | print(result.data['clubStatusUpdate']['memberStats'][0]); 119 | return new TabBarView( 120 | controller: tabController, 121 | children: [_topFive(result), _worstFive(result)], 122 | ); 123 | }, 124 | ), 125 | ), 126 | ), 127 | ); 128 | } 129 | 130 | dateTimeRangePicker() async { 131 | DateTimeRange picked = await showDateRangePicker( 132 | context: context, 133 | firstDate: DateTime(DateTime.now().year - 5), 134 | lastDate: lastDate, 135 | initialDateRange: DateTimeRange( 136 | end: lastDate, 137 | start: initialDate)); 138 | if (picked != null) 139 | setState(() { 140 | lastDate = picked.end; 141 | initialDate = picked.start; 142 | }); 143 | build(context); 144 | } 145 | 146 | 147 | @override 148 | void dispose() { 149 | tabController.dispose(); 150 | super.dispose(); 151 | } 152 | 153 | Widget _topFive(QueryResult result) { 154 | var topFiveList = result.data['clubStatusUpdate']; 155 | return ListView.separated( 156 | itemCount: 5, 157 | itemBuilder: (context, index) { 158 | String url = topFiveList['memberStats'][index]['user']['avatar'] 159 | ['githubUsername']; 160 | if (url == null) { 161 | url = 'github'; 162 | } 163 | return ListTile( 164 | leading: new CircleAvatar( 165 | radius: 30, 166 | backgroundColor: Colors.grey, 167 | backgroundImage: NetworkImage(ImageAddressProvider.imageAddress( 168 | url, 169 | topFiveList['memberStats'][index]['user']['profile'] 170 | ['profilePic'])), 171 | ), 172 | title: Text(topFiveList['memberStats'][index]['user']['fullName']), 173 | subtitle: Text( 174 | "count: " + topFiveList['memberStats'][index]['statusCount']), 175 | ); 176 | }, 177 | separatorBuilder: (context, index) => Divider()); 178 | } 179 | 180 | Widget _worstFive(QueryResult result) { 181 | var worstFiveList = result.data['clubStatusUpdate']; 182 | var lentgh = worstFiveList['memberStats'].length; 183 | return ListView.separated( 184 | itemCount: 5, 185 | itemBuilder: (context, index) { 186 | String url = worstFiveList['memberStats'][lentgh - index - 1]['user'] 187 | ['avatar']['githubUsername']; 188 | if (url == null) { 189 | url = 'github'; 190 | } 191 | return ListTile( 192 | leading: new CircleAvatar( 193 | radius: 30, 194 | backgroundColor: Colors.grey, 195 | backgroundImage: NetworkImage(ImageAddressProvider.imageAddress( 196 | url, 197 | worstFiveList['memberStats'][lentgh - index - 1]['user'] 198 | ['profile']['profilePic'])), 199 | ), 200 | title: Text(worstFiveList['memberStats'][lentgh - index - 1]['user'] 201 | ['fullName']), 202 | subtitle: Text("count: " + 203 | worstFiveList['memberStats'][lentgh - index - 1] 204 | ['statusCount']), 205 | ); 206 | }, 207 | separatorBuilder: (context, index) => Divider()); 208 | } 209 | 210 | String _getFormatedDate(DateTime dateTime) { 211 | return DateFormat("yyyy-MM-dd").format(dateTime); 212 | } 213 | 214 | String _buildQuery() { 215 | return ''' 216 | query{ 217 | clubStatusUpdate(startDate: "${DateFormat("yyyy-MM-dd").format(initialDate)}", endDate: "${DateFormat("yyyy-MM-dd").format(lastDate)}") 218 | { 219 | memberStats 220 | { 221 | user 222 | { 223 | fullName 224 | avatar 225 | { 226 | githubUsername 227 | } 228 | profile { 229 | profilePic 230 | } 231 | } 232 | statusCount 233 | } 234 | } 235 | } 236 | '''; 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /lib/screens/attendance/statistics/attendance_stats.dart: -------------------------------------------------------------------------------- 1 | import 'package:cms_mobile/screens/home.dart'; 2 | import 'package:cms_mobile/utilities/constants.dart'; 3 | import 'package:cms_mobile/utilities/image_address.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/scheduler.dart'; 6 | import 'package:flutter_offline/flutter_offline.dart'; 7 | import 'package:graphql_flutter/graphql_flutter.dart'; 8 | import 'package:intl/intl.dart'; 9 | 10 | class AttendanceStats extends StatefulWidget { 11 | @override 12 | _AttendanceStats createState() => _AttendanceStats(); 13 | } 14 | 15 | class _AttendanceStats extends State 16 | with SingleTickerProviderStateMixin { 17 | TabController tabController; 18 | bool isConnection = true; 19 | final GlobalKey _scaffoldKey = new GlobalKey(); 20 | 21 | DateTime initialDate = DateTime.now().subtract(Duration(days: 7)); 22 | DateTime lastDate = DateTime.now(); 23 | 24 | @override 25 | void initState() { 26 | tabController = new TabController(vsync: this, length: 2); 27 | super.initState(); 28 | } 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | final ValueNotifier client = ValueNotifier( 33 | GraphQLClient(link: HomePageScreen.url, cache: InMemoryCache()), 34 | ); 35 | 36 | return GraphQLProvider( 37 | client: client, 38 | child: OfflineBuilder( 39 | debounceDuration: Duration.zero, 40 | connectivityBuilder: (BuildContext context, 41 | ConnectivityResult connectivity, 42 | Widget child,) { 43 | if (connectivity == ConnectivityResult.none) { 44 | if (isConnection == true) { 45 | SchedulerBinding.instance.addPostFrameCallback((_) { 46 | final snackBar = SnackBar( 47 | content: Text('You dont have internet Connection')); 48 | _scaffoldKey.currentState.showSnackBar(snackBar); 49 | }); 50 | } 51 | 52 | isConnection = false; 53 | } else { 54 | if (isConnection == false) { 55 | final snackBar = 56 | SnackBar(content: Text('Your internet is live again')); 57 | _scaffoldKey.currentState.showSnackBar(snackBar); 58 | SchedulerBinding.instance 59 | .addPostFrameCallback((_) => 60 | setState(() { 61 | isConnection = true; 62 | })); 63 | } 64 | 65 | isConnection = true; 66 | } 67 | return child; 68 | }, 69 | child: Scaffold( 70 | key: _scaffoldKey, 71 | appBar: AppBar( 72 | backgroundColor: appPrimaryColor, 73 | title: Text("Attendance stats" + 74 | "\n" + 75 | _getFormatedDate(initialDate) + 76 | " - " + 77 | _getFormatedDate(lastDate)), 78 | bottom: new TabBar( 79 | controller: tabController, 80 | tabs: [ 81 | new Tab( 82 | icon: new Icon(Icons.assignment_turned_in), 83 | text: "Top 5", 84 | ), 85 | new Tab( 86 | icon: new Icon(Icons.report), 87 | text: "Worst 5", 88 | ), 89 | ], 90 | ), 91 | leading: IconButton( 92 | icon: new Icon(Icons.calendar_today), 93 | onPressed: () { 94 | setState(() { 95 | dateTimeRangePicker(); 96 | });} 97 | ), 98 | ), 99 | body: Query( 100 | options: QueryOptions(documentNode: gql(_buildQuery())), 101 | builder: (QueryResult result, 102 | {VoidCallback refetch, FetchMore fetchMore}) { 103 | if (result.loading) { 104 | return Center( 105 | child: CircularProgressIndicator(), 106 | ); 107 | } 108 | if (result.data == null) { 109 | return Center( 110 | child: Text('Attendance not found'), 111 | ); 112 | } 113 | if (result.data['clubAttendance']['memberStats'].length == 0) { 114 | return Center( 115 | child: Text('No attendance logged for these days.'), 116 | ); 117 | } 118 | print(result.data['clubAttendance']['memberStats'][0]); 119 | return new TabBarView( 120 | controller: tabController, 121 | children: [_topFive(result), _worstFive(result)], 122 | ); 123 | }, 124 | ), 125 | ), 126 | ), 127 | ); 128 | } 129 | 130 | dateTimeRangePicker() async { 131 | DateTimeRange picked = await showDateRangePicker( 132 | context: context, 133 | firstDate: DateTime(DateTime.now().year - 5), 134 | lastDate: lastDate, 135 | initialDateRange: DateTimeRange( 136 | end: lastDate, 137 | start: initialDate)); 138 | if (picked != null) 139 | setState(() { 140 | lastDate = picked.end; 141 | initialDate = picked.start; 142 | }); 143 | build(context); 144 | } 145 | 146 | @override 147 | void dispose() { 148 | tabController.dispose(); 149 | super.dispose(); 150 | } 151 | 152 | Widget _topFive(QueryResult result) { 153 | var topFiveList = result.data['clubAttendance']; 154 | return ListView.separated( 155 | itemCount: 5, 156 | itemBuilder: (context, index) { 157 | String url = topFiveList['memberStats'][index]['user']['avatar'] 158 | ['githubUsername']; 159 | if (url == null) { 160 | url = 'github'; 161 | } 162 | return ListTile( 163 | leading: new CircleAvatar( 164 | radius: 30, 165 | backgroundColor: Colors.grey, 166 | backgroundImage: NetworkImage(ImageAddressProvider.imageAddress( 167 | url, 168 | topFiveList['memberStats'][index]['user']['profile'] 169 | ['profilePic'])), 170 | ), 171 | title: Text(topFiveList['memberStats'][index]['user']['fullName']), 172 | subtitle: Text("Count: " + 173 | topFiveList['memberStats'][index]['presentCount'] + 174 | "\nTotal Time: " + 175 | topFiveList['memberStats'][index]['totalDuration'] + 176 | " hours"), 177 | ); 178 | }, 179 | separatorBuilder: (context, index) => Divider()); 180 | } 181 | 182 | Widget _worstFive(QueryResult result) { 183 | var worstFiveList = result.data['clubAttendance']; 184 | var lentgh = worstFiveList['memberStats'].length; 185 | return ListView.separated( 186 | itemCount: 5, 187 | itemBuilder: (context, index) { 188 | String url = worstFiveList['memberStats'][lentgh - index - 1]['user'] 189 | ['avatar']['githubUsername']; 190 | if (url == null) { 191 | url = 'github'; 192 | } 193 | return ListTile( 194 | leading: new CircleAvatar( 195 | radius: 30, 196 | backgroundColor: Colors.grey, 197 | backgroundImage: NetworkImage(ImageAddressProvider.imageAddress( 198 | url, 199 | worstFiveList['memberStats'][lentgh - index - 1]['user'] 200 | ['profile']['profilePic'])), 201 | ), 202 | title: Text(worstFiveList['memberStats'][lentgh - index - 1]['user'] 203 | ['fullName']), 204 | subtitle: Text("count: " + 205 | worstFiveList['memberStats'][lentgh - index - 1] 206 | ['presentCount'] + 207 | "\nTotal TIme: " + 208 | worstFiveList['memberStats'][lentgh - index - 1] 209 | ['totalDuration'] + 210 | " hours"), 211 | ); 212 | }, 213 | separatorBuilder: (context, index) => Divider()); 214 | } 215 | 216 | String _getFormatedDate(DateTime dateTime) { 217 | return DateFormat("yyyy-MM-dd").format(dateTime); 218 | } 219 | 220 | String _buildQuery() { 221 | return ''' 222 | query{ 223 | clubAttendance(startDate: "${DateFormat("yyyy-MM-dd").format(initialDate)}", endDate: "${DateFormat("yyyy-MM-dd").format(lastDate)}") 224 | { 225 | memberStats{ 226 | user { 227 | fullName 228 | avatar { 229 | githubUsername 230 | } 231 | profile{ 232 | profilePic 233 | } 234 | } 235 | presentCount 236 | avgDuration 237 | totalDuration 238 | } 239 | } 240 | } 241 | '''; 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /lib/screens/profile/profile.dart: -------------------------------------------------------------------------------- 1 | import 'package:cms_mobile/utilities/drawer.dart'; 2 | import 'package:cms_mobile/utilities/image_address.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/scheduler.dart'; 5 | import 'package:flutter_icons/flutter_icons.dart'; 6 | import 'package:flutter_offline/flutter_offline.dart'; 7 | import 'package:graphql_flutter/graphql_flutter.dart'; 8 | import 'package:intl/intl.dart'; 9 | import 'package:url_launcher/url_launcher.dart'; 10 | 11 | import '../../utilities/constants.dart'; 12 | 13 | class Profile extends StatefulWidget { 14 | final String username; 15 | 16 | const Profile({this.username}); 17 | 18 | @override 19 | _Profile createState() => _Profile(username); 20 | } 21 | 22 | class _Profile extends State { 23 | final String username; 24 | bool isConnection = true; 25 | final GlobalKey _scaffoldKey = new GlobalKey(); 26 | 27 | _Profile(this.username); 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return OfflineBuilder( 32 | debounceDuration: Duration.zero, 33 | connectivityBuilder: ( 34 | BuildContext context, 35 | ConnectivityResult connectivity, 36 | Widget child, 37 | ) { 38 | if (connectivity == ConnectivityResult.none) { 39 | isConnection = false; 40 | } else { 41 | if (isConnection == false) { 42 | final snackBar = 43 | SnackBar(content: Text('Your internet is live again')); 44 | _scaffoldKey.currentState.showSnackBar(snackBar); 45 | SchedulerBinding.instance.addPostFrameCallback((_) => setState(() { 46 | isConnection = true; 47 | })); 48 | } 49 | isConnection = true; 50 | } 51 | return child; 52 | }, 53 | child: Scaffold( 54 | key: _scaffoldKey, 55 | resizeToAvoidBottomInset: false, 56 | endDrawer: AppDrawer(), 57 | appBar: AppBar( 58 | backgroundColor: appPrimaryColor, 59 | title: const Text('Profile'), 60 | ), 61 | body: Query( 62 | options: QueryOptions( 63 | documentNode: gql(_getBuildQuery()), 64 | ), 65 | builder: (QueryResult result, 66 | {VoidCallback refetch, FetchMore fetchMore}) { 67 | if (result.loading) { 68 | return Center(child: CircularProgressIndicator()); 69 | } 70 | if (!isConnection) { 71 | print('No Internet'); 72 | return Center( 73 | child: Text('Please check your internet connection')); 74 | } 75 | if (result.data == null) { 76 | print(username); 77 | print('NOT FOUND NAMES'); 78 | return Center(child: Text('Names not found.')); 79 | } 80 | return _profileView(result); 81 | }, 82 | ), 83 | ), 84 | ); 85 | } 86 | 87 | String _getBuildQuery() { 88 | return ''' 89 | query { 90 | user(username: "$username"){ 91 | admissionYear 92 | isMembershipActive 93 | isInLab 94 | lastSeenInLab 95 | } 96 | profile(username: "$username"){ 97 | about 98 | batch 99 | email 100 | fullName 101 | githubUsername 102 | gitlabUsername 103 | profilePic 104 | telegramID 105 | } 106 | } 107 | '''; 108 | } 109 | 110 | Widget _profileView(QueryResult result) { 111 | final nameList = result.data['profile']; 112 | return new Container( 113 | margin: const EdgeInsets.only(left: 15, right: 20), 114 | child: new Column(children: [ 115 | new Row( 116 | crossAxisAlignment: CrossAxisAlignment.start, 117 | children: [ 118 | new Container( 119 | margin: EdgeInsets.only(top: 20, bottom: 20, right: 20), 120 | child: new CircleAvatar( 121 | radius: 40, 122 | backgroundColor: Colors.grey, 123 | backgroundImage: NetworkImage( 124 | ImageAddressProvider.imageAddress( 125 | nameList['githubUsername'], nameList['profilePic'])), 126 | ), 127 | ), 128 | new Container( 129 | margin: EdgeInsets.only(top: 20, bottom: 20), 130 | child: new Column( 131 | crossAxisAlignment: CrossAxisAlignment.start, 132 | children: [ 133 | Container( 134 | padding: EdgeInsets.only(left: 4), 135 | margin: EdgeInsets.only(left: 8), 136 | child: new Text('${nameList['fullName']}', 137 | style: Theme.of(context).textTheme.headline), 138 | ), 139 | Row( 140 | children: [ 141 | IconButton( 142 | icon: new Icon(FlutterIcons.github_faw5d), 143 | onPressed: () { 144 | if (nameList['githubUsername'] != null) { 145 | launch('https://github.com/' + 146 | nameList['githubUsername']); 147 | } else { 148 | final snackBar = SnackBar( 149 | content: Text('github username not found')); 150 | _scaffoldKey.currentState 151 | .showSnackBar(snackBar); 152 | } 153 | }), 154 | IconButton( 155 | icon: new Icon(FlutterIcons.gitlab_faw5d), 156 | onPressed: () { 157 | if (nameList['gitlabUsername'] != null) { 158 | launch('https://gitlab.com/' + 159 | nameList['gitlabUsername']); 160 | } else { 161 | final snackBar = SnackBar( 162 | content: Text('Gitlab username not found')); 163 | _scaffoldKey.currentState 164 | .showSnackBar(snackBar); 165 | } 166 | }), 167 | IconButton( 168 | icon: new Icon(FlutterIcons.telegram_faw5d), 169 | onPressed: () { 170 | if (nameList['telegramID'] != null) { 171 | launch( 172 | 'https://t.me/' + nameList['telegramID']); 173 | } else { 174 | final snackBar = SnackBar( 175 | content: Text( 176 | 'Telegram username not availible')); 177 | _scaffoldKey.currentState 178 | .showSnackBar(snackBar); 179 | } 180 | }) 181 | ], 182 | ) 183 | ], 184 | ), 185 | ) 186 | ], 187 | ), 188 | new Container( 189 | margin: const EdgeInsets.only(top: 5.0), 190 | child: new Text(nameList['about'], 191 | style: Theme.of(context).textTheme.subtitle), 192 | ), 193 | Divider(color: Colors.black), 194 | _details(result), 195 | ])); 196 | } 197 | 198 | Widget _details(QueryResult result) { 199 | final nameList = result.data['profile']; 200 | final detailsList = result.data['user']; 201 | 202 | String membership = 203 | (detailsList['isMembershipActive']) ? "Active" : "Suspended"; 204 | 205 | return Expanded( 206 | child: ListView( 207 | children: [ 208 | ListTile( 209 | leading: Icon(Icons.account_circle), 210 | title: Text( 211 | 'CMS Username: $username', 212 | ), 213 | ), 214 | Divider(), 215 | ListTile( 216 | leading: Icon(Icons.mail), 217 | title: Text( 218 | 'Email ID: ${nameList['email']}', 219 | ), 220 | ), 221 | Divider(), 222 | ListTile( 223 | leading: Icon(Icons.calendar_today), 224 | title: Text( 225 | 'Admission Year: ${detailsList['admissionYear']}', 226 | ), 227 | ), 228 | Divider(), 229 | ListTile( 230 | leading: Icon(Icons.assessment), 231 | title: Text( 232 | 'Membership Status: $membership', 233 | ), 234 | ), 235 | Divider(), 236 | ListTile( 237 | leading: Icon(FlutterIcons.eye_faw5s), 238 | title: Text( 239 | _isInLab(result), 240 | ), 241 | ) 242 | ], 243 | )); 244 | } 245 | 246 | String _isInLab(QueryResult result) { 247 | final detailsList = result.data['user']; 248 | if (detailsList['isInLab']) { 249 | return "You are in lab now"; 250 | } 251 | String lastSeen = 252 | "Last seen: " + _getFormatedDate(detailsList['lastSeenInLab']); 253 | return lastSeen; 254 | } 255 | 256 | String _getFormatedDate(String date) { 257 | if(date == null){ 258 | return "No Info"; 259 | } 260 | var formmatedTime = 261 | DateFormat("HH:mm:ss").parse(date.substring(11, 19), true); 262 | var dateLocal = formmatedTime.toLocal(); 263 | return date.substring(0, 10) + 264 | " " + 265 | dateLocal.toIso8601String().substring(11, 19); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /lib/screens/login_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:cms_mobile/screens/home.dart'; 2 | import 'package:cms_mobile/utilities/constants.dart'; 3 | import 'package:cms_mobile/utilities/sizeconfig.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:graphql_flutter/graphql_flutter.dart'; 7 | import 'package:provider/provider.dart'; 8 | import 'package:toast/toast.dart'; 9 | 10 | import '../data/user_database.dart'; 11 | 12 | class LoginScreen extends StatefulWidget { 13 | @override 14 | _LoginScreenState createState() => _LoginScreenState(); 15 | } 16 | 17 | class _LoginScreenState extends State { 18 | bool _rememberMe = false; 19 | bool passwordInvisible = true; 20 | bool userExist = false; 21 | String refreshCred; 22 | TextEditingController _usernameController = new TextEditingController(); 23 | TextEditingController _passwordController = new TextEditingController(); 24 | FocusNode _usernameFocus = new FocusNode(); 25 | FocusNode _passwordFocus = new FocusNode(); 26 | 27 | _fieldFocusChange( 28 | BuildContext context, FocusNode currentFocus, FocusNode nextFocus) { 29 | currentFocus.unfocus(); 30 | FocusScope.of(context).requestFocus(nextFocus); 31 | } 32 | 33 | _onLoginPress() async { 34 | final db = Provider.of(context, listen: false); 35 | if (_usernameController.text.isNotEmpty && 36 | _passwordController.text.isNotEmpty) { 37 | final String authMutation = ''' 38 | mutation{ 39 | tokenAuth(username:"${_usernameController.text}", password:"${_passwordController.text}") { 40 | token 41 | } 42 | } 43 | '''; 44 | 45 | final HttpLink httpLink = HttpLink( 46 | uri: 'https://api.amfoss.in/', 47 | ); 48 | GraphQLClient _client = GraphQLClient( 49 | link: httpLink, 50 | cache: OptimisticCache(dataIdFromObject: typenameDataIdFromObject)); 51 | QueryResult result; 52 | String token; 53 | try { 54 | result = await _client.mutate(MutationOptions(document: authMutation)); 55 | token = result.data['tokenAuth']['token']; 56 | userExist = true; 57 | }on NoSuchMethodError catch(e){ 58 | print(e); 59 | } 60 | 61 | final AuthLink authLink = AuthLink( 62 | getToken: () async => 'JWT $token', 63 | ); 64 | final Link link = authLink.concat(httpLink); 65 | refreshCred = _usernameController.text + " " + _passwordController.text; 66 | 67 | User user = User(authToken: token, username: _usernameController.text, refreshToken: refreshCred); 68 | db.getSingleUser().then((userFromDb) { 69 | if(!userExist){ 70 | Toast.show("Invalid username or password", context, 71 | duration: Toast.LENGTH_LONG, gravity: Toast.BOTTOM); 72 | }else if (userFromDb == null) 73 | db.insertUser(user).then((onValue) { 74 | Navigator.pushReplacement( 75 | context, 76 | MaterialPageRoute( 77 | builder: (context) => HomePage( 78 | username: _usernameController.text, 79 | url: link, 80 | ), 81 | ), 82 | ); 83 | }); 84 | }); 85 | } else { 86 | Toast.show("Please enter the required fields", context, 87 | duration: Toast.LENGTH_LONG, gravity: Toast.BOTTOM); 88 | } 89 | } 90 | 91 | Widget _buildEmailTF() { 92 | return Column( 93 | crossAxisAlignment: CrossAxisAlignment.start, 94 | children: [ 95 | Text( 96 | 'Username', 97 | style: loginLabelStyle, 98 | ), 99 | SizedBox(height: SizeConfig.heightFactor * 10.0), 100 | Container( 101 | alignment: Alignment.centerLeft, 102 | decoration: loginBoxDecorationStyle, 103 | height: SizeConfig.heightFactor * 60.0, 104 | child: TextFormField( 105 | keyboardType: TextInputType.emailAddress, 106 | controller: _usernameController, 107 | textInputAction: TextInputAction.next, 108 | focusNode: _usernameFocus, 109 | onFieldSubmitted: (term) { 110 | _fieldFocusChange(context, _usernameFocus, _passwordFocus); 111 | }, 112 | style: TextStyle( 113 | color: Colors.white, 114 | fontFamily: 'OpenSans', 115 | ), 116 | decoration: InputDecoration( 117 | border: InputBorder.none, 118 | contentPadding: 119 | EdgeInsets.only(top: SizeConfig.heightFactor * 14.0), 120 | prefixIcon: Icon( 121 | Icons.account_circle, 122 | color: Colors.white54, 123 | ), 124 | hintText: 'Enter your Username', 125 | hintStyle: loginHintTextStyle, 126 | ), 127 | ), 128 | ), 129 | ], 130 | ); 131 | } 132 | 133 | Widget _buildPasswordTF() { 134 | return Column( 135 | crossAxisAlignment: CrossAxisAlignment.start, 136 | children: [ 137 | Text( 138 | 'Password', 139 | style: loginLabelStyle, 140 | ), 141 | SizedBox(height: SizeConfig.heightFactor * 10.0), 142 | Container( 143 | alignment: Alignment.centerLeft, 144 | decoration: loginBoxDecorationStyle, 145 | height: SizeConfig.heightFactor * 60.0, 146 | child: TextFormField( 147 | keyboardType: TextInputType.emailAddress, 148 | enableSuggestions: false, 149 | autocorrect: false, 150 | controller: _passwordController, 151 | textInputAction: TextInputAction.done, 152 | focusNode: _passwordFocus, 153 | onFieldSubmitted: (value) { 154 | _passwordFocus.unfocus(); 155 | _onLoginPress(); 156 | }, 157 | obscureText: passwordInvisible, 158 | style: TextStyle( 159 | color: Colors.white, 160 | fontFamily: 'OpenSans', 161 | ), 162 | decoration: InputDecoration( 163 | border: InputBorder.none, 164 | contentPadding: 165 | EdgeInsets.only(top: SizeConfig.heightFactor * 14.0), 166 | prefixIcon: Icon( 167 | Icons.lock, 168 | color: Colors.white54, 169 | ), 170 | hintText: 'Enter your Password', 171 | hintStyle: loginHintTextStyle, 172 | suffixIcon: IconButton( 173 | icon: Icon( 174 | passwordInvisible ? Icons.visibility_off : Icons.visibility, 175 | color: Colors.white54, 176 | ), 177 | onPressed: () { 178 | setState(() { 179 | passwordInvisible = !passwordInvisible; 180 | }); 181 | }, 182 | ), 183 | ), 184 | ), 185 | ), 186 | ], 187 | ); 188 | } 189 | 190 | Widget _buildRememberMeCheckbox() { 191 | return Container( 192 | height: SizeConfig.heightFactor * 20.0, 193 | child: Row( 194 | children: [ 195 | Theme( 196 | data: ThemeData(unselectedWidgetColor: Colors.black), 197 | child: Checkbox( 198 | value: _rememberMe, 199 | checkColor: Colors.amberAccent, 200 | activeColor: Colors.black, 201 | onChanged: (value) { 202 | setState(() { 203 | _rememberMe = value; 204 | }); 205 | }, 206 | ), 207 | ), 208 | Text( 209 | 'Remember me', 210 | style: loginLabelStyle, 211 | ), 212 | ], 213 | ), 214 | ); 215 | } 216 | 217 | Widget _buildLoginBtn() { 218 | return Container( 219 | padding: EdgeInsets.symmetric(vertical: SizeConfig.heightFactor * 25.0), 220 | width: double.infinity, 221 | child: RaisedButton( 222 | elevation: SizeConfig.widthFactor * 5.0, 223 | padding: EdgeInsets.all(SizeConfig.aspectRation * 15.0), 224 | shape: RoundedRectangleBorder( 225 | borderRadius: BorderRadius.circular(SizeConfig.aspectRation * 30.0), 226 | ), 227 | color: Colors.amber, 228 | child: Text( 229 | 'LOGIN', 230 | style: TextStyle( 231 | color: Colors.black, 232 | letterSpacing: SizeConfig.widthFactor * 1.5, 233 | fontSize: SizeConfig.widthFactor * 18.0, 234 | fontWeight: FontWeight.bold, 235 | fontFamily: 'OpenSans', 236 | ), 237 | ), 238 | onPressed: _onLoginPress, 239 | )); 240 | } 241 | 242 | Widget _buildFromText() { 243 | return Padding( 244 | padding: const EdgeInsets.only(bottom: 60), 245 | child: Text( 246 | 'From', 247 | style: loginLabelStyle, 248 | ), 249 | ); 250 | } 251 | 252 | Widget _buildSignInWithLogo() { 253 | return Padding( 254 | padding: const EdgeInsets.only(bottom: 20), 255 | child: Image.asset( 256 | 'assets/images/amfoss.png', 257 | width: SizeConfig.screenWidth / 2.5, 258 | alignment: Alignment.bottomCenter, 259 | ), 260 | ); 261 | } 262 | 263 | Widget _buildLogoCMS() { 264 | return Image.asset( 265 | 'assets/images/cms.jpg', 266 | width: SizeConfig.screenWidth / 1.5, 267 | alignment: Alignment.topCenter, 268 | ); 269 | } 270 | 271 | @override 272 | void dispose() { 273 | super.dispose(); 274 | _usernameController.dispose(); 275 | _passwordController.dispose(); 276 | } 277 | 278 | @override 279 | Widget build(BuildContext context) { 280 | SizeConfig().init(context); 281 | return Scaffold( 282 | body: AnnotatedRegion( 283 | value: SystemUiOverlayStyle.light, 284 | child: GestureDetector( 285 | onTap: () => FocusScope.of(context).unfocus(), 286 | child: Stack( 287 | children: [ 288 | _buildSignInWithLogo(), 289 | Container( 290 | height: double.infinity, 291 | width: double.infinity, 292 | decoration: BoxDecoration( 293 | gradient: LinearGradient( 294 | begin: Alignment.topCenter, 295 | end: Alignment.bottomCenter, 296 | colors: [ 297 | Colors.black, 298 | Colors.black, 299 | Colors.black, 300 | Colors.black, 301 | ], 302 | stops: [0.1, 0.4, 0.7, 0.9], 303 | ), 304 | ), 305 | ), 306 | Container( 307 | padding: EdgeInsets.symmetric( 308 | horizontal: SizeConfig.widthFactor * 40.0, 309 | ), 310 | height: double.infinity, 311 | child: Column( 312 | mainAxisAlignment: MainAxisAlignment.center, 313 | children: [ 314 | _buildLogoCMS(), 315 | SizedBox(height: SizeConfig.heightFactor * 30.0), 316 | _buildEmailTF(), 317 | SizedBox( 318 | height: SizeConfig.heightFactor * 30.0, 319 | ), 320 | _buildPasswordTF(), 321 | // TODO: Implement remember me 322 | // _buildRememberMeCheckbox(), 323 | SizedBox( 324 | height: SizeConfig.heightFactor * 30, 325 | ), 326 | _buildLoginBtn(), 327 | ], 328 | ), 329 | ), 330 | Align( 331 | alignment: Alignment.bottomCenter, 332 | child: _buildFromText(), 333 | ), 334 | Align( 335 | alignment: Alignment.bottomCenter, 336 | child: _buildSignInWithLogo(), 337 | ), 338 | ], 339 | ), 340 | ), 341 | ), 342 | resizeToAvoidBottomInset: false, 343 | ); 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /lib/screens/profile/update_profile.dart: -------------------------------------------------------------------------------- 1 | import 'package:cms_mobile/screens/home.dart'; 2 | import 'package:cms_mobile/utilities/constants.dart'; 3 | import 'package:cms_mobile/utilities/sizeconfig.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/scheduler.dart'; 6 | import 'package:flutter_icons/flutter_icons.dart'; 7 | import 'package:flutter_offline/flutter_offline.dart'; 8 | import 'package:graphql_flutter/graphql_flutter.dart'; 9 | 10 | class UpdateProfile extends StatefulWidget { 11 | @override 12 | UpdateProfileScreen createState() => UpdateProfileScreen(); 13 | } 14 | 15 | class UpdateProfileScreen extends State { 16 | final String username = HomePageScreen.username; 17 | 18 | bool isConnection = true; 19 | final _scaffoldKey = GlobalKey(); 20 | 21 | TextEditingController _usernameController = new TextEditingController(); 22 | TextEditingController _firstNameController = new TextEditingController(); 23 | TextEditingController _lastNameController = new TextEditingController(); 24 | TextEditingController _emailController = new TextEditingController(); 25 | TextEditingController _customEmailController = new TextEditingController(); 26 | TextEditingController _phoneNumberController = new TextEditingController(); 27 | TextEditingController _githubUsernameController = new TextEditingController(); 28 | TextEditingController _gitlabUsernameController = new TextEditingController(); 29 | TextEditingController _rollNumberController = new TextEditingController(); 30 | TextEditingController _batchController = new TextEditingController(); 31 | TextEditingController _aboutController = new TextEditingController(); 32 | 33 | String about, 34 | batch, 35 | email, 36 | firstName, 37 | githubUsername, 38 | lastName, 39 | phoneNo, 40 | roll, 41 | gitlabUsername, 42 | customEmail; 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | final theme = getTheme(context); 47 | final ValueNotifier client = ValueNotifier( 48 | GraphQLClient(link: HomePageScreen.url, cache: InMemoryCache()), 49 | ); 50 | 51 | GraphQLClient graphQLClient = 52 | new GraphQLClient(link: HomePageScreen.url, cache: InMemoryCache()); 53 | 54 | return GraphQLProvider( 55 | client: client, 56 | child: OfflineBuilder( 57 | debounceDuration: Duration.zero, 58 | connectivityBuilder: (BuildContext context, 59 | ConnectivityResult connectivity, 60 | Widget child,) { 61 | if (connectivity == ConnectivityResult.none) { 62 | if (isConnection == true) { 63 | SchedulerBinding.instance.addPostFrameCallback((_) { 64 | final snackBar = SnackBar( 65 | content: Text('You dont have internet Connection')); 66 | _scaffoldKey.currentState.showSnackBar(snackBar); 67 | }); 68 | } 69 | 70 | isConnection = false; 71 | } else { 72 | if (isConnection == false) { 73 | final snackBar = 74 | SnackBar(content: Text('Your internet is live again')); 75 | _scaffoldKey.currentState.showSnackBar(snackBar); 76 | SchedulerBinding.instance 77 | .addPostFrameCallback((_) => 78 | setState(() { 79 | isConnection = true; 80 | })); 81 | } 82 | 83 | isConnection = true; 84 | } 85 | return child; 86 | }, 87 | child: Scaffold( 88 | key: _scaffoldKey, 89 | appBar: AppBar( 90 | backgroundColor: appPrimaryColor, 91 | title: Text("Update Profile"), 92 | ), 93 | body: Query( 94 | options: QueryOptions(documentNode: gql(_buildQuery())), 95 | builder: (QueryResult result, 96 | {VoidCallback refetch, FetchMore fetchMore}) { 97 | if (result.loading) { 98 | return Center( 99 | child: CircularProgressIndicator(), 100 | ); 101 | } 102 | if (result.data == null) { 103 | return Center( 104 | child: Text('Status Update not found'), 105 | ); 106 | } 107 | if (result.data['profile'].length == 0) { 108 | return Center( 109 | child: Text('No status updates for this date'), 110 | ); 111 | } 112 | print(result.data['profile']); 113 | _fillTextFormFields(result); 114 | return Container( 115 | height: double.infinity, 116 | child: ListView( 117 | padding: EdgeInsets.symmetric( 118 | horizontal: SizeConfig.widthFactor * 30.0, 119 | vertical: SizeConfig.widthFactor * 20.0), 120 | children: [ 121 | _buildTextField( 122 | "Username", "Enter your username", _usernameController, theme, FlutterIcons.person_mdi), 123 | _buildTextField("First Name", "Enter your First Name", 124 | _firstNameController, theme, FlutterIcons.first_page_mdi), 125 | _buildTextField("Last Name", "Enter your Last Name", 126 | _lastNameController, theme, FlutterIcons.last_page_mdi), 127 | _buildTextField( 128 | "Email", "Enter your Email", _emailController, theme, Icons.email), 129 | _buildTextField("Custom Email", "Enter your Custom Email", 130 | _customEmailController, theme, Icons.alternate_email), 131 | _buildTextField("Phone Number", "Enter your Phone Number", 132 | _phoneNumberController, theme, Icons.phone), 133 | _buildTextField( 134 | "GitHub Username", 135 | "Enter your GitHub username", 136 | _githubUsernameController, theme, FlutterIcons.github_faw5d), 137 | _buildTextField( 138 | "GitLab Username", 139 | "Enter your GitLab username", 140 | _gitlabUsernameController, theme, FlutterIcons.gitlab_faw5d), 141 | _buildTextField("Roll Number", "Enter your Roll number", 142 | _rollNumberController, theme, FlutterIcons.list_alt_faw), 143 | _buildTextField( 144 | "Batch", "Enter your Batch", _batchController, theme, FlutterIcons.graduation_cap_faw), 145 | _buildTextField("About", "Enter About", _aboutController, theme, FlutterIcons.info_outline_mdi), 146 | _buildUpdateBtn(graphQLClient, context), 147 | ], 148 | ), 149 | ); 150 | }, 151 | ), 152 | ), 153 | ), 154 | ); 155 | } 156 | 157 | _fillTextFormFields(QueryResult result) { 158 | _usernameController.text = HomePageScreen.username; 159 | _firstNameController.text = result.data['profile']['firstName']; 160 | _lastNameController.text = result.data['profile']['lastName']; 161 | _emailController.text = result.data['profile']['email']; 162 | _customEmailController.text = result.data['profile']['customEmail']; 163 | _phoneNumberController.text = result.data['profile']['phone']; 164 | _githubUsernameController.text = result.data['profile']['githubUsername']; 165 | _gitlabUsernameController.text = result.data['profile']['gitlabUsername']; 166 | _rollNumberController.text = result.data['profile']['roll']; 167 | _batchController.text = result.data['profile']['batch']; 168 | _aboutController.text = result.data['profile']['about']; 169 | } 170 | 171 | _getTexts() { 172 | about = _aboutController.text; 173 | firstName = _firstNameController.text; 174 | lastName = _lastNameController.text; 175 | email = _emailController.text; 176 | phoneNo = _phoneNumberController.text; 177 | githubUsername = _githubUsernameController.text; 178 | roll = _rollNumberController.text; 179 | batch = _batchController.text; 180 | about = _aboutController.text; 181 | gitlabUsername = _gitlabUsernameController.text; 182 | customEmail = _customEmailController.text; 183 | } 184 | 185 | Widget _buildUpdateBtn(GraphQLClient client, BuildContext context) { 186 | return Container( 187 | padding: EdgeInsets.symmetric( 188 | vertical: SizeConfig.heightFactor * 25.0, 189 | horizontal: SizeConfig.widthFactor * 30), 190 | width: double.infinity, 191 | child: RaisedButton( 192 | elevation: SizeConfig.widthFactor * 5.0, 193 | padding: EdgeInsets.all(SizeConfig.aspectRation * 15.0), 194 | shape: RoundedRectangleBorder( 195 | borderRadius: BorderRadius.circular(SizeConfig.aspectRation * 30.0), 196 | ), 197 | color: appPrimaryColor, 198 | child: Text( 199 | 'UPDATE', 200 | style: TextStyle( 201 | color: Colors.black, 202 | letterSpacing: SizeConfig.widthFactor * 1.5, 203 | fontSize: SizeConfig.widthFactor * 16.0, 204 | fontWeight: FontWeight.bold, 205 | fontFamily: 'OpenSans', 206 | ), 207 | ), 208 | onPressed: () async { 209 | GraphQLClient _client = GraphQLClient( 210 | link: HomePageScreen.url, 211 | cache: OptimisticCache( 212 | dataIdFromObject: typenameDataIdFromObject)); 213 | QueryResult result = await _client 214 | .mutate(MutationOptions(document: _updateMutation())); 215 | 216 | if (result.data['UpdateProfile']['id'] != null) { 217 | final snackBar = SnackBar(content: Text('Profile Updated')); 218 | _scaffoldKey.currentState.showSnackBar(snackBar); 219 | } else { 220 | final snackBar = 221 | SnackBar(content: Text('Something error occoured')); 222 | _scaffoldKey.currentState.showSnackBar(snackBar); 223 | } 224 | }, 225 | )); 226 | } 227 | 228 | Widget _buildTextField(String title, String hintText, 229 | TextEditingController textEditingController, bool theme, IconData icon) { 230 | return Column( 231 | crossAxisAlignment: CrossAxisAlignment.start, 232 | children: [ 233 | Text( 234 | title, 235 | style: kLabelStyle, 236 | ), 237 | SizedBox(height: SizeConfig.heightFactor * 10.0), 238 | Container( 239 | alignment: Alignment.centerLeft, 240 | height: SizeConfig.heightFactor * 50.0, 241 | child: TextField( 242 | keyboardType: TextInputType.emailAddress, 243 | controller: textEditingController, 244 | style: TextStyle( 245 | color: theme ? Colors.white : Colors.black, 246 | fontFamily: 'OpenSans', 247 | ), 248 | decoration: InputDecoration( 249 | enabledBorder: OutlineInputBorder( 250 | borderSide: BorderSide(color: Colors.transparent), 251 | borderRadius: BorderRadius.all(Radius.circular(30)) 252 | ), 253 | focusedBorder: OutlineInputBorder( 254 | borderSide: BorderSide(color: Colors.amber), 255 | borderRadius: BorderRadius.all(Radius.circular(30)) 256 | ), 257 | prefixIcon: Icon(icon,color: theme ? Colors.white : Colors.black,), 258 | filled: true, 259 | fillColor: theme ? Colors.grey[1000] :Colors.grey[200], 260 | hintText: hintText, 261 | hintStyle: TextStyle( 262 | color: theme ? Colors.white38 : Colors.black38, 263 | fontFamily: 'OpenSans' 264 | ), 265 | ), 266 | ), 267 | ), 268 | SizedBox( 269 | height: SizeConfig.heightFactor * 20, 270 | ) 271 | ], 272 | ); 273 | } 274 | 275 | @override 276 | void initState() { 277 | super.initState(); 278 | } 279 | 280 | String _buildQuery() { 281 | return ''' 282 | query 283 | { 284 | profile(username: "$username") { 285 | about 286 | batch 287 | email 288 | firstName 289 | lastName 290 | phone 291 | roll 292 | githubUsername 293 | gitlabUsername 294 | customEmail 295 | } 296 | } 297 | '''; 298 | } 299 | 300 | String _updateMutation() { 301 | _getTexts(); 302 | return ''' 303 | mutation { 304 | UpdateProfile(about: "${_aboutController.text}", 305 | batch: ${int.parse(_batchController.text)}, 306 | email: "${_emailController.text}", 307 | customEmail: "${_customEmailController.text}", 308 | firstName: "${_firstNameController.text}", 309 | githubUsername: "${_githubUsernameController.text}", 310 | gitlabUsername: "${_gitlabUsernameController.text}", 311 | lastName: "${_lastNameController.text}", 312 | phoneNo: "${_phoneNumberController.text}", 313 | roll: "${_rollNumberController.text}", 314 | username: "${_usernameController.text}") { 315 | id 316 | } 317 | } 318 | '''; 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /lib/screens/statusUpdate/statistics/status_update_graphs.dart: -------------------------------------------------------------------------------- 1 | import 'package:cms_mobile/screens/home.dart'; 2 | import 'package:cms_mobile/utilities/ColorGenerator.dart'; 3 | import 'package:cms_mobile/utilities/constants.dart'; 4 | import 'package:cms_mobile/utilities/indicator.dart'; 5 | import 'package:fl_chart/fl_chart.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter/scheduler.dart'; 8 | import 'package:flutter_offline/flutter_offline.dart'; 9 | import 'package:graphql_flutter/graphql_flutter.dart'; 10 | import 'package:intl/intl.dart'; 11 | 12 | class StatusUpdateGraphs extends StatefulWidget { 13 | @override 14 | _StatusUpdateGraphs createState() => _StatusUpdateGraphs(); 15 | } 16 | 17 | class _StatusUpdateGraphs extends State 18 | with SingleTickerProviderStateMixin { 19 | TabController tabController; 20 | bool isConnection = true; 21 | final GlobalKey _scaffoldKey = new GlobalKey(); 22 | 23 | DateTime initialDate = DateTime.now().subtract(Duration(days: 7)); 24 | DateTime lastDate = DateTime.now().subtract(Duration(hours: 29)); 25 | 26 | @override 27 | void initState() { 28 | tabController = new TabController(vsync: this, length: 2); 29 | super.initState(); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | final ValueNotifier client = ValueNotifier( 35 | GraphQLClient(link: HomePageScreen.url, cache: InMemoryCache()), 36 | ); 37 | 38 | return GraphQLProvider( 39 | client: client, 40 | child: OfflineBuilder( 41 | debounceDuration: Duration.zero, 42 | connectivityBuilder: (BuildContext context, 43 | ConnectivityResult connectivity, 44 | Widget child,) { 45 | if (connectivity == ConnectivityResult.none) { 46 | if (isConnection == true) { 47 | SchedulerBinding.instance.addPostFrameCallback((_) { 48 | final snackBar = SnackBar( 49 | content: Text('You dont have internet Connection')); 50 | _scaffoldKey.currentState.showSnackBar(snackBar); 51 | }); 52 | } 53 | 54 | isConnection = false; 55 | } else { 56 | if (isConnection == false) { 57 | final snackBar = 58 | SnackBar(content: Text('Your internet is live again')); 59 | _scaffoldKey.currentState.showSnackBar(snackBar); 60 | SchedulerBinding.instance 61 | .addPostFrameCallback((_) => 62 | setState(() { 63 | isConnection = true; 64 | })); 65 | } 66 | 67 | isConnection = true; 68 | } 69 | return child; 70 | }, 71 | child: Scaffold( 72 | key: _scaffoldKey, 73 | appBar: AppBar( 74 | backgroundColor: appPrimaryColor, 75 | title: Text("Status Update trends" + 76 | "\n" + 77 | _getFormatedDate(initialDate) + 78 | " - " + 79 | _getFormatedDate(lastDate)), 80 | bottom: new TabBar( 81 | controller: tabController, 82 | tabs: [ 83 | new Tab( 84 | icon: new Icon(Icons.assignment_turned_in), 85 | text: "Update Trends", 86 | ), 87 | new Tab( 88 | icon: new Icon(Icons.card_membership), 89 | text: "Member Trends", 90 | ), 91 | ], 92 | ), 93 | leading: IconButton( 94 | icon: new Icon(Icons.calendar_today), 95 | onPressed: () { 96 | setState(() { 97 | dateTimeRangePicker(); 98 | });} 99 | ), 100 | ), 101 | body: Query( 102 | options: QueryOptions(documentNode: gql(_buildQuery())), 103 | builder: (QueryResult result, 104 | {VoidCallback refetch, FetchMore fetchMore}) { 105 | if (result.loading) { 106 | return Center( 107 | child: CircularProgressIndicator(), 108 | ); 109 | } 110 | if (result.data == null) { 111 | return Center( 112 | child: Text('Status Update not found'), 113 | ); 114 | } 115 | if (result.data['clubStatusUpdate']['memberStats'].length == 0) { 116 | return Center( 117 | child: Text('No one a sent status update yet'), 118 | ); 119 | } 120 | print(result.data['clubStatusUpdate']['memberStats'][0]); 121 | return new TabBarView( 122 | controller: tabController, 123 | children: [ 124 | _UpdateTrends(result), 125 | _memberUpdates(result) 126 | ], 127 | ); 128 | }, 129 | ), 130 | ), 131 | ), 132 | ); 133 | } 134 | 135 | dateTimeRangePicker() async { 136 | DateTimeRange picked = await showDateRangePicker( 137 | context: context, 138 | firstDate: DateTime(DateTime.now().year - 5), 139 | lastDate: lastDate, 140 | initialDateRange: DateTimeRange( 141 | end: lastDate, 142 | start: initialDate)); 143 | if (picked != null) 144 | setState(() { 145 | lastDate = picked.end; 146 | initialDate = picked.start; 147 | }); 148 | build(context); 149 | } 150 | 151 | @override 152 | void dispose() { 153 | tabController.dispose(); 154 | super.dispose(); 155 | } 156 | 157 | // ignore: non_constant_identifier_names 158 | Widget _UpdateTrends(QueryResult result) { 159 | var updateList = result.data['clubStatusUpdate']; 160 | var length = result.data['clubStatusUpdate']['dailyLog'].length; 161 | var dates = []; 162 | var totalCount = 0; 163 | List count = []; 164 | var colors = []; 165 | for (int i = 0; i < length; ++i) { 166 | dates.add(updateList['dailyLog'][i]['date']); 167 | count.add(updateList['dailyLog'][i]['membersSentCount']); 168 | totalCount += updateList['dailyLog'][i]['membersSentCount']; 169 | colors.add(ColorGenerator.getColor()); 170 | } 171 | return new GridView.count( 172 | crossAxisCount: 1, 173 | children: new List.generate( 174 | 1, 175 | (index) { 176 | return new GridTile( 177 | header: Text( 178 | " Status Update trends for given dates", 179 | style: messageLabelStyle, 180 | ), 181 | child: new Card( 182 | child: _makePieChart(length, dates, count, colors, totalCount), 183 | ), 184 | ); 185 | }, 186 | ), 187 | ); 188 | } 189 | 190 | Widget _memberUpdates(QueryResult result) { 191 | var updateList = result.data['clubStatusUpdate']; 192 | var members1 = []; 193 | List count1 = []; 194 | var color1 = []; 195 | int totalCount1 = 0; 196 | 197 | var members2 = []; 198 | List count2 = []; 199 | var color2 = []; 200 | int totalCount2 = 0; 201 | 202 | var members3 = []; 203 | List count3 = []; 204 | var color3 = []; 205 | int totalCount3 = 0; 206 | 207 | var members4 = []; 208 | List count4 = []; 209 | var color4 = []; 210 | int totalCount4 = 0; 211 | 212 | for (int i = 0; 213 | i < result.data['clubStatusUpdate']['memberStats'].length; 214 | ++i) { 215 | if (updateList['memberStats'][i]['user']['admissionYear'] == 2016) { 216 | if ((updateList['memberStats'][i]['user']['username']) 217 | .toString() 218 | .length < 219 | 9) { 220 | members1.add(updateList['memberStats'][i]['user']['username']); 221 | } else { 222 | members1.add((updateList['memberStats'][i]['user']['username']) 223 | .toString() 224 | .substring(0, 9)); 225 | } 226 | count1.add(int.parse(updateList['memberStats'][i]['statusCount'])); 227 | totalCount1 += int.parse(updateList['memberStats'][i]['statusCount']); 228 | color1.add(ColorGenerator.getColor()); 229 | } else if (updateList['memberStats'][i]['user']['admissionYear'] == 230 | 2017) { 231 | if ((updateList['memberStats'][i]['user']['username']) 232 | .toString() 233 | .length < 234 | 9) { 235 | members2.add(updateList['memberStats'][i]['user']['username']); 236 | } else { 237 | members2.add((updateList['memberStats'][i]['user']['username']) 238 | .toString() 239 | .substring(0, 9)); 240 | } 241 | count2.add(int.parse(updateList['memberStats'][i]['statusCount'])); 242 | totalCount2 += int.parse(updateList['memberStats'][i]['statusCount']); 243 | color2.add(ColorGenerator.getColor()); 244 | } else if (updateList['memberStats'][i]['user']['admissionYear'] == 245 | 2018) { 246 | if ((updateList['memberStats'][i]['user']['username']) 247 | .toString() 248 | .length < 249 | 9) { 250 | members3.add(updateList['memberStats'][i]['user']['username']); 251 | } else { 252 | members3.add((updateList['memberStats'][i]['user']['username']) 253 | .toString() 254 | .substring(0, 9)); 255 | } 256 | count3.add(int.parse(updateList['memberStats'][i]['statusCount'])); 257 | totalCount3 += int.parse(updateList['memberStats'][i]['statusCount']); 258 | color3.add(ColorGenerator.getColor()); 259 | } else if (updateList['memberStats'][i]['user']['admissionYear'] == 260 | 2019) { 261 | if ((updateList['memberStats'][i]['user']['username']) 262 | .toString() 263 | .length < 264 | 9) { 265 | members4.add(updateList['memberStats'][i]['user']['username']); 266 | } else { 267 | members4.add((updateList['memberStats'][i]['user']['username']) 268 | .toString() 269 | .substring(0, 9)); 270 | } 271 | count4.add(int.parse(updateList['memberStats'][i]['statusCount'])); 272 | totalCount4 += int.parse(updateList['memberStats'][i]['statusCount']); 273 | color4.add(ColorGenerator.getColor()); 274 | } 275 | } 276 | 277 | var membersList = [members1, members2, members3, members4]; 278 | var countList = [count1, count2, count3, count4]; 279 | List totalCounts = [ 280 | totalCount1, 281 | totalCount2, 282 | totalCount3, 283 | totalCount4 284 | ]; 285 | var colorsList = [color1, color2, color3, color4]; 286 | var years = ["2016", "2017", "2018", "2019"]; 287 | 288 | return new GridView.count( 289 | crossAxisCount: 1, 290 | children: new List.generate( 291 | 4, 292 | (index) { 293 | return new GridTile( 294 | header: Text( 295 | " ${years[index]}", 296 | style: messageLabelStyle, 297 | ), 298 | child: new Card( 299 | child: _makePieChart( 300 | membersList[index].length, 301 | membersList[index], 302 | countList[index], 303 | colorsList[index], 304 | totalCounts[index]), 305 | ), 306 | ); 307 | }, 308 | ), 309 | ); 310 | } 311 | 312 | AspectRatio _makePieChart( 313 | int length, List dates, List count, List colors, int totalCount) { 314 | return AspectRatio( 315 | aspectRatio: 1.3, 316 | child: Row( 317 | children: [ 318 | const SizedBox( 319 | height: 18, 320 | ), 321 | Expanded( 322 | child: AspectRatio( 323 | aspectRatio: 1, 324 | child: PieChart( 325 | PieChartData( 326 | borderData: FlBorderData( 327 | show: false, 328 | ), 329 | sectionsSpace: 2, 330 | centerSpaceRadius: 25, 331 | sections: 332 | showingSections(length, count, colors, totalCount)), 333 | ), 334 | ), 335 | ), 336 | Column( 337 | mainAxisSize: MainAxisSize.max, 338 | mainAxisAlignment: MainAxisAlignment.end, 339 | crossAxisAlignment: CrossAxisAlignment.start, 340 | children: showLegend(length, dates, colors), 341 | ), 342 | const SizedBox( 343 | width: 28, 344 | height: 20, 345 | ), 346 | ], 347 | ), 348 | ); 349 | } 350 | 351 | String _getFormatedDate(DateTime dateTime) { 352 | return DateFormat("yyyy-MM-dd").format(dateTime); 353 | } 354 | 355 | String _buildQuery() { 356 | return ''' 357 | query{ 358 | clubStatusUpdate(startDate: "${DateFormat("yyyy-MM-dd").format(initialDate)}", endDate: "${DateFormat("yyyy-MM-dd").format(lastDate)}") 359 | { 360 | dailyLog { 361 | date 362 | membersSentCount 363 | } 364 | memberStats 365 | { 366 | user 367 | { 368 | username 369 | admissionYear 370 | } 371 | statusCount 372 | } 373 | } 374 | } 375 | '''; 376 | } 377 | 378 | List showLegend(int length, List dates, List colors) { 379 | return List.generate(length, (i) { 380 | return Indicator( 381 | color: colors[i], 382 | text: dates[i].toString(), 383 | isSquare: true, 384 | ); 385 | }); 386 | } 387 | 388 | List showingSections( 389 | int length, List count, List colors, int totalCount) { 390 | return List.generate(length, (i) { 391 | final double fontSize = 14; 392 | final double radius = 80; 393 | return PieChartSectionData( 394 | color: colors[i], 395 | value: count[i] / totalCount * 100, 396 | title: count[i].toString(), 397 | radius: radius, 398 | titleStyle: TextStyle( 399 | fontSize: fontSize, 400 | fontWeight: FontWeight.bold, 401 | color: Colors.white), 402 | ); 403 | }); 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.0.0" 11 | analyzer: 12 | dependency: transitive 13 | description: 14 | name: analyzer 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "0.39.5" 18 | analyzer_plugin_fork: 19 | dependency: transitive 20 | description: 21 | name: analyzer_plugin_fork 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "0.2.2" 25 | archive: 26 | dependency: transitive 27 | description: 28 | name: archive 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.0.11" 32 | args: 33 | dependency: transitive 34 | description: 35 | name: args 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.5.2" 39 | async: 40 | dependency: transitive 41 | description: 42 | name: async 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "2.6.1" 46 | boolean_selector: 47 | dependency: transitive 48 | description: 49 | name: boolean_selector 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.1.0" 53 | build: 54 | dependency: transitive 55 | description: 56 | name: build 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.2.2" 60 | build_config: 61 | dependency: transitive 62 | description: 63 | name: build_config 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "0.4.2" 67 | build_daemon: 68 | dependency: transitive 69 | description: 70 | name: build_daemon 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "2.1.4" 74 | build_resolvers: 75 | dependency: transitive 76 | description: 77 | name: build_resolvers 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "1.3.4" 81 | build_runner: 82 | dependency: "direct dev" 83 | description: 84 | name: build_runner 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "1.8.1" 88 | build_runner_core: 89 | dependency: transitive 90 | description: 91 | name: build_runner_core 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "5.0.0" 95 | built_collection: 96 | dependency: transitive 97 | description: 98 | name: built_collection 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "4.3.2" 102 | built_value: 103 | dependency: transitive 104 | description: 105 | name: built_value 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "7.0.9" 109 | characters: 110 | dependency: transitive 111 | description: 112 | name: characters 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "1.1.0" 116 | charcode: 117 | dependency: transitive 118 | description: 119 | name: charcode 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "1.2.0" 123 | checked_yaml: 124 | dependency: transitive 125 | description: 126 | name: checked_yaml 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "1.0.2" 130 | cli_util: 131 | dependency: transitive 132 | description: 133 | name: cli_util 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "0.1.3+2" 137 | clock: 138 | dependency: transitive 139 | description: 140 | name: clock 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "1.1.0" 144 | code_builder: 145 | dependency: transitive 146 | description: 147 | name: code_builder 148 | url: "https://pub.dartlang.org" 149 | source: hosted 150 | version: "3.2.1" 151 | collection: 152 | dependency: transitive 153 | description: 154 | name: collection 155 | url: "https://pub.dartlang.org" 156 | source: hosted 157 | version: "1.15.0" 158 | connectivity: 159 | dependency: transitive 160 | description: 161 | name: connectivity 162 | url: "https://pub.dartlang.org" 163 | source: hosted 164 | version: "0.4.8+2" 165 | connectivity_macos: 166 | dependency: transitive 167 | description: 168 | name: connectivity_macos 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "0.1.0+2" 172 | connectivity_platform_interface: 173 | dependency: transitive 174 | description: 175 | name: connectivity_platform_interface 176 | url: "https://pub.dartlang.org" 177 | source: hosted 178 | version: "1.0.3" 179 | convert: 180 | dependency: transitive 181 | description: 182 | name: convert 183 | url: "https://pub.dartlang.org" 184 | source: hosted 185 | version: "2.1.1" 186 | crypto: 187 | dependency: transitive 188 | description: 189 | name: crypto 190 | url: "https://pub.dartlang.org" 191 | source: hosted 192 | version: "2.1.3" 193 | csslib: 194 | dependency: transitive 195 | description: 196 | name: csslib 197 | url: "https://pub.dartlang.org" 198 | source: hosted 199 | version: "0.15.0" 200 | cupertino_icons: 201 | dependency: "direct main" 202 | description: 203 | name: cupertino_icons 204 | url: "https://pub.dartlang.org" 205 | source: hosted 206 | version: "0.1.3" 207 | dart_config: 208 | dependency: transitive 209 | description: 210 | path: "." 211 | ref: HEAD 212 | resolved-ref: a7ed88a4793e094a4d5d5c2d88a89e55510accde 213 | url: "https://github.com/MarkOSullivan94/dart_config.git" 214 | source: git 215 | version: "0.5.0" 216 | dart_style: 217 | dependency: transitive 218 | description: 219 | name: dart_style 220 | url: "https://pub.dartlang.org" 221 | source: hosted 222 | version: "1.3.4" 223 | equatable: 224 | dependency: transitive 225 | description: 226 | name: equatable 227 | url: "https://pub.dartlang.org" 228 | source: hosted 229 | version: "1.1.1" 230 | fake_async: 231 | dependency: transitive 232 | description: 233 | name: fake_async 234 | url: "https://pub.dartlang.org" 235 | source: hosted 236 | version: "1.2.0" 237 | fixnum: 238 | dependency: transitive 239 | description: 240 | name: fixnum 241 | url: "https://pub.dartlang.org" 242 | source: hosted 243 | version: "0.10.11" 244 | fl_chart: 245 | dependency: "direct main" 246 | description: 247 | name: fl_chart 248 | url: "https://pub.dartlang.org" 249 | source: hosted 250 | version: "0.9.0" 251 | flutter: 252 | dependency: "direct main" 253 | description: flutter 254 | source: sdk 255 | version: "0.0.0" 256 | flutter_icons: 257 | dependency: "direct main" 258 | description: 259 | name: flutter_icons 260 | url: "https://pub.dartlang.org" 261 | source: hosted 262 | version: "1.1.0" 263 | flutter_launcher_icons: 264 | dependency: "direct dev" 265 | description: 266 | name: flutter_launcher_icons 267 | url: "https://pub.dartlang.org" 268 | source: hosted 269 | version: "0.6.1" 270 | flutter_markdown: 271 | dependency: "direct main" 272 | description: 273 | name: flutter_markdown 274 | url: "https://pub.dartlang.org" 275 | source: hosted 276 | version: "0.3.4" 277 | flutter_offline: 278 | dependency: "direct main" 279 | description: 280 | name: flutter_offline 281 | url: "https://pub.dartlang.org" 282 | source: hosted 283 | version: "0.3.0" 284 | flutter_test: 285 | dependency: "direct dev" 286 | description: flutter 287 | source: sdk 288 | version: "0.0.0" 289 | flutter_web_plugins: 290 | dependency: transitive 291 | description: flutter 292 | source: sdk 293 | version: "0.0.0" 294 | glob: 295 | dependency: transitive 296 | description: 297 | name: glob 298 | url: "https://pub.dartlang.org" 299 | source: hosted 300 | version: "1.2.0" 301 | gql: 302 | dependency: transitive 303 | description: 304 | name: gql 305 | url: "https://pub.dartlang.org" 306 | source: hosted 307 | version: "0.12.2" 308 | graphql: 309 | dependency: transitive 310 | description: 311 | name: graphql 312 | url: "https://pub.dartlang.org" 313 | source: hosted 314 | version: "3.1.0-beta.1" 315 | graphql_flutter: 316 | dependency: "direct main" 317 | description: 318 | name: graphql_flutter 319 | url: "https://pub.dartlang.org" 320 | source: hosted 321 | version: "3.1.0-beta.1" 322 | graphs: 323 | dependency: transitive 324 | description: 325 | name: graphs 326 | url: "https://pub.dartlang.org" 327 | source: hosted 328 | version: "0.2.0" 329 | html: 330 | dependency: transitive 331 | description: 332 | name: html 333 | url: "https://pub.dartlang.org" 334 | source: hosted 335 | version: "0.14.0+3" 336 | html2md: 337 | dependency: "direct main" 338 | description: 339 | name: html2md 340 | url: "https://pub.dartlang.org" 341 | source: hosted 342 | version: "0.5.1" 343 | http: 344 | dependency: transitive 345 | description: 346 | name: http 347 | url: "https://pub.dartlang.org" 348 | source: hosted 349 | version: "0.12.0+4" 350 | http_multi_server: 351 | dependency: transitive 352 | description: 353 | name: http_multi_server 354 | url: "https://pub.dartlang.org" 355 | source: hosted 356 | version: "2.2.0" 357 | http_parser: 358 | dependency: transitive 359 | description: 360 | name: http_parser 361 | url: "https://pub.dartlang.org" 362 | source: hosted 363 | version: "3.1.3" 364 | image: 365 | dependency: "direct overridden" 366 | description: 367 | name: image 368 | url: "https://pub.dartlang.org" 369 | source: hosted 370 | version: "2.0.7" 371 | intl: 372 | dependency: "direct main" 373 | description: 374 | name: intl 375 | url: "https://pub.dartlang.org" 376 | source: hosted 377 | version: "0.16.1" 378 | io: 379 | dependency: transitive 380 | description: 381 | name: io 382 | url: "https://pub.dartlang.org" 383 | source: hosted 384 | version: "0.3.4" 385 | js: 386 | dependency: transitive 387 | description: 388 | name: js 389 | url: "https://pub.dartlang.org" 390 | source: hosted 391 | version: "0.6.3" 392 | json_annotation: 393 | dependency: transitive 394 | description: 395 | name: json_annotation 396 | url: "https://pub.dartlang.org" 397 | source: hosted 398 | version: "3.0.1" 399 | logging: 400 | dependency: transitive 401 | description: 402 | name: logging 403 | url: "https://pub.dartlang.org" 404 | source: hosted 405 | version: "0.11.4" 406 | markdown: 407 | dependency: transitive 408 | description: 409 | name: markdown 410 | url: "https://pub.dartlang.org" 411 | source: hosted 412 | version: "2.1.3" 413 | matcher: 414 | dependency: transitive 415 | description: 416 | name: matcher 417 | url: "https://pub.dartlang.org" 418 | source: hosted 419 | version: "0.12.10" 420 | meta: 421 | dependency: transitive 422 | description: 423 | name: meta 424 | url: "https://pub.dartlang.org" 425 | source: hosted 426 | version: "1.3.0" 427 | mime: 428 | dependency: transitive 429 | description: 430 | name: mime 431 | url: "https://pub.dartlang.org" 432 | source: hosted 433 | version: "0.9.6+3" 434 | moor: 435 | dependency: transitive 436 | description: 437 | name: moor 438 | url: "https://pub.dartlang.org" 439 | source: hosted 440 | version: "2.4.2" 441 | moor_flutter: 442 | dependency: "direct main" 443 | description: 444 | name: moor_flutter 445 | url: "https://pub.dartlang.org" 446 | source: hosted 447 | version: "2.1.1" 448 | moor_generator: 449 | dependency: "direct dev" 450 | description: 451 | name: moor_generator 452 | url: "https://pub.dartlang.org" 453 | source: hosted 454 | version: "2.4.0" 455 | nested: 456 | dependency: transitive 457 | description: 458 | name: nested 459 | url: "https://pub.dartlang.org" 460 | source: hosted 461 | version: "0.0.4" 462 | node_interop: 463 | dependency: transitive 464 | description: 465 | name: node_interop 466 | url: "https://pub.dartlang.org" 467 | source: hosted 468 | version: "1.0.3" 469 | node_io: 470 | dependency: transitive 471 | description: 472 | name: node_io 473 | url: "https://pub.dartlang.org" 474 | source: hosted 475 | version: "1.0.1+2" 476 | package_config: 477 | dependency: transitive 478 | description: 479 | name: package_config 480 | url: "https://pub.dartlang.org" 481 | source: hosted 482 | version: "1.9.3" 483 | path: 484 | dependency: transitive 485 | description: 486 | name: path 487 | url: "https://pub.dartlang.org" 488 | source: hosted 489 | version: "1.8.0" 490 | path_drawing: 491 | dependency: transitive 492 | description: 493 | name: path_drawing 494 | url: "https://pub.dartlang.org" 495 | source: hosted 496 | version: "0.4.1" 497 | path_parsing: 498 | dependency: transitive 499 | description: 500 | name: path_parsing 501 | url: "https://pub.dartlang.org" 502 | source: hosted 503 | version: "0.1.4" 504 | path_provider: 505 | dependency: transitive 506 | description: 507 | name: path_provider 508 | url: "https://pub.dartlang.org" 509 | source: hosted 510 | version: "1.5.1" 511 | pedantic: 512 | dependency: transitive 513 | description: 514 | name: pedantic 515 | url: "https://pub.dartlang.org" 516 | source: hosted 517 | version: "1.8.0+1" 518 | petitparser: 519 | dependency: transitive 520 | description: 521 | name: petitparser 522 | url: "https://pub.dartlang.org" 523 | source: hosted 524 | version: "2.4.0" 525 | platform: 526 | dependency: transitive 527 | description: 528 | name: platform 529 | url: "https://pub.dartlang.org" 530 | source: hosted 531 | version: "2.2.1" 532 | plugin_platform_interface: 533 | dependency: transitive 534 | description: 535 | name: plugin_platform_interface 536 | url: "https://pub.dartlang.org" 537 | source: hosted 538 | version: "1.0.2" 539 | pool: 540 | dependency: transitive 541 | description: 542 | name: pool 543 | url: "https://pub.dartlang.org" 544 | source: hosted 545 | version: "1.4.0" 546 | provider: 547 | dependency: "direct main" 548 | description: 549 | name: provider 550 | url: "https://pub.dartlang.org" 551 | source: hosted 552 | version: "4.0.5" 553 | pub_semver: 554 | dependency: transitive 555 | description: 556 | name: pub_semver 557 | url: "https://pub.dartlang.org" 558 | source: hosted 559 | version: "1.4.4" 560 | pubspec_parse: 561 | dependency: transitive 562 | description: 563 | name: pubspec_parse 564 | url: "https://pub.dartlang.org" 565 | source: hosted 566 | version: "0.1.5" 567 | quiver: 568 | dependency: transitive 569 | description: 570 | name: quiver 571 | url: "https://pub.dartlang.org" 572 | source: hosted 573 | version: "2.0.5" 574 | recase: 575 | dependency: transitive 576 | description: 577 | name: recase 578 | url: "https://pub.dartlang.org" 579 | source: hosted 580 | version: "3.0.0" 581 | rxdart: 582 | dependency: transitive 583 | description: 584 | name: rxdart 585 | url: "https://pub.dartlang.org" 586 | source: hosted 587 | version: "0.23.1" 588 | shared_preferences: 589 | dependency: "direct main" 590 | description: 591 | name: shared_preferences 592 | url: "https://pub.dartlang.org" 593 | source: hosted 594 | version: "0.5.7" 595 | shared_preferences_macos: 596 | dependency: transitive 597 | description: 598 | name: shared_preferences_macos 599 | url: "https://pub.dartlang.org" 600 | source: hosted 601 | version: "0.0.1+7" 602 | shared_preferences_platform_interface: 603 | dependency: transitive 604 | description: 605 | name: shared_preferences_platform_interface 606 | url: "https://pub.dartlang.org" 607 | source: hosted 608 | version: "1.0.3" 609 | shared_preferences_web: 610 | dependency: transitive 611 | description: 612 | name: shared_preferences_web 613 | url: "https://pub.dartlang.org" 614 | source: hosted 615 | version: "0.1.2+4" 616 | shelf: 617 | dependency: transitive 618 | description: 619 | name: shelf 620 | url: "https://pub.dartlang.org" 621 | source: hosted 622 | version: "0.7.5" 623 | shelf_web_socket: 624 | dependency: transitive 625 | description: 626 | name: shelf_web_socket 627 | url: "https://pub.dartlang.org" 628 | source: hosted 629 | version: "0.2.3" 630 | sky_engine: 631 | dependency: transitive 632 | description: flutter 633 | source: sdk 634 | version: "0.0.99" 635 | source_gen: 636 | dependency: transitive 637 | description: 638 | name: source_gen 639 | url: "https://pub.dartlang.org" 640 | source: hosted 641 | version: "0.9.5" 642 | source_span: 643 | dependency: transitive 644 | description: 645 | name: source_span 646 | url: "https://pub.dartlang.org" 647 | source: hosted 648 | version: "1.8.1" 649 | sqflite: 650 | dependency: transitive 651 | description: 652 | name: sqflite 653 | url: "https://pub.dartlang.org" 654 | source: hosted 655 | version: "1.3.0" 656 | sqflite_common: 657 | dependency: transitive 658 | description: 659 | name: sqflite_common 660 | url: "https://pub.dartlang.org" 661 | source: hosted 662 | version: "1.0.0+1" 663 | sqlparser: 664 | dependency: transitive 665 | description: 666 | name: sqlparser 667 | url: "https://pub.dartlang.org" 668 | source: hosted 669 | version: "0.7.0" 670 | stack_trace: 671 | dependency: transitive 672 | description: 673 | name: stack_trace 674 | url: "https://pub.dartlang.org" 675 | source: hosted 676 | version: "1.10.0" 677 | stream_channel: 678 | dependency: transitive 679 | description: 680 | name: stream_channel 681 | url: "https://pub.dartlang.org" 682 | source: hosted 683 | version: "2.1.0" 684 | stream_transform: 685 | dependency: transitive 686 | description: 687 | name: stream_transform 688 | url: "https://pub.dartlang.org" 689 | source: hosted 690 | version: "1.2.0" 691 | string_scanner: 692 | dependency: transitive 693 | description: 694 | name: string_scanner 695 | url: "https://pub.dartlang.org" 696 | source: hosted 697 | version: "1.1.0" 698 | synchronized: 699 | dependency: transitive 700 | description: 701 | name: synchronized 702 | url: "https://pub.dartlang.org" 703 | source: hosted 704 | version: "2.2.0" 705 | term_glyph: 706 | dependency: transitive 707 | description: 708 | name: term_glyph 709 | url: "https://pub.dartlang.org" 710 | source: hosted 711 | version: "1.2.0" 712 | test_api: 713 | dependency: transitive 714 | description: 715 | name: test_api 716 | url: "https://pub.dartlang.org" 717 | source: hosted 718 | version: "0.3.0" 719 | timing: 720 | dependency: transitive 721 | description: 722 | name: timing 723 | url: "https://pub.dartlang.org" 724 | source: hosted 725 | version: "0.1.1+2" 726 | toast: 727 | dependency: "direct main" 728 | description: 729 | name: toast 730 | url: "https://pub.dartlang.org" 731 | source: hosted 732 | version: "0.1.5" 733 | typed_data: 734 | dependency: transitive 735 | description: 736 | name: typed_data 737 | url: "https://pub.dartlang.org" 738 | source: hosted 739 | version: "1.3.0" 740 | url_launcher: 741 | dependency: "direct main" 742 | description: 743 | name: url_launcher 744 | url: "https://pub.dartlang.org" 745 | source: hosted 746 | version: "5.4.2" 747 | url_launcher_macos: 748 | dependency: transitive 749 | description: 750 | name: url_launcher_macos 751 | url: "https://pub.dartlang.org" 752 | source: hosted 753 | version: "0.0.1+4" 754 | url_launcher_platform_interface: 755 | dependency: transitive 756 | description: 757 | name: url_launcher_platform_interface 758 | url: "https://pub.dartlang.org" 759 | source: hosted 760 | version: "1.0.6" 761 | url_launcher_web: 762 | dependency: transitive 763 | description: 764 | name: url_launcher_web 765 | url: "https://pub.dartlang.org" 766 | source: hosted 767 | version: "0.1.1+1" 768 | uuid_enhanced: 769 | dependency: transitive 770 | description: 771 | name: uuid_enhanced 772 | url: "https://pub.dartlang.org" 773 | source: hosted 774 | version: "3.0.2" 775 | vector_math: 776 | dependency: transitive 777 | description: 778 | name: vector_math 779 | url: "https://pub.dartlang.org" 780 | source: hosted 781 | version: "2.1.0" 782 | watcher: 783 | dependency: transitive 784 | description: 785 | name: watcher 786 | url: "https://pub.dartlang.org" 787 | source: hosted 788 | version: "0.9.7+14" 789 | web_socket_channel: 790 | dependency: transitive 791 | description: 792 | name: web_socket_channel 793 | url: "https://pub.dartlang.org" 794 | source: hosted 795 | version: "1.1.0" 796 | websocket: 797 | dependency: transitive 798 | description: 799 | name: websocket 800 | url: "https://pub.dartlang.org" 801 | source: hosted 802 | version: "0.0.5" 803 | xml: 804 | dependency: transitive 805 | description: 806 | name: xml 807 | url: "https://pub.dartlang.org" 808 | source: hosted 809 | version: "3.5.0" 810 | yaml: 811 | dependency: transitive 812 | description: 813 | name: yaml 814 | url: "https://pub.dartlang.org" 815 | source: hosted 816 | version: "2.2.0" 817 | sdks: 818 | dart: ">=2.12.0 <3.0.0" 819 | flutter: ">=1.12.13+hotfix.5" 820 | --------------------------------------------------------------------------------