├── 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 | [](https://www.gnu.org/licenses/gpl-3.0)
4 | [](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 |
--------------------------------------------------------------------------------