├── .github
└── workflows
│ └── workflow1.yml
├── README.md
├── analysis_options.yaml
├── firebase.json
├── lib
├── authentication
│ ├── authentication.dart
│ └── authentication_service.dart
├── dto
│ ├── admin_dashboard_cache_model.dart
│ ├── constant.dart
│ ├── issue.dart
│ ├── issue_model.dart
│ ├── issue_model.g.dart
│ ├── pull_model.dart
│ ├── pull_model.g.dart
│ ├── repo_model.dart
│ ├── repo_model.g.dart
│ └── table_data.dart
├── main.dart
├── page
│ ├── bottom_sheet.dart
│ ├── drawer_widget.dart
│ ├── issue_details_mobile.dart
│ ├── issue_details_page.dart
│ ├── issue_details_web.dart
│ ├── login_page.dart
│ ├── mobile_repo_details.dart
│ ├── repo_details_page.dart
│ ├── repo_list_element.dart
│ ├── repo_page.dart
│ ├── web_repo_details.dart
│ └── welcome_page.dart
├── provider
│ └── provider_list.dart
├── route
│ └── routes.dart
├── service
│ ├── basic_service.dart
│ ├── dashboard_services.dart
│ ├── fire_base.dart
│ ├── github_service.dart
│ ├── issue_service.dart
│ ├── pull_request_service.dart
│ └── table_data_service.dart
└── view_util.dart
├── pubspec.yaml
└── web
├── favicon.png
├── icons
├── Icon-192.png
├── Icon-512.png
├── Icon-maskable-192.png
└── Icon-maskable-512.png
├── index.html
└── manifest.json
/.github/workflows/workflow1.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | push:
4 | branches:
5 | - main
6 | pull_request:
7 | branches:
8 | - main
9 | jobs:
10 | build_web:
11 | name: Build Flutter (Web)
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v1
15 | - uses: subosito/flutter-action@v1
16 | with:
17 | channel: 'dev'
18 |
19 | - name: flutter upgrade
20 | run: flutter upgrade
21 | - name: get Version
22 | run: flutter --version
23 | - run: flutter pub get
24 | - run: flutter config --enable-web
25 | - name: Make envfile
26 | uses: SpicyPizza/create-envfile@v1.3
27 | with:
28 | DEBUG: false
29 | envkey_my_admin_dashboard_token: ${{ secrets.ADMIN_DASHBOARD_TOKEN }}
30 | envkey_my_admin_dashboard_clientId: ${{ secrets.ADMIN_DASHBOARD_CLIENTID }}
31 | envkey_my_admin_dashboard_clientSecret: ${{ secrets.ADMIN_DASHBOARD_CLIENTSECRET }}
32 | envkey_my_admin_dashboard_redirectUrl: ${{ secrets.ADMIN_DASHBOARD_REDIRECTURL }}
33 | envkey_my_apiKey: ${{ secrets.APIKEY }}
34 | envkey_my_appId: ${{ secrets.APPID }}
35 | envkey_my_messagingSenderId: ${{ secrets.MESSAGING_SENDER_ID }}
36 | envkey_my_projectId: admin-dashboard-b9503
37 | file_name: env_var.env
38 | fail_on_empty: true
39 | - name: build_app
40 | run: flutter build web
41 | - name: Archive Production Artifact
42 | uses: actions/upload-artifact@master
43 | with:
44 | name: web-build
45 | path: build/web
46 |
47 | deploy_web:
48 | name: Deploy Web to Firebase Hosting
49 | needs: build_web
50 | runs-on: ubuntu-latest
51 | env:
52 | admin_dashboard_token: NOHA
53 | steps:
54 | - name: Checkout Repo
55 | uses: actions/checkout@master
56 | - name: Download Artifact
57 | uses: actions/download-artifact@master
58 | with:
59 | name: web-build
60 | path: build/web
61 |
62 | - name: npx firebase-tools
63 | run: npx firebase-tools
64 |
65 | - name: Deploy to Firebase
66 | uses: FirebaseExtended/action-hosting-deploy@v0
67 | with:
68 | repoToken: '${{ secrets.GITHUB_TOKEN }}'
69 | firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_ADMIN_DASHBOARD_B9503 }}'
70 | adminDashboardToken: '${{ secrets.ADMIN_DASHBOARD_TOKEN}}'
71 |
72 | projectId: admin-dashboard-b9503
73 | channelId: live
74 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Flutter Community Admin Dashboard
2 |
3 | ## The Issue
4 | The Flutter Community provides several packages to the community and pub. dev that all differ in regards to maintenance requirements.
5 |
6 | The Flutter Community admins struggle to keep up with all of the changes, releases, changelogs, and activities on all packages. Two of the biggest challenges include keeping track of releases, as well as the inability to allow maintainers to trigger package releases on pub. dev without giving them super admin access.
7 |
8 | ## The solution
9 | We are looking to implement a Flutter Community Dashboard that assists admins by rounding up and providing the admins with information regarding issues, latest activities on repositories, maintainers, level of access and implementing a trigger to build and deploy to pub.dev, and more.
10 | [GitHub Pages](https://pages.github.com/).
11 | ## More about the project
12 | This project was initially started as a GSoC'22 project with [@abdelrahmanmagdii](https://github.com/abdelrahmanmagdii) as the mentee and [@mhadaily](https://github.com/mhadaily) as the mentor.
13 | This entire project was implemented using flutter for the frontend and Firebase used in the backend. Firebase was used for authentication purposes (as a single sign-on provider) so that it manages access to github and manages access to specific report pages depending on the access level of the user.
Moreover, the github APIs are not suited to create reports reflecting the repositories' variations over time. Therefore, the firebase backend will also be used to run periodic aggregation APIs and store statistical time variant information so that reports could be created that show the KPI (Key Performance Indicator) variations over time.
14 |
15 | ## What has been achieved so far
16 | During the GSoC period, we were able to
17 | 1. Initialize firebase and create a github action to deploy to firebase hosting
18 | 2. Succesfully implement logging in via github
19 | 3. Retrieve and display a list of all the repositories in the flutter community
20 | 4. Create a Repository dashboard that displays statistical data regarding the repository
21 | 5. Create a sortable table format to be able to display a list of issues/PRs based on the criteria chosen by the user
22 | 6. Retrieve and display issues based on criterias chosen by the user
23 | 7. Everything regarding the frontend
24 |
25 | ## What is left
26 | 1. Some data regarding the repository dashboard still uses dummy providers, not real data from the github API
27 | 2. Create an algorithm which decides whether a repository's status is RED, ORANGE or GREEN (Indicating whether or not it requires a maintainer/admins attention)
28 | 3. Implement a trigger to build and deploy to pub.dev
29 | 4. Previously, our application deployed and was hosted succesfully by firebase, but recently an error started occuring to the untouched code where you now face a bug whenever you try to login via Github on only the web version. We are currently still waiting on firebase's documentation to be updated inorder to implement how to login via github on firebase's new version.
30 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | analyzer:
2 | language:
3 | strict-casts: true
4 | strict-inference: true
5 | strict-raw-types: true
6 |
7 | errors:
8 | close_sinks: ignore
9 | missing_required_param: error
10 | missing_return: error
11 |
12 | exclude:
13 | - test/.test_coverage.dart
14 | - lib/generated_plugin_registrant.dart
15 |
16 | linter:
17 | rules:
18 | - always_declare_return_types
19 | - always_require_non_null_named_parameters
20 | - always_use_package_imports
21 | - annotate_overrides
22 | - avoid_bool_literals_in_conditional_expressions
23 | - avoid_catching_errors
24 | - avoid_double_and_int_checks
25 | - avoid_dynamic_calls
26 | - avoid_empty_else
27 | - avoid_equals_and_hash_code_on_mutable_classes
28 | - avoid_escaping_inner_quotes
29 | - avoid_field_initializers_in_const_classes
30 | - avoid_function_literals_in_foreach_calls
31 | - avoid_init_to_null
32 | - avoid_js_rounded_ints
33 | - avoid_null_checks_in_equality_operators
34 | - avoid_positional_boolean_parameters
35 | - avoid_print
36 | - avoid_private_typedef_functions
37 | - avoid_redundant_argument_values
38 | - avoid_relative_lib_imports
39 | - avoid_renaming_method_parameters
40 | - avoid_return_types_on_setters
41 | - avoid_returning_null
42 | - avoid_returning_null_for_future
43 | - avoid_returning_null_for_void
44 | - avoid_returning_this
45 | - avoid_setters_without_getters
46 | - avoid_shadowing_type_parameters
47 | - avoid_single_cascade_in_expression_statements
48 | - avoid_slow_async_io
49 | - avoid_type_to_string
50 | - avoid_types_as_parameter_names
51 | - avoid_unnecessary_containers
52 | - avoid_unused_constructor_parameters
53 | - avoid_void_async
54 | - avoid_web_libraries_in_flutter
55 | - await_only_futures
56 | - camel_case_extensions
57 | - camel_case_types
58 | - cancel_subscriptions
59 | - cascade_invocations
60 | - cast_nullable_to_non_nullable
61 | - comment_references
62 | - conditional_uri_does_not_exist
63 | - constant_identifier_names
64 | - control_flow_in_finally
65 | - curly_braces_in_flow_control_structures
66 | - deprecated_consistency
67 | - directives_ordering
68 | - empty_catches
69 | - empty_constructor_bodies
70 | - empty_statements
71 | - eol_at_end_of_file
72 | - exhaustive_cases
73 | - file_names
74 | - flutter_style_todos
75 | - hash_and_equals
76 | - implementation_imports
77 | - invariant_booleans
78 | - iterable_contains_unrelated_type
79 | - join_return_with_assignment
80 | - leading_newlines_in_multiline_strings
81 | - library_names
82 | - library_prefixes
83 | - library_private_types_in_public_api
84 | - lines_longer_than_80_chars
85 | - list_remove_unrelated_type
86 | - literal_only_boolean_expressions
87 | - missing_whitespace_between_adjacent_strings
88 | - no_adjacent_strings_in_list
89 | - no_default_cases
90 | - no_duplicate_case_values
91 | - no_logic_in_create_state
92 | - no_runtimeType_toString
93 | - non_constant_identifier_names
94 | - noop_primitive_operations
95 | - null_check_on_nullable_type_parameter
96 | - null_closures
97 | - omit_local_variable_types
98 | - one_member_abstracts
99 | - only_throw_errors
100 | - overridden_fields
101 | - package_api_docs
102 | - package_names
103 | - package_prefixed_library_names
104 | - parameter_assignments
105 | - prefer_adjacent_string_concatenation
106 | - prefer_asserts_in_initializer_lists
107 | - prefer_asserts_with_message
108 | - prefer_collection_literals
109 | - prefer_conditional_assignment
110 | - prefer_const_constructors
111 | - prefer_const_constructors_in_immutables
112 | - prefer_const_declarations
113 | - prefer_const_literals_to_create_immutables
114 | - prefer_constructors_over_static_methods
115 | - prefer_contains
116 | - prefer_equal_for_default_values
117 | - prefer_final_fields
118 | - prefer_final_in_for_each
119 | - prefer_final_locals
120 | - prefer_for_elements_to_map_fromIterable
121 | - prefer_function_declarations_over_variables
122 | - prefer_generic_function_type_aliases
123 | - prefer_if_elements_to_conditional_expressions
124 | - prefer_if_null_operators
125 | - prefer_initializing_formals
126 | - prefer_inlined_adds
127 | - prefer_int_literals
128 | - prefer_interpolation_to_compose_strings
129 | - prefer_is_empty
130 | - prefer_is_not_empty
131 | - prefer_is_not_operator
132 | - prefer_iterable_whereType
133 | - prefer_null_aware_method_calls
134 | - prefer_null_aware_operators
135 | - prefer_single_quotes
136 | - prefer_spread_collections
137 | - prefer_typing_uninitialized_variables
138 | - prefer_void_to_null
139 | - provide_deprecation_message
140 | - public_member_api_docs
141 | - recursive_getters
142 | - require_trailing_commas
143 | - secure_pubspec_urls
144 | - sized_box_for_whitespace
145 | - sized_box_shrink_expand
146 | - slash_for_doc_comments
147 | - sort_child_properties_last
148 | - sort_constructors_first
149 | - sort_pub_dependencies
150 | - sort_unnamed_constructors_first
151 | - test_types_in_equals
152 | - throw_in_finally
153 | - tighten_type_of_initializing_formals
154 | - type_annotate_public_apis
155 | - type_init_formals
156 | - unawaited_futures
157 | - unnecessary_await_in_return
158 | - unnecessary_brace_in_string_interps
159 | - unnecessary_const
160 | - unnecessary_constructor_name
161 | - unnecessary_getters_setters
162 | - unnecessary_lambdas
163 | - unnecessary_late
164 | - unnecessary_new
165 | - unnecessary_null_aware_assignments
166 | - unnecessary_null_checks
167 | - unnecessary_null_in_if_null_operators
168 | - unnecessary_nullable_for_final_variable_declarations
169 | - unnecessary_overrides
170 | - unnecessary_parenthesis
171 | - unnecessary_raw_strings
172 | - unnecessary_statements
173 | - unnecessary_string_escapes
174 | - unnecessary_string_interpolations
175 | - unnecessary_this
176 | - unrelated_type_equality_checks
177 | - use_build_context_synchronously
178 | - use_colored_box
179 | - use_decorated_box
180 | - use_enums
181 | - use_full_hex_values_for_flutter_colors
182 | - use_function_type_syntax_for_parameters
183 | - use_is_even_rather_than_modulo
184 | - use_key_in_widget_constructors
185 | - use_late_for_private_fields_and_variables
186 | - use_named_constants
187 | - use_raw_strings
188 | - use_rethrow_when_possible
189 | - use_setters_to_change_properties
190 | - use_string_buffers
191 | - use_super_parameters
192 | - use_to_and_as_if_applicable
193 | - valid_regexps
194 | - void_checks
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "hosting": {
3 | "public": "build/web",
4 | "ignore": [
5 | "firebase.json",
6 | "**/.*",
7 | "**/node_modules/**"
8 | ],
9 | "rewrites": [
10 | {
11 | "source": "**",
12 | "destination": "/index.html"
13 | }
14 | ]
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/lib/authentication/authentication.dart:
--------------------------------------------------------------------------------
1 | import 'package:admin_dashboard/authentication/authentication_service.dart';
2 | import 'package:admin_dashboard/dto/admin_dashboard_cache_model.dart';
3 | import 'package:admin_dashboard/main.dart';
4 | import 'package:firebase_auth/firebase_auth.dart';
5 | import 'package:firebase_core/firebase_core.dart';
6 | import 'package:flutter/cupertino.dart';
7 | import 'package:github_sign_in/github_sign_in.dart';
8 |
9 | ///Providing Authentication Service via Firebase
10 | class FirebaseAuthenticationService implements AuthenticationService {
11 | ///Constructor that takes firebaseApp as a parameter
12 | FirebaseAuthenticationService(this.firebaseApp);
13 |
14 | ///Instance of firebaseApp
15 | FirebaseApp firebaseApp;
16 |
17 |
18 | //final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
19 |
20 | @override
21 | Future emailLogin(String email, String password) async {
22 | final firebaseAuth = FirebaseAuth.instanceFor(app: firebaseApp);
23 | UserCredential userCredential;
24 | userCredential = await firebaseAuth.signInWithEmailAndPassword(
25 | email: email,
26 | password: password,
27 | );
28 | return userCredential.user?.uid;
29 | }
30 |
31 | @override
32 | ///Handles login through Github interface, stores UID in admin_dashboard_cache
33 | Future githubLogin(
34 | BuildContext context, AdminDashboardCache cache,) async {
35 | // Create a new provider
36 | final githubProvider = GithubAuthProvider();
37 |
38 | //https://firebase.flutter.dev/docs/auth/usage/
39 |
40 | if(cache.isWeb) {
41 | FirebaseAuth.instance
42 | .authStateChanges()
43 | .listen((User? user) {
44 | });
45 | await FirebaseAuth.instance.setPersistence(Persistence.NONE);
46 | // Once signed in, return the UserCredential
47 | final userCredential =
48 | await FirebaseAuth.instance.signInWithPopup(githubProvider);
49 | // debugPrint(userCredential.toString());
50 | return userCredential;
51 | }
52 | else {
53 | //Create a GitHubSignIn instance
54 | try {
55 | debugPrint('Step 1');
56 | final GitHubSignIn gitHubSignIn;
57 | gitHubSignIn = GitHubSignIn(
58 | clientId: EnvironmentConfig.clientId,
59 | clientSecret: EnvironmentConfig.clientSecret,
60 | redirectUrl: EnvironmentConfig.redirectUrl,
61 | );
62 | debugPrint('Step 2');
63 |
64 | // Trigger the sign-in flow
65 | final result = await gitHubSignIn.signIn(context);
66 | debugPrint('Step 3');
67 | // if the login is valid,
68 | // store uid in AdminDashboardCache and authenticate using firebase
69 | if (result.token != null) {
70 | debugPrint('Step 4');
71 | cache..uid = gitHubSignIn.clientId
72 | ..loggedIn = true;
73 | // Create a credential from the access token
74 | final githubAuthCredential =
75 | GithubAuthProvider.credential(result.token ?? '');
76 | UserCredential userCredential; //local variable for debugging
77 | return userCredential = await FirebaseAuth.instance
78 | .signInWithCredential(githubAuthCredential);
79 | // Once signed in, return the UserCredential
80 | }
81 | throw Exception('Error logging in');
82 |
83 | }catch(e){
84 | debugPrint(e.toString());
85 | rethrow;
86 | }
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/lib/authentication/authentication_service.dart:
--------------------------------------------------------------------------------
1 | import 'package:admin_dashboard/dto/admin_dashboard_cache_model.dart';
2 | import 'package:firebase_auth/firebase_auth.dart';
3 | import 'package:flutter/cupertino.dart';
4 |
5 | ///The interface used to define operations that will be provided by
6 | ///the Authentication Service Implementor
7 | abstract class AuthenticationService {
8 | ///Login using an email and password
9 | Future emailLogin(String email, String password);
10 | ///Authenticate login using github's interface
11 | Future githubLogin(BuildContext context,
12 | AdminDashboardCache cache,);
13 | }
14 |
--------------------------------------------------------------------------------
/lib/dto/admin_dashboard_cache_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:admin_dashboard/dto/repo_model.dart';
2 | import 'package:admin_dashboard/main.dart';
3 | ///Our application's cache where user ID is stored
4 | class AdminDashboardCache {
5 | ///user's github ID
6 | String? uid = '';
7 | ///Token used for authentication
8 | String token = EnvironmentConfig.token;
9 |
10 | ///List of repositories in the repo screen
11 | List myTitleList = [];
12 | /// Instance of simpleRepo object which is used to display a list of repos
13 | /// in an organization
14 | SimpleRepo simpleRepo = SimpleRepo(name: '');
15 | /// boolean value to see if the platform is mobile or web browser
16 | bool isWeb = false;
17 | /// boolean value to see if the user is logged in or not
18 | bool loggedIn = false;
19 | }
20 |
--------------------------------------------------------------------------------
/lib/dto/constant.dart:
--------------------------------------------------------------------------------
1 | ///All constants in project
2 | class Constants {
3 | ///Fixed repo name, Will be read from github/firebase in future
4 | static const String repoNameTest = 'plus_plugins';
5 | ///Issue keyword in our collection's path
6 | static const String issues = 'issues';
7 | ///Fixed issue number, will be read from github/firebase in future
8 | static const String issueNumber = '908';
9 | ///Pull keyword in our collection's path
10 | static const String pulls = 'pulls';
11 | ///Fixed Pull number, will be read from github/firebase in future
12 | static const String pullNumber = '504';
13 | ///Link to the activity log in our firebase collection
14 | static const String repoLogLink =
15 | 'https://admin-dashboard-b9503-default-rtdb.firebaseio.com/activity_log';
16 | ///Flutter community organization's name as a constant
17 | static const String flc = 'fluttercommunity';
18 | ///Repos keyword constant for the github API
19 | static const String repos = 'repos';
20 | ///Start of github URL for the API
21 | static const String gitApi = 'https://api.github.com';
22 | ///Orgs keyword for the github API
23 | static const String orgs = 'orgs';
24 | ///Text for the repo page from the drawer
25 | static const String printRepos = 'Print a list of all repositories';
26 | ///Text for the login page from the drawer
27 | static const String login = 'login';
28 | ///Text for the logout page from the drawer
29 | static const String logout = 'logout';
30 | ///Title for the repo page
31 | static const String repoTitle = 'Repository page';
32 | ///Firebase Project name
33 | // static const String projectName = 'admin-dashboard';
34 | static const String projectName = 'MyAdmin2';
35 | ///Welcome page title
36 | static const String welcomePageTitle = 'Flutter Admin Dashboard';
37 | ///Activity log URL
38 | static const String siteUrl =
39 | 'https://admin-dashboard-b9503-default-rtdb.firebaseio.com/activity_log';
40 | ///Flutter community organization's name as a title
41 | static const String flcTitle = 'Flutter Community';
42 | ///Status of bottomSheet widget
43 | static const String status = 'Status: RED';
44 | ///Updated at for bottom sheet widget
45 | static const String update = 'Last Updated At: ';
46 | ///URL for the repos API from github
47 | static const String repoGitUrl = 'https://api.github.com/repos';
48 | /// the /issues keyword to search for issues in a repo from the github API
49 | static const String repoGitIssue = '/issues';
50 | ///No data keyword for the table if data retrieval fails
51 | static const String noData = 'No Data';
52 | ///status keyword in the bottom sheet
53 | static const String statusWord = 'Status';
54 | ///owner keyword in the bottom sheet
55 | static const String owner = 'Owner';
56 | ///Login page title
57 | static const String loginPage = 'Login Page';
58 | ///flutter community path for the api
59 | static const String fluttercommunityPath = '/fluttercommunity/';
60 | ///Status keyword in the bottom sheet
61 | static const String statusKeyword = 'Status';
62 | ///Owner keyword in the bottom sheet
63 | static const String ownerKeyword = 'Owner';
64 | ///the word OK
65 | static const String ok = 'OK';
66 | }
67 |
--------------------------------------------------------------------------------
/lib/dto/issue.dart:
--------------------------------------------------------------------------------
1 | import 'package:admin_dashboard/dto/table_data.dart';
2 | import 'package:admin_dashboard/service/github_service.dart';
3 | import 'package:admin_dashboard/service/table_data_service.dart';
4 |
5 | ///Model of issue that is displayed in our application
6 | class SimpleIssue{
7 | ///Simple Issue constructor
8 | SimpleIssue({required this.title, required this.url, required this.state,
9 | required this.comments, required this.createdAt,
10 | required this.updatedAt,});
11 |
12 | ///title of issue
13 | final String title;
14 | ///URL of issue
15 | final String url;
16 | ///State of the issue (Open or closed)
17 | final String state;
18 | ///Number of comments the issue has
19 | final int comments;
20 | ///Date the issue was created
21 | final DateTime createdAt;
22 | ///Last time the issue was updated (new comments, change of state, etc)
23 | final DateTime updatedAt;
24 |
25 | @override
26 | String toString() {
27 | return 'SimpleIssue{title: $title, url: $url, state: $state, '
28 | 'comments: $comments, '
29 | 'createdAt: ${createdAt.month}/${createdAt.day}/${createdAt.year},'
30 | ' updatedAt: ${updatedAt.month}/${updatedAt.day}/${updatedAt.year} }';
31 | }
32 | }
33 |
34 | ///List of issues in a repo to be displayed in the table
35 | class SimpleIssueList{
36 | ///Constructor
37 | SimpleIssueList(this.repoName);
38 | ///Name of the repository
39 | String repoName;
40 | ///List of issues to be displayed in the table
41 | List data = [];
42 | ///method to add issues to the SimpleIssueList
43 | void add(SimpleIssue issue) {
44 | data.add(issue);
45 | }
46 |
47 | @override
48 | String toString() {
49 | return 'SimpleIssueList{data: $data}';
50 | }
51 | ///List of issues opened within the last six months
52 | Future> getOpenInLastSixMonths() async{
53 | if(data.isEmpty){
54 | final service = GithubService();
55 | data = await service.getAllIssuesOfRepo(repoName);
56 | }
57 |
58 | final result = [];
59 | for(final issue in data){
60 | if(issue.state != 'open') {
61 | continue;
62 | }
63 | if(issue.createdAt.difference(DateTime.now()).
64 | compareTo(const Duration(days: 180))>0) {
65 | continue;
66 | }
67 | result.add(issue);
68 | }
69 |
70 | return result;
71 | }
72 | }
73 | ///The actual github table provider that fetches data from github
74 | class GithubTableDataProvider extends TableDataProviderInterface {
75 | ///Create a simple issue list where the repoName is set
76 | GithubTableDataProvider(String repoName) :
77 | simpleIssueList = SimpleIssueList(repoName), super(repoName);
78 | ///Object that contains the list of issues that are displayed in the
79 | ///issue details page for a specific repo
80 | SimpleIssueList simpleIssueList;
81 | @override
82 | Future getOpenInLastSixMonths() async{
83 | final data = await simpleIssueList.getOpenInLastSixMonths();
84 | final result = TableData();
85 | for(final issue in data){
86 | result.add(TableDataRow().addValue('Title', issue.title)
87 | .addValue('Created', ' ${issue.createdAt.month}/'
88 | '${issue.createdAt.day}/${issue.createdAt.year},',)
89 | .addValue('Last Update', ' ${issue.updatedAt.month}/'
90 | '${issue.updatedAt.day}/${issue.updatedAt.year},',)
91 | .addValue('Comments', issue.comments.toString()),
92 | );
93 | }
94 | return result;
95 | }
96 |
97 | @override
98 | Future getSecondTable() async{
99 | return TableData().add(TableDataRow().addValue('Name', 'Bug fix').
100 | addValue('Open', '3 days'),)
101 | .add(TableDataRow().addValue('Name', 'Enhancement').
102 | addValue('Open', '7 days'),)
103 | .add(TableDataRow().addValue('Name', 'Feature').
104 | addValue('Open', '1 month'),)
105 | .add(TableDataRow().addValue('Name', 'Help wanted').
106 | addValue('Open', '4 months'),);
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/lib/dto/issue_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:json_annotation/json_annotation.dart';
2 |
3 | part 'issue_model.g.dart';
4 |
5 | @JsonSerializable()
6 |
7 | ///Model of github issues
8 | class Issue {
9 |
10 | ///Model of github issues
11 | Issue({
12 | required this.issueID,
13 | required this.state,
14 | required this.title,
15 | required this.loggedAt,
16 | required this.closedAt,
17 | required this.closedBy,
18 | required this.commentsNumber,
19 | required this.repository,
20 | });
21 | ///Conversion of JSON into issue object
22 | factory Issue.fromJson(Map json) => _$IssueFromJson(json);
23 | ///ID of issue retrieved from github API
24 | final int issueID;
25 | ///state of issue
26 | final String state;
27 | ///title of issue
28 | final String title;
29 | ///time issue was logged
30 | final DateTime loggedAt;
31 | ///time issue was closed, null if still open
32 | final DateTime? closedAt;
33 | ///username of user who closed issue, null if still open
34 | final String? closedBy;
35 | ///number of comments in issue, 0 if none
36 | final int? commentsNumber;
37 | ///repo issue belongs to
38 | final String repository;
39 |
40 | ///conversion of issues into JSON
41 | Map toJson() => _$IssueToJson(this);
42 | }
43 | ///Array of labels for an issue
44 | class Label {
45 |
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/lib/dto/issue_model.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'issue_model.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | Issue _$IssueFromJson(Map json) => Issue(
10 | issueID: json['issueID'] as int,
11 | state: json['state'] as String,
12 | title: json['title'] as String,
13 | loggedAt: DateTime.parse(json['loggedAt'] as String),
14 | closedAt: json['closedAt'] == null
15 | ? null
16 | : DateTime.parse(json['closedAt'] as String),
17 | closedBy: json['closedBy'] as String?,
18 | commentsNumber: json['commentsNumber'] as int?,
19 | repository: json['repository'] as String,
20 | );
21 |
22 | Map _$IssueToJson(Issue instance) => {
23 | 'issueID': instance.issueID,
24 | 'state': instance.state,
25 | 'title': instance.title,
26 | 'loggedAt': instance.loggedAt.toIso8601String(),
27 | 'closedAt': instance.closedAt?.toIso8601String(),
28 | 'closedBy': instance.closedBy,
29 | 'commentsNumber': instance.commentsNumber,
30 | 'repository': instance.repository,
31 | };
32 |
--------------------------------------------------------------------------------
/lib/dto/pull_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:json_annotation/json_annotation.dart';
2 |
3 | part 'pull_model.g.dart';
4 |
5 | @JsonSerializable()
6 | ///model of github PR
7 | class Pull {
8 | ///Model of PR
9 | Pull({
10 | required this.userId,
11 | required this.state,
12 | required this.title,
13 | required this.loggedAt,
14 | required this.closedAt,
15 | required this.closedBy,
16 | required this.commentsNumber,
17 | required this.repository,
18 | });
19 | ///Conversion of JSON into PR object
20 | factory Pull.fromJson(Map json) => _$PullFromJson(json);
21 | ///user that created PR's ID
22 | final int userId;
23 | ///state of PR
24 | final String state;
25 | ///title of PR
26 | final String title;
27 | ///time PR was logged
28 | final DateTime loggedAt;
29 | ///time PR was closed
30 | final DateTime? closedAt;
31 | ///user who closed PR
32 | final String? closedBy;
33 | ///number of comments in PR
34 | final int? commentsNumber;
35 | ///repository in which PR resides
36 | final String repository;
37 |
38 | ///conversion of PR into JSON
39 | Map toJson() => _$PullToJson(this);
40 | }
41 |
--------------------------------------------------------------------------------
/lib/dto/pull_model.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'pull_model.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | Pull _$PullFromJson(Map json) => Pull(
10 | userId: json['userId'] as int,
11 | state: json['state'] as String,
12 | title: json['title'] as String,
13 | loggedAt: DateTime.parse(json['loggedAt'] as String),
14 | closedAt: json['closedAt'] == null
15 | ? null
16 | : DateTime.parse(json['closedAt'] as String),
17 | closedBy: json['closedBy'] as String?,
18 | commentsNumber: json['commentsNumber'] as int?,
19 | repository: json['repository'] as String,
20 | );
21 |
22 | Map _$PullToJson(Pull instance) => {
23 | 'userId': instance.userId,
24 | 'state': instance.state,
25 | 'title': instance.title,
26 | 'loggedAt': instance.loggedAt.toIso8601String(),
27 | 'closedAt': instance.closedAt?.toIso8601String(),
28 | 'closedBy': instance.closedBy,
29 | 'commentsNumber': instance.commentsNumber,
30 | 'repository': instance.repository,
31 | };
32 |
--------------------------------------------------------------------------------
/lib/dto/repo_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:json_annotation/json_annotation.dart';
2 |
3 | part 'repo_model.g.dart';
4 | @JsonSerializable()
5 |
6 | ///Model of github issues
7 | class Repo {
8 |
9 | ///Model of github repos
10 | Repo({
11 | required this.name,
12 | required this.openIssuesCount,
13 | required this.watchersCount,
14 | required this.stargazersCount,
15 | required this.language,
16 | required this.url,
17 | });
18 | ///Conversion of JSON into repo object
19 | factory Repo.fromJson(Map json) => _$RepoFromJson(json);
20 | ///name of repository
21 | final String name;
22 | ///number of open issues in repo
23 | final int openIssuesCount;
24 | ///number of watchers in repo
25 | final int watchersCount;
26 | ///number of star gazers in repo
27 | final int stargazersCount;
28 | ///repo's main programming language
29 | final String language;
30 | ///URL of repo
31 | final String url;
32 |
33 | ///conversion of repos into JSON
34 | Map toJson() => _$RepoToJson(this);
35 | }
36 | ///Class for the repo objects displayed in the repo page
37 | class SimpleRepo extends Comparable{
38 | ///constructor
39 | SimpleRepo({required this.name});
40 | ///name of repository
41 | final String name;
42 |
43 | @override
44 | int compareTo(dynamic other) {
45 | if(other is! SimpleRepo) {
46 | return 0;
47 | }
48 | final otherRepo = other;
49 | final myName = name.toLowerCase().replaceAll(RegExp('[-_]'), ' ');
50 | final otherName =
51 | otherRepo.name.toLowerCase().replaceAll(RegExp('[-_]'), ' ');
52 | return myName.compareTo(otherName);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/lib/dto/repo_model.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'repo_model.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | Repo _$RepoFromJson(Map json) => Repo(
10 | name: json['name'] as String,
11 | openIssuesCount: json['openIssuesCount'] as int,
12 | watchersCount: json['watchersCount'] as int,
13 | stargazersCount: json['stargazersCount'] as int,
14 | language: json['language'] as String,
15 | url: json['url'] as String,
16 | );
17 |
18 | Map _$RepoToJson(Repo instance) => {
19 | 'name': instance.name,
20 | 'openIssuesCount': instance.openIssuesCount,
21 | 'watchersCount': instance.watchersCount,
22 | 'stargazersCount': instance.stargazersCount,
23 | 'language': instance.language,
24 | 'url': instance.url,
25 | };
26 |
--------------------------------------------------------------------------------
/lib/dto/table_data.dart:
--------------------------------------------------------------------------------
1 | import 'package:admin_dashboard/dto/constant.dart';
2 | import 'package:admin_dashboard/page/issue_details_mobile.dart';
3 | import 'package:admin_dashboard/page/issue_details_web.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | ///Class that handles everything concerning the data in the table
7 | class TableData {
8 |
9 | ///List of TableData elements in the rows
10 | List rowList = [];
11 |
12 | Map flags = {};
13 | ///Sorts the rows in ascending order
14 | void sort(String columnName){
15 | rowList.sort((TableDataRow a, TableDataRow b) =>
16 | (a.elements[columnName]??'').compareTo(b.elements[columnName]??''),);
17 | }
18 | ///Sorts the rows in descending order
19 | void reverseSort(String columnName){
20 | rowList.sort((TableDataRow a, TableDataRow b) =>
21 | (b.elements[columnName]??'').compareTo(a.elements[columnName]??''),);
22 | }
23 | ///Adds new entry to table
24 | TableData add(TableDataRow row){
25 | if(flags.isEmpty){
26 | for(final title in row.elements.keys){
27 | flags.update(title, (value) => true, ifAbsent: () => true);
28 | }
29 | }
30 | rowList.add(row);
31 | return this;
32 | }
33 |
34 | ///Returns a widget that is scrollable and that also uses the SilverGrid to
35 | ///return the table
36 | CustomScrollView getCustomScrollViewWide(WideIssueWidgetState parent){
37 | var length = 1;
38 | final childrenList = [];
39 | if(rowList.isEmpty){
40 | childrenList.add(const Text(Constants.noData,
41 | style: TextStyle(color: Colors.white),),);
42 | } else{
43 | length = rowList[0].elements.keys.length;
44 | for(final title in rowList[0].elements.keys){
45 | childrenList.add(
46 | TextButton(
47 | style: ButtonStyle(
48 | foregroundColor: MaterialStateProperty.all(Colors.blue),
49 | ),
50 | onPressed: () {
51 | final flag = flags[title]??true;
52 | if(flag) {
53 | parent.sort(title);
54 | }else{
55 | parent.reverseSort(title);
56 | }
57 | flags[title] = !flag;
58 | },
59 | child: Text(title,
60 | style: const TextStyle(fontStyle: FontStyle.italic,
61 | color: Colors.lightBlueAccent,),
62 | ),
63 | ),
64 |
65 |
66 | );
67 |
68 |
69 | }
70 | for(final row in rowList){
71 | for(final value in row.elements.values){
72 | childrenList.add(Container(
73 | alignment: Alignment.center,
74 | child: Text(value.length>20?'${value.substring(0,20)}...':value,
75 | style: const TextStyle(fontStyle: FontStyle.italic,
76 | color: Colors.white,),
77 | ),),);
78 | }
79 |
80 | }
81 | }
82 |
83 | final result = CustomScrollView(slivers: [
84 | SliverGrid.count(
85 | childAspectRatio: 4,
86 | crossAxisCount: length,
87 | children: childrenList,),
88 |
89 | ],
90 | );
91 | return result;
92 | }
93 | ///Returns a widget that is scrollable and that also uses the SilverGrid to
94 | ///return the table for narrow devices
95 | CustomScrollView getCustomScrollViewNarrow(NarrowIssueWidgetState parent){
96 | var length = 1;
97 | final childrenList = [];
98 | if(rowList.isEmpty){
99 | childrenList.add(const Text(Constants.noData,
100 | style: TextStyle(color: Colors.white),),);
101 | } else{
102 | length = rowList[0].elements.keys.length;
103 | for(final title in rowList[0].elements.keys){
104 | childrenList.add(
105 | TextButton(
106 | style: ButtonStyle(
107 | foregroundColor: MaterialStateProperty.all(Colors.blue),
108 | ),
109 | onPressed: () {
110 | final flag = flags[title]??true;
111 | if(flag) {
112 | parent.sort(title);
113 | }else{
114 | parent.reverseSort(title);
115 | }
116 | flags[title] = !flag;
117 | },
118 | child: Text(title,
119 | style: const TextStyle(fontStyle: FontStyle.italic,
120 | color: Colors.lightBlueAccent,),
121 | ),
122 | ),
123 |
124 |
125 | );
126 |
127 |
128 | }
129 | for(final row in rowList){
130 | for(final value in row.elements.values){
131 | childrenList.add(Container(
132 | alignment: Alignment.center,
133 | child: Text(value.length>20?'${value.substring(0,20)}...':value,
134 | style: const TextStyle(fontStyle: FontStyle.italic,
135 | color: Colors.white,),
136 | ),),);
137 | }
138 |
139 | }
140 | }
141 |
142 | final result = CustomScrollView(slivers: [
143 | SliverGrid.count(
144 | childAspectRatio: 4,
145 | crossAxisCount: length,
146 | children: childrenList,),
147 |
148 | ],
149 | );
150 | return result;
151 | }
152 |
153 | }
154 |
155 | ///Adds value to the row of a table
156 | class TableDataRow{
157 | ///elements in the table
158 | Map elements = {};
159 | ///Adds values to the table row
160 | TableDataRow addValue(String key, String value){
161 | //this doesn't write if the key does already exists so use update
162 | //with ifAbsentinstead
163 | elements.update(key, (value) => value, ifAbsent: () => value);
164 | return this;
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:admin_dashboard/dto/constant.dart';
2 | import 'package:admin_dashboard/provider/provider_list.dart';
3 | import 'package:admin_dashboard/route/routes.dart';
4 | import 'package:firebase_core/firebase_core.dart';
5 | import 'package:flutter/material.dart';
6 | import 'package:flutter_dotenv/flutter_dotenv.dart';
7 | import 'package:flutter_riverpod/flutter_riverpod.dart';
8 | import 'package:web_browser_detect/web_browser_detect.dart';
9 |
10 |
11 | Future main() async {
12 | WidgetsFlutterBinding.ensureInitialized();
13 | await dotenv.load(fileName: 'env_var.env');
14 |
15 | final browser = Browser.detectOrNull();
16 | FirebaseApp firebaseApp;
17 |
18 | if(browser == null) {
19 | firebaseApp = await Firebase.initializeApp(
20 | name: Constants.projectName,
21 | options: FirebaseOptions(
22 | apiKey: EnvironmentConfig.apiKey,
23 | appId: EnvironmentConfig.appId,
24 | messagingSenderId: EnvironmentConfig.messagingSenderId,
25 | projectId: EnvironmentConfig.projectId,
26 | ),
27 | );
28 | } else {
29 | firebaseApp = await Firebase.initializeApp(
30 | options: FirebaseOptions(
31 | authDomain: 'admin-dashboard.firebaseapp.com',
32 | apiKey: EnvironmentConfig.apiKey,
33 | appId: EnvironmentConfig.appId,
34 | messagingSenderId: EnvironmentConfig.messagingSenderId,
35 | projectId: EnvironmentConfig.projectId,
36 | ),
37 | );
38 | }
39 |
40 | runApp(
41 | ProviderScope(
42 | child: MyAppRoutes( firebaseApp: firebaseApp,),
43 | ),
44 | );
45 | }
46 |
47 | /// MyAppRoutes is the default page run at start up, it handles navigation
48 | /// to the homepage and calls the class generateRoute when navigating
49 | class MyAppRoutes extends ConsumerWidget {
50 | ///MyAppRoutes constructor
51 | const MyAppRoutes( {required this.firebaseApp, Key? key}) :
52 | super(key: key);
53 |
54 | ///instance of firebaseApp
55 | final FirebaseApp firebaseApp;
56 |
57 | @override
58 | Widget build(BuildContext context, WidgetRef ref) {
59 | final cache = ref.read(cacheProvider);
60 | //to detect if web
61 | final browser = Browser.detectOrNull();
62 | if(browser == null){
63 | cache.isWeb = false;
64 | }
65 | else{
66 | cache.isWeb = true;
67 | }
68 |
69 | return MaterialApp(
70 | onGenerateTitle: (context) => Constants.welcomePageTitle,
71 | initialRoute: RouteGenerator.loginPage,
72 | onGenerateRoute: //RouteGenerator.generateRoute,
73 | (settings) {
74 | return RouteGenerator.generateRoute(settings, //cache,
75 | firebaseApp, );
76 | },
77 | debugShowCheckedModeBanner: false,
78 | );
79 | }
80 | }
81 |
82 | ///environment variables
83 | class EnvironmentConfig {
84 | ///logged in client Token which is stored as a environment variable
85 | static final token = dotenv.get('MY_ADMIN_DASHBOARD_TOKEN', fallback:
86 | 'no .env',);
87 | ///logged in client ID which is stored as a environment variable
88 | static final clientId =
89 | dotenv.get('MY_ADMIN_DASHBOARD_CLIENTID', fallback: 'no .env');
90 | ///logged in client Secret which is stored as a environment variable
91 | static final clientSecret =
92 | dotenv.get('MY_ADMIN_DASHBOARD_CLIENTSECRET', fallback: 'no .env');
93 | ///The environment variable containing the redirect URL of GitHub
94 | static final redirectUrl =
95 | dotenv.get('MY_ADMIN_DASHBOARD_REDIRECTURL', fallback: 'no .env');
96 | ///our firebase's API key as an environment variable
97 | static final apiKey = dotenv.get('MY_APIKEY', fallback: 'no .env');
98 | ///our android appID from the firebase console as an environment variable
99 | static final appId = dotenv.get('MY_APPID', fallback: 'no .env');
100 | ///our messagingSenderID from the firebase console as an environment variable
101 | static final messagingSenderId =
102 | dotenv.get('MY_MESSAGINGSENDERID', fallback: 'no .env');
103 | ///Our projectID from the firebase console as an environment variable
104 | static final projectId = dotenv.get('MY_PROJECTID', fallback: 'no .env');
105 | }
106 |
--------------------------------------------------------------------------------
/lib/page/bottom_sheet.dart:
--------------------------------------------------------------------------------
1 | import 'package:admin_dashboard/dto/admin_dashboard_cache_model.dart';
2 | import 'package:admin_dashboard/dto/constant.dart';
3 | import 'package:admin_dashboard/dto/repo_model.dart';
4 | import 'package:firebase_core/firebase_core.dart';
5 | import 'package:flutter/material.dart';
6 | ///Bottom sheet where the repo owner, status and name are displayed
7 | class BottomSheet extends StatelessWidget{
8 | ///Constructor
9 | const BottomSheet( this.cache, this.firebaseApp, this.simpleRepo,
10 | {Key? key,}):super(key: key);
11 |
12 | ///Instance of our admin dashboard's cache
13 | final AdminDashboardCache cache;
14 |
15 | ///instance of firebaseApp
16 | final FirebaseApp firebaseApp;
17 |
18 | ///Instance of simpleRepo object used in the repository page
19 | final SimpleRepo simpleRepo;
20 | @override
21 | Widget build(BuildContext context) {
22 | return Container(
23 | constraints: const BoxConstraints(
24 | maxHeight: 30,
25 | maxWidth: 5000,
26 | minWidth: 150,
27 | minHeight: 20,
28 | ),
29 | child: BottomAppBar(
30 | color: Colors.black12,
31 | child: Row(
32 | children: [
33 | const Spacer(),
34 | Container(
35 | alignment: Alignment.center,
36 | child: Text(cache.simpleRepo.name),
37 | ),
38 | const Spacer(),
39 | Container(
40 | alignment: Alignment.center,
41 | child: const Text(Constants.statusWord),
42 | ),
43 | const Spacer(),
44 | Container(
45 | alignment: Alignment.center,
46 | child: const Text(Constants.owner),
47 | ),
48 | const Spacer(),
49 | ],
50 | ),
51 | ),
52 | );
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/lib/page/drawer_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:admin_dashboard/authentication/authentication.dart';
2 | import 'package:admin_dashboard/dto/constant.dart';
3 | import 'package:admin_dashboard/route/routes.dart';
4 | import 'package:firebase_core/firebase_core.dart';
5 | import 'package:flutter/material.dart';
6 |
7 | ///Drawer widget that is used for navigation throughout the app
8 | class DrawerWidget extends StatelessWidget{
9 | ///Constructor for the drawer widget
10 |
11 | const DrawerWidget(
12 | this.firebaseApp, {Key? key,}) : super(key: key);
13 | ///Instance of firebaseApp
14 | final FirebaseApp firebaseApp;
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 |
19 | return buildLoggedIn(context);
20 | }
21 | ///Build for items in the drawer widget
22 | Widget buildMenuItem({
23 | required String text,
24 | required IconData icon,
25 | VoidCallback? onClicked,
26 | }) {
27 | const color = Colors.white;
28 |
29 | return ListTile(
30 | leading: Icon(icon, color: color),
31 | title: Text(text, style: const TextStyle(color: color)),
32 | onTap: onClicked,
33 | );
34 | }
35 | ///the login method
36 | Future login(BuildContext context) async {
37 | FirebaseAuthenticationService(firebaseApp);
38 | }
39 |
40 | ///Build drawer widget for when a user isn't logged in
41 | Widget buildNotLoggedIn(BuildContext context) {
42 | return Drawer(
43 | child: Material(
44 | color: Colors.deepPurple,
45 | child: ListView(
46 | children: [
47 | const SizedBox(height: 48),
48 | buildMenuItem(
49 | text: Constants.login,
50 | icon: Icons.play_arrow,
51 | onClicked: () => {login(context)},
52 | ),
53 | //
54 | buildMenuItem(text: Constants.printRepos,
55 | icon: Icons.play_arrow,
56 | onClicked: () =>{
57 | Navigator.of(context).pushReplacementNamed(
58 | RouteGenerator.repoPage,)
59 | },
60 | ),
61 | buildMenuItem(text: 'placeholder',
62 | icon: Icons.abc,
63 | onClicked: () => {
64 | },
65 | ),
66 | ],
67 | ),
68 | ),
69 | );
70 |
71 | }
72 | ///Drawer widget if the user is logged in
73 | Widget buildLoggedIn(BuildContext context) {
74 | return Drawer(
75 | child: Material(
76 | color: Colors.deepPurple,
77 | child: ListView(
78 | children: [
79 | const SizedBox(height: 48),
80 | //
81 | buildMenuItem(text: Constants.printRepos,
82 | icon: Icons.play_arrow,
83 | onClicked: () =>{
84 | Navigator.of(context).pushReplacementNamed(
85 | RouteGenerator.repoPage,)
86 | },
87 | ),
88 | ],
89 | ),
90 | ),
91 | );
92 |
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/lib/page/issue_details_mobile.dart:
--------------------------------------------------------------------------------
1 | import 'package:admin_dashboard/dto/admin_dashboard_cache_model.dart';
2 | import 'package:admin_dashboard/dto/constant.dart';
3 | import 'package:admin_dashboard/dto/issue.dart';
4 | import 'package:admin_dashboard/dto/repo_model.dart';
5 | import 'package:admin_dashboard/dto/table_data.dart';
6 | import 'package:admin_dashboard/page/drawer_widget.dart';
7 | import 'package:admin_dashboard/provider/provider_list.dart';
8 | import 'package:admin_dashboard/service/table_data_service.dart';
9 | import 'package:firebase_core/firebase_core.dart';
10 | import 'package:flutter/material.dart';
11 | import 'package:flutter_riverpod/flutter_riverpod.dart';
12 |
13 | ///Issue page displayed on mobile devices
14 | class NarrowIssuePage extends ConsumerWidget {
15 | ///Constructor
16 | const NarrowIssuePage( this.firebaseApp, this.simpleRepo, this.myWidth,
17 | this.myHeight,);
18 |
19 | ///instance of firebaseApp
20 | final FirebaseApp firebaseApp;
21 | ///instance of simpleRepo that is used to display a list of simple repos
22 | final SimpleRepo simpleRepo;
23 | ///width of the screen
24 | final double myWidth;
25 | ///height of the screen
26 | final double myHeight;
27 |
28 |
29 | @override
30 | Widget build(BuildContext context, WidgetRef ref) {
31 | final cache = ref.read(cacheProvider);
32 | final dataProvider = GithubTableDataProvider(cache.simpleRepo.name);
33 | return NarrowIssueWidget(firebaseApp, simpleRepo, myWidth,
34 | myHeight, cache, dataProvider,);
35 | }
36 | }
37 | ///State for the mobile issue page
38 | class NarrowIssueWidget extends StatefulWidget {
39 | ///Constructor
40 | const NarrowIssueWidget(this.firebaseApp, this.simpleRepo, this.myWidth,
41 | this.myHeight, this.cache, this.tableDataProvider,);
42 |
43 | @override
44 | State createState() {
45 | return NarrowIssueWidgetState(
46 | firebaseApp, simpleRepo, myWidth, myHeight,
47 | cache, tableDataProvider,);
48 | }
49 | ///instance of firebaseApp
50 | final FirebaseApp firebaseApp;
51 | ///instance of simpleRepo
52 | final SimpleRepo simpleRepo;
53 | ///width of the screen
54 | final double myWidth;
55 | ///height of the screen
56 | final double myHeight;
57 | /// Instance of our Admin Dashboard's cache
58 | final AdminDashboardCache cache;
59 | ///Instance of the provider for the tableData
60 | final TableDataProviderInterface tableDataProvider;
61 | }
62 | ///State for the issue page for mobile devices
63 | class NarrowIssueWidgetState extends State {
64 | ///Constructor
65 | NarrowIssueWidgetState( this.firebaseApp, this.simpleRepo, this.myWidth,
66 | this.myHeight, this.cache, this.tableDataProvider,);
67 | ///Instance of firebase App
68 | final FirebaseApp firebaseApp;
69 | ///Instance of simple repo
70 | final SimpleRepo simpleRepo;
71 | ///Width of the screen
72 | final double myWidth;
73 | ///Height of the screen
74 | final double myHeight;
75 | ///Instance of our admin dashboard's cache
76 | final AdminDashboardCache cache;
77 | ///Instance of the provider for the table data
78 | final TableDataProviderInterface tableDataProvider;
79 | ///contains the data of all the issues for the chosen repo
80 | late Future data;
81 | ///subset of the data of all the issues that matches the criteria chosen
82 | late TableData currentData;
83 | @override
84 | void initState() {
85 | super.initState();
86 | data = tableDataProvider.getOpenInLastSixMonths();
87 | }
88 | @override
89 | Widget build(BuildContext context) {
90 | final width = MediaQuery.of(context).size.width;
91 | return Scaffold(
92 | backgroundColor: Colors.grey,
93 | drawer: DrawerWidget(
94 | firebaseApp,
95 | ),
96 | appBar: AppBar(
97 | backgroundColor: Colors.deepPurpleAccent,
98 | title: Text(
99 | cache.simpleRepo.name.replaceAll(RegExp('/fluttercommunity/'), ''),
100 | ),
101 | ),
102 | body:
103 | ConstrainedBox(
104 | constraints: BoxConstraints(minWidth: width),
105 | child: FutureBuilder(
106 | future: data,
107 | builder: (context, snapshot) {
108 | if (snapshot.connectionState == ConnectionState.done) {
109 | if (snapshot.hasData) {
110 | currentData = snapshot.data!;
111 | return currentData.getCustomScrollViewNarrow(this);
112 | } else if (snapshot.hasError) {
113 | return Text('${snapshot.error}');
114 | }
115 | }
116 | return const LinearProgressIndicator();
117 | },
118 | ),
119 | ),
120 |
121 | bottomSheet: buildBottomSheet(context, cache,
122 | ),
123 | );
124 | }
125 | ///The widget for our application's bottom sheet
126 | Widget buildBottomSheet(BuildContext context, AdminDashboardCache cache,
127 | ) {
128 | return Container(
129 | constraints: const BoxConstraints(
130 | maxHeight: 30,
131 | maxWidth: 5000,
132 | minWidth: 150,
133 | minHeight: 20,
134 | ),
135 | child: BottomAppBar(
136 | color: Colors.black12,
137 | child: Row(
138 | children: [
139 | const Spacer(),
140 | Container(
141 | alignment: Alignment.center,
142 | child: Text(
143 | cache.simpleRepo.name.
144 | replaceAll(RegExp('/fluttercommunity/'),''),),
145 | ),
146 | const Spacer(),
147 | Container(
148 | alignment: Alignment.center,
149 | child: const Text(Constants.statusKeyword),
150 | ),
151 | const Spacer(),
152 | Container(
153 | alignment: Alignment.center,
154 | child: const Text(Constants.ownerKeyword),
155 | ),
156 | const Spacer(),
157 | ],
158 | ),
159 | ),
160 | );
161 | }
162 |
163 | ///Sort method that sorts the table by ascending
164 | void sort(String title) {
165 | setState(() {
166 | currentData.sort(title);
167 | });
168 | }
169 |
170 | ///Sort method that sorts the table by descending
171 | void reverseSort(String title) {
172 | setState(() {
173 | currentData.reverseSort(title);
174 | });
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/lib/page/issue_details_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:admin_dashboard/dto/repo_model.dart';
2 | import 'package:admin_dashboard/page/issue_details_mobile.dart';
3 | import 'package:admin_dashboard/page/issue_details_web.dart';
4 | import 'package:firebase_core/firebase_core.dart';
5 | import 'package:flutter/material.dart';
6 |
7 | ///state of the issue details page
8 | class IssueDetailsPage extends StatefulWidget {
9 | ///Issue page constructor
10 | const IssueDetailsPage({
11 | required this.firebaseApp,
12 | Key? key,
13 | }) : super(key: key);
14 |
15 | ///instance of firebaseApp
16 | final FirebaseApp firebaseApp;
17 |
18 | @override
19 | State createState() =>
20 | _IssueDetailsPageState( firebaseApp, );
21 | }
22 |
23 | class _IssueDetailsPageState extends State {
24 | _IssueDetailsPageState( this.firebaseApp, );
25 |
26 | FirebaseApp firebaseApp;
27 |
28 | @override
29 | Widget build(BuildContext context) {
30 | final myWidth = MediaQuery
31 | .of(context)
32 | .size
33 | .width;
34 | final myHeight = MediaQuery
35 | .of(context)
36 | .size
37 | .height;
38 | var simpleRepo = SimpleRepo(name: '');
39 | final args = ModalRoute
40 | .of(context)!
41 | .settings
42 | .arguments ?? simpleRepo;
43 | simpleRepo = args as SimpleRepo;
44 | if (myWidth >= 600) {
45 | return buildWide(context, simpleRepo, myWidth, myHeight);
46 | } else {
47 | return buildNarrow(context, simpleRepo, myWidth, myHeight);
48 | }
49 | }
50 | Widget buildNarrow(BuildContext context, SimpleRepo simpleRepo,
51 | double myWidth, double myHeight,) {
52 | return NarrowIssuePage(firebaseApp, simpleRepo, myWidth, myHeight);
53 | }
54 |
55 | Widget buildWide(BuildContext context, SimpleRepo simpleRepo, double myWidth,
56 | double myHeight,) {
57 | return WideIssuePage(firebaseApp, simpleRepo, myWidth, myHeight, );
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/lib/page/issue_details_web.dart:
--------------------------------------------------------------------------------
1 | import 'package:admin_dashboard/dto/admin_dashboard_cache_model.dart';
2 | import 'package:admin_dashboard/dto/constant.dart';
3 | import 'package:admin_dashboard/dto/issue.dart';
4 | import 'package:admin_dashboard/dto/repo_model.dart';
5 | import 'package:admin_dashboard/dto/table_data.dart';
6 | import 'package:admin_dashboard/page/drawer_widget.dart';
7 | import 'package:admin_dashboard/provider/provider_list.dart';
8 | import 'package:admin_dashboard/service/table_data_service.dart';
9 | import 'package:firebase_core/firebase_core.dart';
10 | import 'package:flutter/material.dart';
11 | import 'package:flutter_riverpod/flutter_riverpod.dart';
12 |
13 | ///Page displayed when a wide (web or tablet) device is used
14 | class WideIssuePage extends ConsumerWidget {
15 | ///Constructor for Wide Issue Page
16 | const WideIssuePage(this.firebaseApp, this.simpleRepo, this.myWidth,
17 | this.myHeight,);
18 | ///Instance of firebaseApp
19 | final FirebaseApp firebaseApp;
20 | ///Instance of simpleRepo
21 | final SimpleRepo simpleRepo;
22 | ///Width of the screen
23 | final double myWidth;
24 | ///Height of the screen
25 | final double myHeight;
26 |
27 | @override
28 | Widget build(BuildContext context, WidgetRef ref) {
29 | final cache = ref.read(cacheProvider);
30 | final dataProvider = GithubTableDataProvider(cache.simpleRepo.name);
31 | return WideIssueWidget(firebaseApp, simpleRepo, myWidth,
32 | myHeight, cache, dataProvider,);
33 | }
34 | }
35 |
36 | ///Widget for the web/tablet version of our application
37 | class WideIssueWidget extends StatefulWidget {
38 | ///Constructor
39 | const WideIssueWidget(this.firebaseApp, this.simpleRepo, this.myWidth,
40 | this.myHeight, this.cache, this.tableDataProvider,);
41 | @override
42 | State createState() {
43 | return WideIssueWidgetState(
44 | firebaseApp, simpleRepo, myWidth, myHeight,
45 | cache, tableDataProvider,);
46 | }
47 |
48 | ///instance of firebaseApp
49 | final FirebaseApp firebaseApp;
50 | ///instance of simpleRepo
51 | final SimpleRepo simpleRepo;
52 | ///width of the screen
53 | final double myWidth;
54 | ///height of the screen
55 | final double myHeight;
56 | /// Instance of our Admin Dashboard's cache
57 | final AdminDashboardCache cache;
58 | ///Instance of the provider for the tableData
59 | final TableDataProviderInterface tableDataProvider;
60 | }
61 |
62 |
63 | ///State for the wide issue widget
64 | class WideIssueWidgetState extends State {
65 | ///Constructor
66 | WideIssueWidgetState( this.firebaseApp,
67 | this.simpleRepo,
68 | this.myWidth,
69 | this.myHeight, this.cache, this.tableDataProvider,);
70 |
71 | ///Instance of firebase App
72 | final FirebaseApp firebaseApp;
73 | ///Instance of simple repo
74 | final SimpleRepo simpleRepo;
75 | ///Width of the screen
76 | final double myWidth;
77 | ///Height of the screen
78 | final double myHeight;
79 | ///Instance of our admin dashboard's cache
80 | final AdminDashboardCache cache;
81 | ///Instance of the provider for the table data
82 | final TableDataProviderInterface tableDataProvider;
83 | ///contains the data of all the issues for the chosen repo
84 | late Future data;
85 | ///subset of the data of all the issues that matches the criteria chosen
86 | late TableData currentData;
87 | @override
88 | void initState() {
89 | super.initState();
90 | data = tableDataProvider.getOpenInLastSixMonths();
91 | }
92 | @override
93 | Widget build(BuildContext context) {
94 | final width = MediaQuery.of(context).size.width;
95 | return Scaffold(
96 | backgroundColor: Colors.grey,
97 | drawer: DrawerWidget(
98 | firebaseApp,
99 | ),
100 | appBar: AppBar(
101 | backgroundColor: Colors.deepPurpleAccent,
102 | title: Text(
103 | cache.simpleRepo.name.replaceAll(RegExp('/fluttercommunity/'), ''),
104 | ),
105 | ),
106 | body:
107 | ConstrainedBox(
108 | constraints: BoxConstraints(minWidth: width),
109 | child: FutureBuilder(
110 | future: data,
111 | builder: (context, snapshot) {
112 | if (snapshot.connectionState == ConnectionState.done) {
113 | if (snapshot.hasData) {
114 | currentData = snapshot.data!;
115 | //return currentData.getDataTable();
116 | return currentData.getCustomScrollViewWide(this);
117 | } else if (snapshot.hasError) {
118 | return Text('${snapshot.error}');
119 | }
120 | }
121 | return const LinearProgressIndicator();
122 | },
123 | ),
124 | ),
125 |
126 | bottomSheet: buildBottomSheet(context, cache,
127 | ),
128 | );
129 | }
130 |
131 | ///The widget for our application's bottom sheet
132 | Widget buildBottomSheet(BuildContext context, AdminDashboardCache cache,
133 | ) {
134 | return Container(
135 | constraints: const BoxConstraints(
136 | maxHeight: 30,
137 | maxWidth: 5000,
138 | minWidth: 150,
139 | minHeight: 20,
140 | ),
141 | child: BottomAppBar(
142 | color: Colors.black12,
143 | child: Row(
144 | children: [
145 | const Spacer(),
146 | Container(
147 | alignment: Alignment.center,
148 | child:
149 | Text(cache.simpleRepo.name
150 | .replaceAll(RegExp('/fluttercommunity/'),''),),
151 | ),
152 | const Spacer(),
153 | Container(
154 | alignment: Alignment.center,
155 | child: const Text(Constants.statusKeyword),
156 | ),
157 | const Spacer(),
158 | Container(
159 | alignment: Alignment.center,
160 | child: const Text(Constants.ownerKeyword),
161 | ),
162 | const Spacer(),
163 | ],
164 | ),
165 | ),
166 | );
167 | }
168 |
169 | ///Sort method that sorts the table by ascending
170 | void sort(String title) {
171 | setState(() {
172 | currentData.sort(title);
173 | });
174 | }
175 |
176 | ///Sort method that sorts the table by descending
177 | void reverseSort(String title) {
178 | setState(() {
179 | currentData.reverseSort(title);
180 | });
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/lib/page/login_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:admin_dashboard/authentication/authentication.dart';
2 | import 'package:admin_dashboard/dto/admin_dashboard_cache_model.dart';
3 | import 'package:admin_dashboard/dto/constant.dart';
4 | import 'package:admin_dashboard/provider/provider_list.dart';
5 | import 'package:admin_dashboard/route/routes.dart';
6 | import 'package:firebase_core/firebase_core.dart';
7 | import 'package:flutter/material.dart';
8 | import 'package:flutter_riverpod/flutter_riverpod.dart';
9 |
10 | ///Default page displayed for a user to login to the application
11 | class LoginPage extends ConsumerWidget{
12 | ///Login page constructor
13 | const LoginPage({
14 | required this.firebaseApp,
15 | });
16 | ///Instance of firebase app
17 | final FirebaseApp firebaseApp;
18 |
19 | @override
20 | Widget build(BuildContext context, WidgetRef ref) {
21 | final cache = ref.read(cacheProvider);
22 | return LoginWidget(firebaseApp: firebaseApp, cache: cache,);
23 | }
24 |
25 | }
26 |
27 | ///state of the login page
28 | class LoginWidget extends StatefulWidget {
29 |
30 |
31 | ///login page
32 | const LoginWidget({
33 | Key? key,
34 | required this.cache,
35 | required this.firebaseApp,
36 | }) : super(key: key);
37 | ///Instance of firebase app
38 | final FirebaseApp firebaseApp;
39 | ///Instance of admin dashboard cache
40 | final AdminDashboardCache cache;
41 |
42 |
43 | @override
44 | State createState() => _LoginWidgetState(cache, firebaseApp);
45 | }
46 |
47 | class _LoginWidgetState extends State {
48 | _LoginWidgetState(this.cache, this.firebaseApp):
49 | firebaseAuthenticationService =
50 | FirebaseAuthenticationService(firebaseApp);
51 | AdminDashboardCache cache;
52 | final FirebaseApp firebaseApp;
53 | FirebaseAuthenticationService firebaseAuthenticationService;
54 | bool isBusy = false;
55 | bool isLoggedIn = false;
56 | bool enableButton = true;
57 | @override
58 | void initState() {
59 | super.initState();
60 | }
61 | @override
62 | Widget build(BuildContext context) {
63 |
64 | return Scaffold(
65 | appBar: AppBar(
66 | //todo change
67 | title: const Text(Constants.loginPage),
68 | automaticallyImplyLeading: false,
69 | backgroundColor: Colors.deepPurpleAccent,
70 | ),
71 | body: Center(
72 | child: isBusy
73 | ? const CircularProgressIndicator()
74 | : isLoggedIn? const Text(''):
75 | ElevatedButton(
76 | style: ElevatedButton.styleFrom(
77 | padding: const EdgeInsets.all(20),
78 | primary: Colors.deepPurpleAccent,
79 | onPrimary: Colors.white,
80 | textStyle: const TextStyle(fontSize: 20),
81 | ),
82 | onPressed: loginAction,
83 | child: const Text(Constants.login),
84 |
85 | ),
86 |
87 | ),
88 | );
89 | }
90 |
91 |
92 | loginAction() async {
93 | setState(() {
94 | isBusy = true;
95 | enableButton = false;
96 | });
97 | final result = await firebaseAuthenticationService.githubLogin(
98 | context, cache,);
99 | result == null ? Navigator.pushReplacementNamed(context,
100 | RouteGenerator.waitingPage,)
101 | : Navigator.pushReplacementNamed(context,
102 | RouteGenerator.welcomePage,);
103 | if(result!=null) {
104 | setState(() {
105 | isBusy = false;
106 | enableButton = true;
107 | if(result==null){
108 | }
109 | });
110 | }
111 | }
112 | }
113 |
114 | ///Displays a circular progress indicator while login is completed
115 | class WaitingPage extends StatelessWidget {
116 |
117 | ///login page
118 | const WaitingPage({
119 | Key? key,
120 | required this.firebaseApp,
121 | }) : super(key: key);
122 | ///Instancce of firebase App
123 | final FirebaseApp firebaseApp;
124 |
125 | @override
126 | Widget build(BuildContext context) {
127 | return const Scaffold(
128 | body: Center(child: CircularProgressIndicator(),),
129 | );
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/lib/page/mobile_repo_details.dart:
--------------------------------------------------------------------------------
1 | import 'package:admin_dashboard/dto/admin_dashboard_cache_model.dart';
2 | import 'package:admin_dashboard/dto/repo_model.dart';
3 | import 'package:admin_dashboard/page/drawer_widget.dart';
4 | import 'package:admin_dashboard/provider/provider_list.dart';
5 | import 'package:admin_dashboard/route/routes.dart';
6 | import 'package:admin_dashboard/service/dashboard_services.dart';
7 | import 'package:firebase_core/firebase_core.dart';
8 | import 'package:fl_chart/fl_chart.dart';
9 | import 'package:flutter/material.dart';
10 | import 'package:flutter_riverpod/flutter_riverpod.dart';
11 |
12 | ///Repo page that is displayed on mobile
13 | class NarrowRepoPage extends ConsumerWidget {
14 | ///Constructor for narrow page
15 | const NarrowRepoPage(
16 | this.firebaseApp,
17 | this.simpleRepo,
18 | this.myWidth,
19 | this.myHeight,
20 | );
21 |
22 | ///instance of firebaseApp
23 | final FirebaseApp firebaseApp;
24 |
25 | ///Instance of simple repo
26 | final SimpleRepo simpleRepo;
27 |
28 | ///Width of the screen
29 | final double myWidth;
30 |
31 | ///Height of the screen
32 | final double myHeight;
33 |
34 | @override
35 | Widget build(BuildContext context, WidgetRef ref) {
36 | final cache = ref.read(cacheProvider);
37 | final dashboardProvider = ref.read(myDashboardProvider);
38 | return MobileSummaryDashboard(
39 | cache,
40 | firebaseApp,
41 | dashboardProvider,
42 | myWidth,
43 | myHeight,
44 | );
45 | }
46 | }
47 |
48 | ///The dashboard for mobile devices
49 | class MobileSummaryDashboard extends StatefulWidget {
50 | ///Constructor for mobile summary page
51 | const MobileSummaryDashboard(
52 | this.cache,
53 | this.firebaseApp,
54 | this.dashboardProvider,
55 | this.myWidth,
56 | this.myHeight,
57 | );
58 |
59 | ///Instance of admin dashboard cache
60 | final AdminDashboardCache cache;
61 |
62 | ///Instance of firebase app
63 | final FirebaseApp firebaseApp;
64 |
65 | ///Instance of dashboard provider
66 | final DashboardServiceInterface dashboardProvider;
67 |
68 | ///Width of the screen
69 | final double myWidth;
70 |
71 | ///Height of the screen
72 | final double myHeight;
73 |
74 | @override
75 | State createState() {
76 | return _MobileSummaryDashboard(
77 | cache,
78 | firebaseApp,
79 | dashboardProvider,
80 | myWidth,
81 | myHeight,
82 | );
83 | }
84 | }
85 |
86 | class _MobileSummaryDashboard extends State {
87 | _MobileSummaryDashboard(
88 | this.cache,
89 | this.firebaseApp,
90 | this.dashboardProvider,
91 | this.myWidth,
92 | this.myHeight,
93 | );
94 |
95 | AdminDashboardCache cache;
96 | FirebaseApp firebaseApp;
97 | DashboardServiceInterface dashboardProvider;
98 |
99 | final double myWidth;
100 | final double myHeight;
101 |
102 | late Future params;
103 |
104 | @override
105 | void initState() {
106 | super.initState();
107 | params = dashboardProvider.getDashboardParameters();
108 | }
109 |
110 | @override
111 | Widget build(BuildContext context) {
112 | return Scaffold(
113 | drawer: DrawerWidget(
114 | firebaseApp,
115 | ),
116 | appBar: AppBar(
117 | backgroundColor: Colors.deepPurpleAccent,
118 | ),
119 | body: FutureBuilder(
120 | future: params,
121 | builder: (context, snapshot) {
122 | if (snapshot.connectionState == ConnectionState.done) {
123 | if (snapshot.hasData) {
124 | final dashboardParams = snapshot.data!;
125 | return scrollView(context, dashboardParams);
126 | } else if (snapshot.hasError) {
127 | return Text('${snapshot.error}');
128 | }
129 | }
130 | return const CircularProgressIndicator();
131 | },
132 | ),
133 | );
134 | }
135 |
136 | Widget scrollView(
137 | BuildContext context,
138 | DashboardParameterList dashboardParams,
139 | ) {
140 | return SingleChildScrollView(
141 | child: Column(
142 | children: [
143 | Container(
144 | width: double.infinity,
145 | height: myHeight / 10,
146 | color: Colors.white,
147 | child: GestureDetector(
148 | onTap: () => {
149 | Navigator.of(context).pushReplacementNamed(
150 | RouteGenerator.issueDetailPage,
151 | )
152 | },
153 | child: Card(
154 | color: Colors.blueAccent,
155 | child: Center(
156 | child: Text(dashboardParams.issueSummary.issuesOldTitle),
157 | ),
158 | ),
159 | ),
160 | ),
161 | Container(
162 | width: double.infinity,
163 | height: myHeight / 10,
164 | color: Colors.white,
165 | child: GestureDetector(
166 | onTap: () => {
167 | Navigator.of(context).pushReplacementNamed(
168 | RouteGenerator.issueDetailPage,
169 | )
170 | },
171 | child: Card(
172 | color: Colors.deepPurpleAccent,
173 | child: Center(
174 | child: Text(dashboardParams.issueSummary.issuesHighActivity),
175 | ),
176 | ),
177 | ),
178 | ),
179 | Container(
180 | width: double.infinity,
181 | height: myHeight / 10,
182 | color: Colors.white,
183 | child: GestureDetector(
184 | onTap: () => {
185 | Navigator.of(context).pushReplacementNamed(
186 | RouteGenerator.issueDetailPage,
187 | )
188 | },
189 | child: Card(
190 | color: Colors.blueAccent,
191 | child: Center(
192 | child: Text(dashboardParams.issueSummary.issuesLowActivity),
193 | ),
194 | ),
195 | ),
196 | ),
197 | Container(
198 | width: double.infinity,
199 | height: myHeight / 10,
200 | color: Colors.white,
201 | child: GestureDetector(
202 | onTap: () => {
203 | Navigator.of(context).pushReplacementNamed(
204 | RouteGenerator.issueDetailPage,
205 | )
206 | },
207 | child: Card(
208 | color: Colors.deepPurpleAccent,
209 | child: Center(
210 | child: Text(dashboardParams.issueSummary.issuesLabeled),
211 | ),
212 | ),
213 | ),
214 | ),
215 | Container(
216 | width: double.infinity,
217 | height: myHeight / 2,
218 | color: Colors.white,
219 | child: GestureDetector(
220 | onTap: () => {
221 | Navigator.of(context).pushReplacementNamed(
222 | RouteGenerator.issueDetailPage,
223 | )
224 | },
225 | child: Card(
226 | color: Colors.white,
227 | child: MyLineChart(
228 | cache: cache,
229 | ),
230 | ),
231 | ),
232 | ),
233 | Container(
234 | width: double.infinity,
235 | height: myHeight / 10,
236 | color: Colors.white,
237 | child: GestureDetector(
238 | onTap: () => {
239 | Navigator.of(context).pushReplacementNamed(
240 | RouteGenerator.issueDetailPage,
241 | )
242 | },
243 | child: Card(
244 | color: Colors.blueAccent,
245 | child: Center(
246 | child: Center(
247 | child:
248 | Text(dashboardParams.pullRequestSummary.pullsOldTitle),
249 | ),
250 | ),
251 | ),
252 | ),
253 | ),
254 | Container(
255 | width: double.infinity,
256 | height: myHeight / 10,
257 | color: Colors.white,
258 | child: GestureDetector(
259 | onTap: () => {
260 | Navigator.of(context).pushReplacementNamed(
261 | RouteGenerator.issueDetailPage,
262 | )
263 | },
264 | child: Card(
265 | color: Colors.deepPurpleAccent,
266 | child: Center(
267 | child: Text(
268 | dashboardParams.pullRequestSummary.pullsHighActivity,
269 | ),
270 | ),
271 | ),
272 | ),
273 | ),
274 | Container(
275 | width: double.infinity,
276 | height: myHeight / 10,
277 | color: Colors.white,
278 | child: GestureDetector(
279 | onTap: () => {
280 | Navigator.of(context).pushReplacementNamed(
281 | RouteGenerator.issueDetailPage,
282 | )
283 | },
284 | child: Card(
285 | color: Colors.blueAccent,
286 | child: Center(
287 | child: Text(
288 | dashboardParams.pullRequestSummary.pullsLowActivity,
289 | ),
290 | ),
291 | ),
292 | ),
293 | ),
294 | Container(
295 | width: double.infinity,
296 | height: myHeight / 10,
297 | color: Colors.white,
298 | child: GestureDetector(
299 | onTap: () => {
300 | Navigator.of(context).pushReplacementNamed(
301 | RouteGenerator.issueDetailPage,
302 | )
303 | },
304 | child: GestureDetector(
305 | onTap: () => {
306 | Navigator.of(context).pushReplacementNamed(
307 | RouteGenerator.issueDetailPage,
308 | )
309 | },
310 | child: Card(
311 | color: Colors.deepPurpleAccent,
312 | child: Center(
313 | child: Text(
314 | dashboardParams.pullRequestSummary.pullsLabeled,
315 | ),
316 | ),
317 | ),
318 | ),
319 | ),
320 | ),
321 | ],
322 | ),
323 | );
324 | }
325 | }
326 |
327 | ///Class that creates and displays the line chart found in the repo page
328 | class MyLineChart extends StatelessWidget {
329 | ///Constructor
330 | MyLineChart({Key? key, required this.cache}) : super(key: key);
331 |
332 | ///Instance of our admin dashboard cache
333 | final AdminDashboardCache cache;
334 |
335 | ///Data that is used in the chart (hardcoded for now)
336 | final data = {0: 0, 1: 0.75, 2: 3, 3: 1.2, 4: 1, 5: 5};
337 |
338 | @override
339 | Widget build(BuildContext context) {
340 | return LineChart(
341 | LineChartData(
342 | minX: 0,
343 | maxX: 5,
344 | minY: 0,
345 | maxY: 5,
346 | lineBarsData: [LineChartBarData(spots: createPoints())],
347 | gridData: FlGridData(
348 | show: true,
349 | getDrawingHorizontalLine: (value) {
350 | return FlLine(
351 | color: Colors.black,
352 | strokeWidth: 1,
353 | );
354 | },
355 | ),
356 | ),
357 | );
358 | }
359 |
360 | ///Creates the points used in the line chart
361 | List createPoints() {
362 | ///List of spots to be used in the line chart
363 | final result = [];
364 | for (final x in data.keys) {
365 | result.add(FlSpot(x, data[x] ?? 0));
366 | }
367 | return result;
368 | }
369 | }
370 |
--------------------------------------------------------------------------------
/lib/page/repo_details_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:admin_dashboard/dto/repo_model.dart';
2 | import 'package:admin_dashboard/page/mobile_repo_details.dart';
3 | import 'package:admin_dashboard/page/web_repo_details.dart';
4 | import 'package:firebase_core/firebase_core.dart';
5 | import 'package:flutter/material.dart';
6 |
7 | ///Page that displays a repository's details
8 | class RepoDetailsPage extends StatefulWidget {
9 |
10 | ///Constructor
11 | const RepoDetailsPage({
12 | required this.firebaseApp,
13 | Key? key,
14 | }) : super(key: key);
15 |
16 | ///instance of firebaseApp
17 | final FirebaseApp firebaseApp;
18 |
19 | @override
20 | State createState() =>
21 | _RepoDetailsPageState( firebaseApp);
22 | }
23 |
24 | class _RepoDetailsPageState extends State {
25 | _RepoDetailsPageState( this.firebaseApp);
26 |
27 | FirebaseApp firebaseApp;
28 |
29 | @override
30 | Widget build(BuildContext context) {
31 | final myWidth = MediaQuery.of(context).size.width;
32 | final myHeight = MediaQuery.of(context).size.height;
33 | var simpleRepo = SimpleRepo(name: '');
34 | final args = ModalRoute.of(context)!.settings.arguments ?? simpleRepo;
35 | simpleRepo = args as SimpleRepo;
36 | if (myWidth >= 600) {
37 | return buildWide(context, simpleRepo, myWidth, myHeight);
38 | } else {
39 | return buildNarrow(context, simpleRepo, myWidth, myHeight);
40 | }
41 | }
42 |
43 | Widget buildNarrow(BuildContext context, SimpleRepo simpleRepo,
44 | double myWidth, double myHeight,) {
45 | return NarrowRepoPage(firebaseApp, simpleRepo, myWidth, myHeight);
46 | }
47 |
48 | Widget buildWide(BuildContext context, SimpleRepo simpleRepo, double myWidth,
49 | double myHeight,) {
50 | return WideRepoPage( firebaseApp, simpleRepo, myWidth, myHeight);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/lib/page/repo_list_element.dart:
--------------------------------------------------------------------------------
1 | import 'package:admin_dashboard/dto/admin_dashboard_cache_model.dart';
2 | import 'package:admin_dashboard/dto/repo_model.dart';
3 | import 'package:admin_dashboard/route/routes.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | ///Element that is used in the list of repos, where a list of all repos are
7 | ///displayed
8 | class RepoListElement{
9 | ///Constructor
10 | RepoListElement(this.simpleRepo, this.width, this.index);
11 | ///Instance of simpleRepo
12 | SimpleRepo simpleRepo;
13 | ///Width of the screen
14 | double width;
15 | ///Index to change color every other list element
16 | int index;
17 | ///Background color for list elements
18 | late Color backgroundColor =
19 | (index.isEven)? Colors.blueAccent:Colors.deepPurpleAccent;
20 | ///Repo list build method
21 | Widget build(BuildContext context, AdminDashboardCache cache){
22 | return ElevatedButton(
23 | onPressed: () {
24 | cache.simpleRepo = simpleRepo;
25 | Navigator.pushReplacementNamed(
26 | context,
27 | RouteGenerator.repoDetailsPage,
28 | );
29 | },
30 | style: ElevatedButton.styleFrom(
31 | primary: backgroundColor,
32 | onPrimary: Colors.black,
33 | maximumSize: Size(width*0.7, 80),
34 | ), child: Text(
35 | ' ${simpleRepo.name.replaceAll(RegExp('/fluttercommunity/'),'')}',),);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lib/page/repo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:admin_dashboard/dto/admin_dashboard_cache_model.dart';
2 | import 'package:admin_dashboard/dto/constant.dart';
3 | import 'package:admin_dashboard/dto/repo_model.dart';
4 | import 'package:admin_dashboard/page/drawer_widget.dart';
5 | import 'package:admin_dashboard/page/repo_list_element.dart';
6 | import 'package:admin_dashboard/provider/provider_list.dart';
7 | import 'package:admin_dashboard/service/fire_base.dart';
8 | import 'package:firebase_core/firebase_core.dart';
9 | import 'package:flutter/material.dart';
10 | import 'package:flutter_riverpod/flutter_riverpod.dart';
11 |
12 | ///Repository page where a list of all repos in an org is displayed
13 | class RepoPage extends ConsumerWidget{
14 | ///Constructor
15 | const RepoPage( {required this.firebaseApp});
16 | ///Instance of firebase App
17 | final FirebaseApp firebaseApp;
18 | @override
19 | Widget build(BuildContext context, WidgetRef ref) {
20 | final cache = ref.read(cacheProvider);
21 | return RepoPageWidget(firebaseApp: firebaseApp, cache: cache);
22 | }
23 |
24 | }
25 |
26 | ///state of the repo page
27 | class RepoPageWidget extends StatefulWidget {
28 | ///repo page constructor
29 | const RepoPageWidget({
30 | Key? key,
31 | //required this.fire,
32 | required this.firebaseApp,
33 | required this.cache,
34 | }) : super(key: key);
35 |
36 | ///Instance of firebaseApp
37 | final FirebaseApp firebaseApp;
38 | ///Instance of the admin dashboard cache
39 | final AdminDashboardCache cache;
40 |
41 | @override
42 | State createState() => _RepoPageWidgetState(
43 | firebaseApp,cache,);
44 | }
45 |
46 | class _RepoPageWidgetState extends State {
47 | _RepoPageWidgetState( //this.fire,
48 | this.firebaseApp, this.cache,);
49 | late Future> myTitleList;
50 |
51 | //BasicServiceInterface fire;
52 | FirebaseApp firebaseApp;
53 | final AdminDashboardCache cache;
54 |
55 | @override
56 | void initState() {
57 | super.initState();
58 | myTitleList = printRepos(context);
59 | }
60 | @override
61 | Widget build(BuildContext context) {
62 | printRepos(context);
63 | return Scaffold(
64 | drawer: DrawerWidget(//fire,
65 | firebaseApp,),
66 | appBar: AppBar(
67 | backgroundColor: Colors.deepPurple,
68 | title: const Text(Constants.repoTitle),
69 | ),
70 | body: Container(
71 | alignment: Alignment.center,
72 | padding: const EdgeInsets.all(8),
73 | child: FutureBuilder>(
74 | future: myTitleList,
75 | builder: (context, snapshot) {
76 | if (snapshot.connectionState == ConnectionState.done) {
77 | if (snapshot.hasData) {
78 | return ListView(
79 | children: _buildList(snapshot.data!.toList()),
80 | );
81 | } else if (snapshot.hasError) {
82 | return Text('${snapshot.error}');
83 | }
84 | }
85 | return const CircularProgressIndicator();
86 | },
87 | ),
88 | ),
89 | );
90 | }
91 | Future> printRepos(BuildContext context) async {
92 | if(cache.myTitleList.isNotEmpty) {
93 | return cache.myTitleList;
94 | }
95 | FireBaseService fireBaseService;
96 | fireBaseService = FireBaseService(firebaseApp);
97 | final list = await fireBaseService.getAllRepos(
98 | context,
99 | cache,
100 | );
101 | cache.myTitleList = list;
102 | return list;
103 | }
104 |
105 | List _buildList(List list) {
106 | list.sort();
107 | var index = 0;
108 | final result = [];
109 | for (final simpleRepo in list) {
110 | final element = SimpleRepoElement(index,simpleRepo, cache);
111 | index++;
112 | result.add(element);
113 | }
114 | return result;
115 | }
116 | }
117 |
118 | ///Our repository object that is used in the repository page
119 | class SimpleRepoElement extends StatelessWidget {
120 | ///SimpleRepo constructor
121 | const SimpleRepoElement(this.index,this.simpleRepo, this.cache, {Key? key})
122 | : super(key: key);
123 | ///Our cache that is used throughout the application, the list of retrieved
124 | ///repositories gets stored in the cache
125 | final AdminDashboardCache cache;
126 | ///SimpleRepo object that is used in the repository page
127 | final SimpleRepo simpleRepo;
128 | ///Index used in the repo page to know which repo has been selected by the
129 | ///user
130 | final int index;
131 |
132 | @override
133 | Widget build(BuildContext context) {
134 | final width = MediaQuery.of(context).size.width;
135 | final element = RepoListElement(simpleRepo, width, index);
136 | return element.build(context, cache);
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/lib/page/web_repo_details.dart:
--------------------------------------------------------------------------------
1 | import 'package:admin_dashboard/dto/admin_dashboard_cache_model.dart';
2 | import 'package:admin_dashboard/dto/constant.dart';
3 | import 'package:admin_dashboard/dto/repo_model.dart';
4 | import 'package:admin_dashboard/page/drawer_widget.dart';
5 | import 'package:admin_dashboard/provider/provider_list.dart';
6 | import 'package:admin_dashboard/route/routes.dart';
7 | import 'package:admin_dashboard/service/dashboard_services.dart';
8 | import 'package:firebase_core/firebase_core.dart';
9 | import 'package:fl_chart/fl_chart.dart';
10 | import 'package:flutter/material.dart';
11 | import 'package:flutter_riverpod/flutter_riverpod.dart';
12 |
13 | ///Repo page that is displayed on tablets/web browsers
14 | class WideRepoPage extends ConsumerWidget {
15 | ///Constructor
16 | const WideRepoPage(
17 | this.firebaseApp,
18 | this.simpleRepo,
19 | this.myWidth,
20 | this.myHeight,
21 | );
22 |
23 | ///instance of firebaseApp
24 | final FirebaseApp firebaseApp;
25 |
26 | ///Instance of simple repo
27 | final SimpleRepo simpleRepo;
28 |
29 | ///Width of screen
30 | final double myWidth;
31 |
32 | ///Height of screen
33 | final double myHeight;
34 |
35 | @override
36 | Widget build(BuildContext context, WidgetRef ref) {
37 | final cache = ref.read(cacheProvider);
38 | final dashboardProvider = ref.read(myDashboardProvider);
39 | return WebSummaryDashboard(
40 | cache,
41 | firebaseApp,
42 | dashboardProvider,
43 | myWidth,
44 | myHeight,
45 | );
46 | }
47 | }
48 |
49 | ///The repo dashboard for browsers/tablet devices
50 | class WebSummaryDashboard extends StatefulWidget {
51 | ///Constructor
52 | const WebSummaryDashboard(
53 | this.cache,
54 | this.firebaseApp,
55 | this.dashboardProvider,
56 | this.myWidth,
57 | this.myHeight,
58 | );
59 |
60 | ///Instance of admin dashboard cache
61 | final AdminDashboardCache cache;
62 |
63 | ///Instance of firebase app
64 | final FirebaseApp firebaseApp;
65 |
66 | ///Instance of dashboard provider
67 | final DashboardServiceInterface dashboardProvider;
68 |
69 | ///Screen's width
70 | final double myWidth;
71 |
72 | ///Screen's height
73 | final double myHeight;
74 |
75 | @override
76 | State createState() {
77 | return _WebSummaryDashboard(
78 | cache,
79 | firebaseApp,
80 | dashboardProvider,
81 | myWidth,
82 | myHeight,
83 | );
84 | }
85 | }
86 |
87 | class _WebSummaryDashboard extends State {
88 | _WebSummaryDashboard(
89 | this.cache,
90 | this.firebaseApp,
91 | this.dashboardProvider,
92 | this.myWidth,
93 | this.myHeight,
94 | );
95 |
96 | AdminDashboardCache cache;
97 | FirebaseApp firebaseApp;
98 | DashboardServiceInterface dashboardProvider;
99 |
100 | final double myWidth;
101 | final double myHeight;
102 |
103 | late Future params;
104 |
105 | @override
106 | void initState() {
107 | super.initState();
108 | params = dashboardProvider.getDashboardParameters();
109 | }
110 |
111 | @override
112 | Widget build(BuildContext context) {
113 | final width = MediaQuery.of(context).size.width;
114 | final height = MediaQuery.of(context).size.height;
115 |
116 | return Scaffold(
117 | backgroundColor: Colors.grey,
118 | drawer: DrawerWidget(
119 | firebaseApp,
120 | ),
121 | appBar: AppBar(
122 | backgroundColor: Colors.deepPurpleAccent,
123 | title: Text(
124 | cache.simpleRepo.name.replaceAll(RegExp('/fluttercommunity/'), ''),
125 | ),
126 | ),
127 | body: FutureBuilder(
128 | future: params,
129 | builder: (context, snapshot) {
130 | if (snapshot.connectionState == ConnectionState.done) {
131 | if (snapshot.hasData) {
132 | final dashboardParams = snapshot.data!;
133 | return Column(
134 | children: [
135 | firstRow(context, width, height, dashboardParams),
136 | secondRow(context, width, height, dashboardParams),
137 | ],
138 | );
139 | } else if (snapshot.hasError) {
140 | return Text('${snapshot.error}');
141 | }
142 | }
143 | return const CircularProgressIndicator();
144 | },
145 | ),
146 | bottomSheet: buildBottomSheet(context),
147 | );
148 | }
149 |
150 | Widget buildBottomSheet(BuildContext context) {
151 | final dateToday = DateTime.now();
152 | final date = dateToday.toString().substring(0, 10);
153 | return Container(
154 | constraints: const BoxConstraints(
155 | maxHeight: 30,
156 | maxWidth: 5000,
157 | minWidth: 150,
158 | minHeight: 20,
159 | ),
160 | child: BottomAppBar(
161 | color: Colors.black12,
162 | child: Row(
163 | children: [
164 | const Spacer(),
165 | Container(
166 | alignment: Alignment.center,
167 | child: const Text(Constants.flcTitle),
168 | ),
169 | const Spacer(),
170 | Container(
171 | alignment: Alignment.center,
172 | child: Text('${Constants.update}$date'),
173 | ),
174 | const Spacer(),
175 | Container(
176 | alignment: Alignment.center,
177 | child: const Text(
178 | Constants.status,
179 | style: TextStyle(
180 | backgroundColor: Colors.red,
181 | ),
182 | ),
183 | ),
184 | const Spacer(),
185 | ],
186 | ),
187 | ),
188 | );
189 | }
190 |
191 | Widget firstColumn(
192 | BuildContext context,
193 | DashboardParameterList dashboardParams,
194 | ) {
195 | return Expanded(
196 | child: Column(
197 | crossAxisAlignment: CrossAxisAlignment.start,
198 | textBaseline: TextBaseline.alphabetic,
199 | children: [
200 | GestureDetector(
201 | onTap: () => {
202 | Navigator.of(context)
203 | .pushReplacementNamed(RouteGenerator.issueDetailPage)
204 | },
205 | child: Container(
206 | margin: const EdgeInsets.only(
207 | bottom: 75,
208 | left: 120,
209 | right: 50,
210 | top: 100,
211 | ),
212 | decoration: BoxDecoration(
213 | color: Colors.white,
214 | borderRadius: BorderRadius.circular(18),
215 | ),
216 | padding: const EdgeInsets.all(15),
217 | child: Text(dashboardParams.issueSummary.issuesOldTitle),
218 | ),
219 | ),
220 | GestureDetector(
221 | onTap: () => {
222 | Navigator.of(context).pushReplacementNamed(
223 | RouteGenerator.issueDetailPage,
224 | )
225 | },
226 | child: Container(
227 | margin: const EdgeInsets.only(bottom: 75, left: 120),
228 | decoration: BoxDecoration(
229 | color: Colors.white,
230 | borderRadius: BorderRadius.circular(18),
231 | ),
232 | padding: const EdgeInsets.all(15),
233 | child: Text(dashboardParams.issueSummary.issuesHighActivity),
234 | ),
235 | ),
236 | GestureDetector(
237 | onTap: () => {
238 | Navigator.of(context).pushReplacementNamed(
239 | RouteGenerator.issueDetailPage,
240 | )
241 | },
242 | child: Container(
243 | margin: const EdgeInsets.only(bottom: 75, left: 120),
244 | decoration: BoxDecoration(
245 | color: Colors.white,
246 | borderRadius: BorderRadius.circular(18),
247 | ),
248 | padding: const EdgeInsets.all(15),
249 | child: Text(dashboardParams.issueSummary.issuesLowActivity),
250 | ),
251 | ),
252 | GestureDetector(
253 | onTap: () => {
254 | Navigator.of(context).pushReplacementNamed(
255 | RouteGenerator.issueDetailPage,
256 | )
257 | },
258 | child: Container(
259 | margin: const EdgeInsets.only(left: 120),
260 | decoration: BoxDecoration(
261 | color: Colors.white,
262 | borderRadius: BorderRadius.circular(18),
263 | ),
264 | padding: const EdgeInsets.all(15),
265 | child: Text(dashboardParams.issueSummary.issuesLabeled),
266 | ),
267 | ),
268 | ],
269 | ),
270 | );
271 | }
272 |
273 | Widget secondColumn(BuildContext context) {
274 | return Expanded(
275 | child: Column(
276 | children: [
277 | Container(
278 | alignment: Alignment.topCenter,
279 | height: myHeight / 2,
280 | width: myWidth / 2,
281 | margin: const EdgeInsets.only(top: 125),
282 | child: MyLineChart(
283 | cache: cache,
284 | ),
285 | )
286 | ],
287 | ),
288 | );
289 | }
290 |
291 | Widget thirdColumn(
292 | BuildContext context,
293 | DashboardParameterList dashboardParams,
294 | ) {
295 | return Expanded(
296 | child: Column(
297 | children: [
298 | GestureDetector(
299 | onTap: () => {
300 | Navigator.of(context).pushReplacementNamed(
301 | RouteGenerator.issueDetailPage,
302 | )
303 | },
304 | child: Container(
305 | margin: const EdgeInsets.only(bottom: 75, top: 100, right: 120),
306 | decoration: BoxDecoration(
307 | color: Colors.white,
308 | borderRadius: BorderRadius.circular(18),
309 | ),
310 | padding: const EdgeInsets.all(15),
311 | child: Text(dashboardParams.pullRequestSummary.pullsOldTitle),
312 | ),
313 | ),
314 | GestureDetector(
315 | onTap: () => {
316 | Navigator.of(context).pushReplacementNamed(
317 | RouteGenerator.issueDetailPage,
318 | )
319 | },
320 | child: Container(
321 | margin: const EdgeInsets.only(bottom: 75, right: 120),
322 | decoration: BoxDecoration(
323 | color: Colors.white,
324 | borderRadius: BorderRadius.circular(18),
325 | ),
326 | padding: const EdgeInsets.all(15),
327 | child: Text(
328 | dashboardParams.pullRequestSummary.pullsHighActivity,
329 | ),
330 | ),
331 | ),
332 | GestureDetector(
333 | onTap: () => {
334 | Navigator.of(context).pushReplacementNamed(
335 | RouteGenerator.issueDetailPage,
336 | )
337 | },
338 | child: Container(
339 | margin: const EdgeInsets.only(bottom: 75, right: 120),
340 | decoration: BoxDecoration(
341 | color: Colors.white,
342 | borderRadius: BorderRadius.circular(18),
343 | ),
344 | padding: const EdgeInsets.all(15),
345 | child: Text(
346 | dashboardParams.pullRequestSummary.pullsLowActivity,
347 | ),
348 | ),
349 | ),
350 | GestureDetector(
351 | onTap: () => {
352 | Navigator.of(context).pushReplacementNamed(
353 | RouteGenerator.issueDetailPage,
354 | )
355 | },
356 | child: Container(
357 | margin: const EdgeInsets.only(right: 120),
358 | decoration: BoxDecoration(
359 | color: Colors.white,
360 | borderRadius: BorderRadius.circular(18),
361 | ),
362 | padding: const EdgeInsets.all(15),
363 | child: Text(dashboardParams.pullRequestSummary.pullsLabeled),
364 | ),
365 | ),
366 | ],
367 | ),
368 | );
369 | }
370 |
371 | Widget firstRow(
372 | BuildContext context,
373 | double width,
374 | double height,
375 | DashboardParameterList dashboardParams,
376 | ) {
377 | return Expanded(
378 | child: Container(
379 | decoration: BoxDecoration(
380 | color: Colors.white,
381 | borderRadius: BorderRadius.circular(18),
382 | ),
383 | height: double.maxFinite,
384 | child: Row(
385 | children: [
386 | const Spacer(),
387 | SizedBox(
388 | width: width / 4,
389 | child: GestureDetector(
390 | onTap: () => {
391 | Navigator.of(context).pushReplacementNamed(
392 | RouteGenerator.issueDetailPage,
393 | )
394 | },
395 | child: Text(
396 | dashboardParams.issueSummary.totalIssueTitle +
397 | dashboardParams.issueSummary.totalIssue,
398 | ),
399 | ),
400 | ),
401 | SizedBox(
402 | width: width / 4,
403 | child: GestureDetector(
404 | onTap: () => {
405 | Navigator.of(context).pushReplacementNamed(
406 | RouteGenerator.issueDetailPage,
407 | )
408 | },
409 | child: Text(
410 | dashboardParams.issueSummary.dormantIssueTitle +
411 | dashboardParams.issueSummary.totalDormantIssue,
412 | ),
413 | ),
414 | ),
415 | SizedBox(
416 | width: width / 4,
417 | child: GestureDetector(
418 | onTap: () => {
419 | Navigator.of(context).pushReplacementNamed(
420 | RouteGenerator.issueDetailPage,
421 | )
422 | },
423 | child: Text(
424 | dashboardParams.pullRequestSummary.dormantPullTitle +
425 | dashboardParams.pullRequestSummary.totalClosedPull,
426 | ),
427 | ),
428 | ),
429 | Expanded(
430 | child: GestureDetector(
431 | onTap: () => {
432 | Navigator.of(context).pushReplacementNamed(
433 | RouteGenerator.issueDetailPage,
434 | )
435 | },
436 | child: Text(
437 | dashboardParams.pullRequestSummary.totalIPullTitle +
438 | dashboardParams.pullRequestSummary.totalPull,
439 | ),
440 | ),
441 | ),
442 | ],
443 | ),
444 | ),
445 | );
446 | }
447 |
448 | Widget secondRow(
449 | BuildContext context,
450 | double width,
451 | double height,
452 | DashboardParameterList dashboardParams,
453 | ) {
454 | return SizedBox(
455 | height: (height * 4) / 5,
456 | child: Row(
457 | children: [
458 | firstColumn(context, dashboardParams),
459 | secondColumn(context),
460 | thirdColumn(context, dashboardParams),
461 | ],
462 | ),
463 | );
464 | }
465 | }
466 |
467 | ///Class that creates and displays the line chart found in the repo page
468 |
469 | class MyLineChart extends StatelessWidget {
470 | ///Constructor
471 |
472 | MyLineChart({Key? key, required this.cache}) : super(key: key);
473 |
474 | ///Instance of our admin dashboard cache
475 |
476 | final AdminDashboardCache cache;
477 |
478 | ///Data that is used in the chart (hardcoded for now)
479 |
480 | final data = {0: 0, 1: 0.75, 2: 3, 3: 1.2, 4: 1, 5: 5};
481 |
482 | @override
483 | Widget build(BuildContext context) {
484 | return LineChart(
485 | LineChartData(
486 | minX: 0,
487 | maxX: 5,
488 | minY: 0,
489 | maxY: 5,
490 | lineBarsData: [
491 | LineChartBarData(
492 | spots: createPoints(),
493 | )
494 | ],
495 | gridData: FlGridData(
496 | show: true,
497 | getDrawingHorizontalLine: (value) {
498 | return FlLine(
499 | color: Colors.black,
500 | strokeWidth: 1,
501 | );
502 | },
503 | ),
504 | ),
505 | );
506 | }
507 |
508 | ///Creates the points used in the line chart
509 | List createPoints() {
510 | ///List of spots to be used in the line chart
511 | final result = [];
512 | for (final x in data.keys) {
513 | result.add(FlSpot(x, data[x] ?? 0));
514 | }
515 | return result;
516 | }
517 | }
518 |
--------------------------------------------------------------------------------
/lib/page/welcome_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:admin_dashboard/dto/constant.dart';
2 | import 'package:admin_dashboard/page/drawer_widget.dart';
3 | import 'package:firebase_core/firebase_core.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | ///Default page shown in our application
7 | class WelcomePage extends StatefulWidget {
8 | ///welcome page constructor
9 | const WelcomePage({
10 | Key? key,
11 | //required this.fire,
12 | required this.firebaseApp,
13 | }) : super(key: key);
14 | ///Instance of firebaseApp
15 | final FirebaseApp firebaseApp;
16 |
17 | ///cache where the logged in user's ID is stored
18 | ///instance to handle firebase services
19 | //final BasicServiceInterface fire;
20 |
21 | @override
22 | State createState() => _WelcomePageState(//cache, //fire,
23 | firebaseApp,);
24 | }
25 |
26 | class _WelcomePageState extends State {
27 | _WelcomePageState(//this.cache, //this.fire,
28 | this.firebaseApp,);
29 | FirebaseApp firebaseApp;
30 | //AdminDashboardCache cache;
31 | //BasicServiceInterface fire;
32 |
33 | @override
34 | Widget build(BuildContext context) => Scaffold(
35 | drawer: DrawerWidget(//cache, //fire,
36 | firebaseApp,),
37 | appBar: AppBar(
38 | title: const Text(Constants.welcomePageTitle),
39 | backgroundColor: Colors.deepPurpleAccent,
40 | ),
41 | );
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/lib/provider/provider_list.dart:
--------------------------------------------------------------------------------
1 | import 'package:admin_dashboard/dto/admin_dashboard_cache_model.dart';
2 | import 'package:admin_dashboard/service/dashboard_services.dart';
3 | import 'package:admin_dashboard/service/issue_service.dart';
4 | import 'package:admin_dashboard/service/pull_request_service.dart';
5 | import 'package:admin_dashboard/service/table_data_service.dart';
6 | import 'package:flutter_riverpod/flutter_riverpod.dart';
7 |
8 | ///Provide the cache for the application
9 | ///It will store information such as the userId,
10 | ///and the token used for authentication
11 | final cacheProvider = Provider((ref) => AdminDashboardCache());
12 | ///Provides the singleton object of the class FireBaseService
13 | ///It helps implements the DI pattern, as the implementor
14 | ///of the Interface BasicServiceInterface
15 | final issueProvider= Provider((ref) => DummyIssueProvider());
16 |
17 | ///the dummy pull provider, returns dummy data for the repo page
18 | final pullProvider = Provider((ref) => DummyPullProvider());
19 |
20 | ///Contains the dummy providers for the repo page
21 | final myDashboardProvider = Provider((ref) =>
22 | DashboardServiceImplementor(DummyIssueProvider(),
23 | DummyPullProvider(),),);
24 |
25 | ///Dummy table provider for the repo/issues page
26 | final tabledataProvider = Provider((ref) => DummyTableDataProvider('')
27 | ,);
28 |
--------------------------------------------------------------------------------
/lib/route/routes.dart:
--------------------------------------------------------------------------------
1 | import 'package:admin_dashboard/page/issue_details_page.dart';
2 | import 'package:admin_dashboard/page/login_page.dart';
3 | import 'package:admin_dashboard/page/repo_details_page.dart';
4 | import 'package:admin_dashboard/page/repo_page.dart';
5 | import 'package:admin_dashboard/page/welcome_page.dart';
6 | import 'package:firebase_core/firebase_core.dart';
7 | import 'package:flutter/material.dart';
8 |
9 | ///The route generator that handles navigation throughout the app\
10 | class RouteGenerator {
11 | RouteGenerator._();
12 | ///Welcome page path
13 | static const String welcomePage = '/';
14 | ///repository page path
15 | static const String repoPage = '/repos';
16 | ///Login page path
17 | static const String loginPage = '/login';
18 | ///Repo details path
19 | static const String repoDetailsPage = '/repoDetails';
20 | ///Waiting page path
21 | static const String waitingPage = '/waiting';
22 | ///Issue details path
23 | static const String issueDetailPage = '/issueDetails';
24 |
25 |
26 |
27 |
28 | ///decides which page to open depending on the path provided to the navigator
29 | static Route generateRoute(RouteSettings settings,
30 | FirebaseApp firebaseApp,
31 |
32 | ) {
33 | switch (settings.name) {
34 | case welcomePage:
35 | return MaterialPageRoute(
36 | builder: (_) => WelcomePage(//cache: cache, //fire: fire,
37 | firebaseApp: firebaseApp,),
38 | );
39 | case repoPage:
40 | return MaterialPageRoute(
41 | builder: (_) => RepoPage(//cache: cache, //fire: fire,
42 | firebaseApp: firebaseApp,),
43 | );
44 | case repoDetailsPage:
45 | return MaterialPageRoute(
46 | builder: (_) => RepoDetailsPage(//cache: cache,
47 | firebaseApp: firebaseApp,)
48 | ,);
49 | case loginPage:
50 | return MaterialPageRoute(
51 | builder: (_) => LoginPage(//cache: cache,
52 | firebaseApp: firebaseApp,
53 | )
54 | ,);
55 | case waitingPage:
56 | return MaterialPageRoute(
57 | builder: (_) => WaitingPage(//cache: cache,
58 | firebaseApp: firebaseApp,
59 | )
60 | ,);
61 | case issueDetailPage:
62 | return MaterialPageRoute(
63 | builder: (_) => IssueDetailsPage(//cache: cache,
64 | firebaseApp: firebaseApp,
65 | )
66 | ,);
67 | default:
68 | throw const FormatException('Route not found');
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/lib/service/basic_service.dart:
--------------------------------------------------------------------------------
1 | import 'package:admin_dashboard/dto/admin_dashboard_cache_model.dart';
2 | import 'package:admin_dashboard/dto/issue_model.dart';
3 | import 'package:admin_dashboard/dto/pull_model.dart';
4 | import 'package:admin_dashboard/dto/repo_model.dart';
5 | import 'package:flutter/cupertino.dart';
6 |
7 | ///The interface used to define methods that the GitHub Service Implementor
8 | ///will provide
9 | abstract class BasicServiceInterface {
10 | ///retrieve all issues across org
11 | List getAllIssues();
12 | ///retrieve all issues in a specific repo
13 | Future> getAllRepoIssues(
14 | String repoName,
15 | BuildContext context,
16 | AdminDashboardCache cache,
17 | );
18 | ///adds issue to a specific repo as defined in the issue param
19 | Future addIssue(Issue issue);
20 |
21 | ///Retrieve all PRs across organization
22 | List getAllPulls();
23 |
24 | ///retrieves all PRs in a specific repo
25 | Future> getAllRepoPulls(
26 | String repoName,
27 | BuildContext context,
28 | AdminDashboardCache cache,
29 | );
30 | ///adds PR to a specific repo as defined in the PR param
31 | Future addPull(Pull pr);
32 |
33 | ///retrieves all repositories belonging to a specific organization
34 | Future> getAllRepos( BuildContext context,
35 | AdminDashboardCache cache,
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/lib/service/dashboard_services.dart:
--------------------------------------------------------------------------------
1 | import 'package:admin_dashboard/service/issue_service.dart';
2 | import 'package:admin_dashboard/service/pull_request_service.dart';
3 |
4 | ///For the repo page, this contains all the data and provides it to the page
5 | ///currently, dummy data is being used
6 | class DashboardParameterList{
7 | ///Pull request summary contains all the info regarding PRs in the repo
8 | ///dashboard page
9 | PullSummaryInformation pullRequestSummary = PullSummaryInformation();
10 | ///Issue summary also contains all the info regarding issues in the
11 | ///repo dashboard page
12 | IssueSummaryInformation issueSummary = IssueSummaryInformation();
13 | }
14 | ///Returns the parameters for all data in the repo page
15 | abstract class DashboardServiceInterface {
16 | ///Returns the dashboard parameters for all data in repo page,
17 | ///currently contains dummy providers
18 | Future getDashboardParameters();
19 | }
20 |
21 | ///Implements the dummy providers for the repo page
22 | class DashboardServiceImplementor extends DashboardServiceInterface{
23 | ///Constructor
24 | DashboardServiceImplementor(this.issueProvider, this.pullServiceProvider);
25 | ///Instance of the issue provider
26 | IssueProviderInterface issueProvider;
27 | ///Instance of the pull request provider
28 | PullProviderInterface pullServiceProvider;
29 |
30 | @override
31 | Future getDashboardParameters() async{
32 | final params = DashboardParameterList();
33 | params.issueSummary = await issueProvider.getIssueSummaryInformation();
34 | params.pullRequestSummary =
35 | await pullServiceProvider.getPullSummaryInformation();
36 | return params;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lib/service/fire_base.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:admin_dashboard/dto/admin_dashboard_cache_model.dart';
4 | import 'package:admin_dashboard/dto/constant.dart';
5 | import 'package:admin_dashboard/dto/issue_model.dart';
6 | import 'package:admin_dashboard/dto/pull_model.dart';
7 | import 'package:admin_dashboard/dto/repo_model.dart';
8 | import 'package:admin_dashboard/main.dart';
9 | import 'package:admin_dashboard/service/basic_service.dart';
10 | import 'package:firebase_core/firebase_core.dart';
11 | import 'package:flutter/cupertino.dart';
12 | import 'package:http/http.dart' as http;
13 |
14 |
15 |
16 | ///implements the methods that Firebase as a middleware
17 | ///will provide for GitHub
18 | class FireBaseService implements BasicServiceInterface {
19 | ///FirebaseService constructor that takes firebaseApp as a parameter
20 | FireBaseService(this.firebaseApp);
21 | ///Instance of firebaseApp
22 | FirebaseApp firebaseApp;
23 |
24 | @override
25 | Future addIssue(Issue issue) async {
26 | const siteUrl =
27 | '${Constants.repoLogLink}'
28 | '/${Constants.repoNameTest}'
29 | '/${Constants.issues}.json';
30 |
31 | try {
32 | final urlChat = Uri.parse(siteUrl);
33 | final issue = Issue(
34 | issueID: 250,
35 | state: 'closed',
36 | title: 'Test Issue 25',
37 | loggedAt: DateTime.now(),
38 | closedAt: DateTime.now(),
39 | closedBy: 'Amer',
40 | commentsNumber: 4,
41 | repository: Constants.repoNameTest,
42 | );
43 | debugPrint(issue.toJson().toString());
44 | final response1 = await http.post(urlChat,
45 | body: json.encode(issue.toJson()),);
46 | debugPrint(response1.statusCode.toString());
47 | return issue;
48 | } catch (error) {
49 | rethrow;
50 | }
51 | }
52 |
53 | @override
54 | List getAllIssues() {
55 | // TODO: implement getAllIssues
56 | throw UnimplementedError();
57 | }
58 |
59 | @override
60 | Future> getAllRepoIssues(
61 | String repoName,
62 | BuildContext context,
63 | AdminDashboardCache cache,
64 | ) async {
65 | final result = [];
66 | final url = Uri.parse('https://api.github.com$repoName');
67 | try {
68 | final response = await http.get(
69 | url,
70 | headers: {
71 | 'Authorization': 'Bearer ${EnvironmentConfig.token}',
72 | },
73 | );
74 | if (response.statusCode != 200) {
75 | return result;
76 | }
77 |
78 | final body = response.body;
79 | if (body == 'null') {
80 | return result;
81 | }
82 |
83 | final issues = json.decode(response.body);
84 | List issueList;
85 | issueList = issues as List;
86 | for (final element in issueList) {
87 | Map issue;
88 | issue = element as Map;
89 | debugPrint("id=${issue['id'] ?? "null"}");
90 | debugPrint("title=${issue['title'] ?? "null"}");
91 | }
92 |
93 | return result;
94 | } catch (error) {
95 | rethrow;
96 | }
97 | }
98 |
99 | @override
100 | Future addPull(Pull pr) async {
101 | const siteUrl =
102 | '${Constants.siteUrl}'
103 | '/${Constants.repoNameTest}'
104 | '/${Constants.pulls}.json';
105 | try {
106 | final urlChat = Uri.parse(siteUrl);
107 | Pull pull;
108 | pull = Pull(
109 | userId: 250,
110 | state: 'closed',
111 | title: 'Test PR',
112 | loggedAt: DateTime.now(),
113 | closedAt: DateTime.now(),
114 | closedBy: 'Amer',
115 | commentsNumber: 4,
116 | repository: 'Flutter Test',
117 | );
118 | debugPrint(pull.toJson().toString());
119 | final response2 =
120 | await http.post(urlChat, body: json.encode(pull.toJson()));
121 | debugPrint(response2.statusCode.toString());
122 | return pull;
123 | } catch (error) {
124 | rethrow;
125 | }
126 | }
127 |
128 | @override
129 | List getAllPulls() {
130 | // TODO: implement getAllPulls
131 | throw UnimplementedError();
132 | }
133 |
134 | @override
135 | Future> getAllRepoPulls(
136 | String repoName, BuildContext context, AdminDashboardCache cache,) {
137 | throw UnimplementedError();
138 | }
139 |
140 | @override
141 | Future> getAllRepos(
142 | BuildContext context,
143 | AdminDashboardCache cache,) async {
144 | final result = [];
145 | var page =0;
146 | while(true) {
147 | page++;
148 | final url = Uri.parse('${Constants.gitApi}/'
149 | '${Constants.orgs}/'
150 | '${Constants.flc}/'
151 | '${Constants.repos}?page=$page',);
152 | try {
153 | final response = await http.get(
154 | url,
155 | headers: {
156 | 'Authorization': 'Bearer ${EnvironmentConfig.token}',
157 | },
158 | );
159 | if (response.statusCode != 200) {
160 | debugPrint('status code = ${response.statusCode} '
161 | ,);
162 | return result;
163 | }
164 |
165 | final body = response.body;
166 | if (body == 'null') {
167 | return result;
168 | }
169 | final repos = json.decode(response.body);
170 | List repoList;
171 | repoList = repos as List;
172 | if(repoList.isEmpty){
173 | break;//breaks the while loop
174 | }
175 | for (final element in repoList) {
176 | Map repo;
177 | repo = element as Map;
178 | final name
179 | = (repo['name'] == null) ? '' : repo['name'].toString();
180 | debugPrint('name=$name');
181 | debugPrint("url=${repo['url'] ?? "url"}");
182 |
183 | if (name.isNotEmpty) {
184 | final repoObj = SimpleRepo(
185 | name: Constants.fluttercommunityPath + name,);
186 | result.add(repoObj);
187 | }
188 | }
189 | } catch (error) {
190 | rethrow;
191 | }
192 | }
193 | return result;
194 |
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/lib/service/github_service.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'package:admin_dashboard/dto/constant.dart';
3 | import 'package:admin_dashboard/dto/issue.dart';
4 | import 'package:admin_dashboard/main.dart';
5 | import 'package:flutter/cupertino.dart';
6 | import 'package:http/http.dart' as http;
7 |
8 | ///class that handles retrieving data from github API
9 | class GithubService{
10 |
11 | ///Retrieves all issues in a repository
12 | Future> getAllIssuesOfRepo(
13 | String repoName,
14 | ) async {
15 | final result = [];
16 | final url =
17 | Uri.parse('${Constants.repoGitUrl}$repoName${Constants.repoGitIssue}');
18 | try {
19 | final response = await http.get(
20 | url,
21 | headers: {
22 | 'Authorization': 'Bearer ${EnvironmentConfig.token}',
23 | },
24 | );
25 | if (response.statusCode != 200) {
26 | return result;
27 | }
28 |
29 | final body = response.body;
30 | if (body == 'null') {
31 | return result;
32 | }
33 |
34 | final issues = json.decode(response.body);
35 | List issueList;
36 | issueList = issues as List;
37 | SimpleIssue myIssue;
38 | for (final element in issueList) {
39 | Map issue;
40 | issue = element as Map;
41 | //debugPrint("id=${issue['id'] ?? "null"}");
42 | debugPrint("title=${issue['title'] ?? "null"}");
43 | debugPrint("url=${issue['url'] ?? "null"}");
44 | debugPrint("state=${issue['state'] ?? "null"}");
45 | debugPrint("comments=${issue['comments'] ?? "null"}");
46 | debugPrint("created_at=${issue['created_at'] ?? "null"}");
47 | debugPrint("updated_at=${issue['updated_at'] ?? "null"}");
48 |
49 |
50 |
51 |
52 | myIssue = SimpleIssue(title: issue['title']!.toString(),
53 | url: issue['url']!.toString(),
54 | state: issue['state']!.toString(),
55 | comments: int.tryParse(issue['comments']!.toString()) ?? 0,
56 | createdAt:
57 | DateTime.tryParse(issue['created_at']!.toString()) ?? DateTime.now(),
58 | updatedAt:
59 | DateTime.tryParse(issue['updated_at']!.toString()) ?? DateTime.now(),
60 | );
61 |
62 | debugPrint(myIssue.toString());
63 | debugPrint('\n********************************\n');
64 | result.add(myIssue);
65 |
66 | }
67 | //debugPrint(result.toString());
68 | return result;
69 | } catch (error) {
70 | rethrow;
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/lib/service/issue_service.dart:
--------------------------------------------------------------------------------
1 | ///Information regarding issues that is displayed in repo dashboard
2 | abstract class IssueProviderInterface {
3 | ///Retrieves total issues in a repo
4 | Future getTotalIssue();
5 | ///Retrieves total closed issues in a repo
6 | Future getTotalClosedIssue();
7 | ///Retrieves number of labeled issues in a repo
8 | Future getTagNumberIssue();
9 | ///Retrieves number of dormant issues in a repo
10 | Future getTotalDormantIssue();
11 | ///Retrieves number of issues that have been recently opened
12 | Future getRecentIssue();
13 | ///Retrieves number of issues older than 6 months
14 | Future getOldIssue();
15 | ///Retrieves number of total issues
16 | Future totalIssueTitle();
17 | ///Retrieves Dormant issues keyword
18 | Future dormantIssueTitle();
19 | ///Retrieves issues with high activity keyword
20 | Future issuesHighActivity();
21 | ///Retrieves issues with low activity keyword
22 | Future issuesLowActivity();
23 | ///Retrieves issues grouped by label keyword
24 | Future issuesLabeled();
25 | ///Retrieves issues older than 6 months keyword
26 | Future issuesOldTitle();
27 | ///contains all the info regarding issues in the repo dashboard
28 | Future getIssueSummaryInformation();
29 |
30 | }
31 | ///Information regarding issues that is displayed in repo dashboard
32 | class IssueSummaryInformation {
33 | ///number of total issues
34 | String totalIssue = '...';
35 | ///number of closed issues
36 | String totalClosedIssue = '...';
37 | ///number of dormant issues
38 | String totalDormantIssue = '...';
39 | ///number of recent issues
40 | String recentIssue = '...';
41 | ///number of old issues
42 | String oldIssue = '...';
43 | ///Dormant issues keyword
44 | String dormantIssueTitle = '...';
45 | ///Total issues keyword
46 | String totalIssueTitle = '...';
47 | ///Issues with label keyword where the user can then see issues with
48 | ///certain labels
49 | String issuesLabeled = '...';
50 | ///Issues with high activity where a user clicks and then sees a list of
51 | ///issues with high activity
52 | String issuesHighActivity = '...';
53 | ///Issues with low activity where a user clicks and then sees a list of
54 | ///issues with low activity
55 | String issuesLowActivity = '...';
56 | /// issues older than 6 months key word
57 | String issuesOldTitle = '...';
58 | }
59 |
60 | ///Dummy issue provider that contains all hardcoded info until they are
61 | ///fully implemented
62 | class DummyIssueProvider extends IssueProviderInterface {
63 | @override
64 | Future getOldIssue() async {
65 | return 13;
66 | }
67 |
68 | @override
69 | Future getRecentIssue() async {
70 | return 2;
71 | }
72 |
73 | @override
74 | Future getTagNumberIssue() async {
75 | return 9;
76 | }
77 |
78 | @override
79 | Future getTotalClosedIssue() async {
80 | return 4;
81 | }
82 |
83 | @override
84 | Future getTotalDormantIssue() async {
85 | return 7;
86 | }
87 |
88 | @override
89 | Future getTotalIssue() async {
90 | return 5;
91 | }
92 |
93 | @override
94 | Future dormantIssueTitle() async {
95 | return 'Dormant Issues: ';
96 | }
97 |
98 | @override
99 | Future totalIssueTitle() async {
100 | return 'Total Issues: ';
101 | }
102 |
103 | @override
104 | Future issuesHighActivity() async {
105 | return 'Open issues with high activity ';
106 | }
107 |
108 | @override
109 | Future issuesLabeled() async {
110 | return 'Open issues grouped by label ';
111 | }
112 |
113 | @override
114 | Future issuesLowActivity() async {
115 | return 'Open issues with no activity';
116 |
117 | }
118 |
119 | @override
120 | Future issuesOldTitle() async {
121 | return 'Open issues older than 6 months';
122 | }
123 |
124 | @override
125 | Future getIssueSummaryInformation() async {
126 | ///info contains all the information regarding the issue dummy provider
127 | final info = IssueSummaryInformation();
128 | info..oldIssue = (await getOldIssue()).toString()
129 | ..recentIssue = (await getRecentIssue()).toString()
130 | ..totalClosedIssue = (await getTotalClosedIssue()).toString()
131 | ..totalDormantIssue = (await getTotalDormantIssue()).toString()
132 | ..totalIssue = (await getTotalIssue()).toString()
133 | ..dormantIssueTitle = (await dormantIssueTitle())
134 | ..totalIssueTitle = (await totalIssueTitle())
135 | ..issuesHighActivity = (await issuesHighActivity())
136 | ..issuesLabeled = (await issuesLabeled())
137 | ..issuesLowActivity = (await issuesLowActivity())
138 | ..issuesOldTitle = (await issuesOldTitle());
139 | return info;
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/lib/service/pull_request_service.dart:
--------------------------------------------------------------------------------
1 | ///Information regarding PRs that is displayed in the repo dashboard
2 | abstract class PullProviderInterface {
3 | ///Retrieves number of total PRs
4 | Future getTotalPull();
5 | ///Retrieves number of total closed PRs
6 | Future getTotalClosedPull();
7 | ///Retrieves number of PRs that are dormant
8 | Future getTotalDormantPull();
9 | ///Retrieves number of recent PRs
10 | Future getRecentPull();
11 | ///Retrieves keyword PRs older than 6 months
12 | Future getOldPull();
13 | ///Retrieves the title for total PRs
14 | Future totalPullTitle();
15 | ///Retrieves the title for Dormant PRs
16 | Future dormantPullTitle();
17 | ///Retrieves keyword PRs with high activity
18 | Future pullsHighActivity();
19 | ///Retrieves keyword PRs with low activity
20 | Future pullsLowActivity();
21 | ///Retrieves keyword PRs with labels
22 | Future pullsLabeled();
23 | ///Retrieves keyword PRs that are older than 6 months
24 | Future pullsOldTitle();
25 | ///Contains all the information regarding PRs in the repo dashboard
26 | Future getPullSummaryInformation();
27 | }
28 | ///Information regarding PRs that is displayed in the repo dashboard
29 | class PullSummaryInformation {
30 | ///Total number of PRs in a repo
31 | String totalPull = '...';
32 | ///Total number of closed PRs in a repo
33 | String totalClosedPull = '...';
34 | ///Total number of dormant issues
35 | String totalDormantPull = '...';
36 | ///Recent PRs
37 | String recentPull = '...';
38 | ///Old PRs
39 | String oldPull = '...';
40 | ///Title for dormant PRs
41 | String dormantPullTitle = '...';
42 | ///Title for total PRs
43 | String totalIPullTitle = '...';
44 | ///PRs grouped by label keyword
45 | String pullsLabeled = '...';
46 | ///PRs with high activity keyword
47 | String pullsHighActivity = '...';
48 | ///PRs with low activity keyword
49 | String pullsLowActivity = '...';
50 | ///PRs older than 6 months keyword
51 | String pullsOldTitle = '...';
52 | }
53 | ///Dummy pull request provider that contains everything hardcoded until it
54 | ///is fully implemented
55 | class DummyPullProvider extends PullProviderInterface {
56 | @override
57 | Future getOldPull() async {
58 | return 8;
59 | }
60 |
61 | @override
62 | Future getRecentPull() async {
63 | return 4;
64 | }
65 |
66 |
67 | @override
68 | Future getTotalClosedPull() async {
69 | return 12;
70 | }
71 |
72 | @override
73 | Future getTotalDormantPull() async {
74 | return 11;
75 | }
76 |
77 | @override
78 | Future getTotalPull() async {
79 | return 42;
80 | }
81 |
82 | @override
83 | Future dormantPullTitle() async {
84 | return 'Dormant Pull Requests: ';
85 | }
86 |
87 | @override
88 | Future totalPullTitle() async {
89 | return 'Total Pull Requests: ';
90 | }
91 |
92 | @override
93 | Future pullsHighActivity() async {
94 | return 'PRs with high activity';
95 |
96 | }
97 |
98 | @override
99 | Future pullsLabeled() async {
100 | return 'PRs grouped by label';
101 |
102 | }
103 |
104 | @override
105 | Future pullsLowActivity() async {
106 | return 'PRs with low activity';
107 |
108 | }
109 |
110 | @override
111 | Future pullsOldTitle() async {
112 | return 'PRs older than 2 months';
113 |
114 | }
115 | @override
116 | Future getPullSummaryInformation() async {
117 | final info = PullSummaryInformation();
118 | info..oldPull = (await getOldPull()).toString()
119 | ..recentPull = (await getRecentPull()).toString()
120 | ..totalClosedPull = (await getTotalClosedPull()).toString()
121 | ..totalDormantPull = (await getTotalDormantPull()).toString()
122 | ..totalPull = (await getTotalPull()).toString()
123 | ..dormantPullTitle = (await dormantPullTitle())
124 | ..totalIPullTitle = (await totalPullTitle())
125 | ..pullsHighActivity = (await pullsHighActivity())
126 | ..pullsLabeled = (await pullsLabeled())
127 | ..pullsLowActivity = (await pullsLowActivity())
128 | ..pullsOldTitle = (await pullsOldTitle());
129 |
130 | return info;
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/lib/service/table_data_service.dart:
--------------------------------------------------------------------------------
1 | import 'package:admin_dashboard/dto/table_data.dart';
2 |
3 | ///Provider for the issue table
4 | abstract class TableDataProviderInterface {
5 | ///Constructor
6 | TableDataProviderInterface(this.repoName);
7 | ///Name of the repository
8 | String repoName;
9 | ///Issues opened within the last 6 months
10 | Future getOpenInLastSixMonths();
11 | ///To be implemented
12 | Future getSecondTable();
13 | }
14 |
15 | ///Dummy table data provider
16 | class DummyTableDataProvider extends TableDataProviderInterface {
17 | ///Constructor
18 | DummyTableDataProvider(String repoName) : super(repoName);
19 |
20 | @override
21 | Future getOpenInLastSixMonths() async{
22 | return TableData().add(TableDataRow().addValue('Name', 'Bug fix').
23 | addValue('Open', '3 days'),)
24 | .add(TableDataRow().addValue('Name', 'Enhancement').
25 | addValue('Open', '7 days'),)
26 | .add(TableDataRow().addValue('Name', 'Feature').
27 | addValue('Open', '1 month'),)
28 | .add(TableDataRow().addValue('Name', 'Help wanted').
29 | addValue('Open', '4 months'),);
30 | }
31 |
32 | @override
33 | Future getSecondTable() async{
34 | return TableData().add(TableDataRow().addValue('Name', 'Bug fix').
35 | addValue('Open', '3 days'),)
36 | .add(TableDataRow().addValue('Name', 'Enhancement').
37 | addValue('Open', '7 days'),)
38 | .add(TableDataRow().addValue('Name', 'Feature').
39 | addValue('Open', '1 month'),)
40 | .add(TableDataRow().addValue('Name', 'Help wanted').
41 | addValue('Open', '4 months'),);
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/lib/view_util.dart:
--------------------------------------------------------------------------------
1 | import 'package:admin_dashboard/dto/constant.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | /// Utility class used to create and display dialogs
5 | class ViewUtil {
6 |
7 | /// set up the AlertDialog
8 | static void showMessage(String title, String message, BuildContext context) {
9 | final Widget okButton = TextButton(
10 | child: const Text(Constants.ok),
11 | onPressed: () {
12 | Navigator.pop(context);
13 | },
14 | );
15 |
16 | final alert = AlertDialog(
17 | title: Text(title),
18 | content: Text(message),
19 | actions: [
20 | okButton,
21 | ],
22 | );
23 |
24 | /// show the dialog
25 | showDialog(
26 | context: context,
27 | builder: (BuildContext context) {
28 | return alert;
29 | },
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: admin_dashboard
2 | description: Admin Dashboard to assist admins and maintainers by pinging them to repositories that require attention
3 |
4 | # The following line prevents the package from being accidentally published to
5 | # pub.dev using `flutter pub publish`. This is preferred for private packages.
6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
7 |
8 | # The following defines the version and build number for your application.
9 | version: 1.0.0+1
10 |
11 | environment:
12 | sdk: ">=2.16.2 <3.0.0"
13 |
14 | # Dependencies specify other packages that your package needs in order to work.
15 | dependencies:
16 | flutter:
17 | sdk: flutter
18 |
19 | build_runner:
20 | cupertino_icons: ^1.0.2
21 | # These 2 are use for mobile only
22 | firebase_auth: ^3.6.3
23 | firebase_core: ^1.20.1
24 |
25 | flutter_riverpod: ^1.0.0
26 | github_sign_in: ^0.0.5-dev.4
27 | http: ^0.13.4
28 | json_serializable: ^6.2.0
29 | json_annotation:
30 | easy_sidemenu: ^0.3.1
31 | flutter_dotenv: ^5.0.2
32 | fl_chart: ^0.55.1
33 | web_browser_detect: ^2.0.3
34 | # cloud_functions: ^3.3.2
35 |
36 | dev_dependencies:
37 | flutter_test:
38 | sdk: flutter
39 | injectable_generator: ^1.5.2
40 |
41 | flutter_lints:
42 |
43 |
44 | # The following section is specific to Flutter.
45 | flutter:
46 | assets:
47 | - env_var.env
48 | uses-material-design: true
49 |
--------------------------------------------------------------------------------
/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercommunity/admin_dashboard/3c89951dd97b1fd648b8bde754cb252dd2f604bb/web/favicon.png
--------------------------------------------------------------------------------
/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercommunity/admin_dashboard/3c89951dd97b1fd648b8bde754cb252dd2f604bb/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercommunity/admin_dashboard/3c89951dd97b1fd648b8bde754cb252dd2f604bb/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/web/icons/Icon-maskable-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercommunity/admin_dashboard/3c89951dd97b1fd648b8bde754cb252dd2f604bb/web/icons/Icon-maskable-192.png
--------------------------------------------------------------------------------
/web/icons/Icon-maskable-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercommunity/admin_dashboard/3c89951dd97b1fd648b8bde754cb252dd2f604bb/web/icons/Icon-maskable-512.png
--------------------------------------------------------------------------------
/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | admin_dashboard_v2
33 |
34 |
35 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
66 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "admin_dashboard_v2",
3 | "short_name": "admin_dashboard_v2",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "background_color": "#0175C2",
7 | "theme_color": "#0175C2",
8 | "description": "A new Flutter project.",
9 | "orientation": "portrait-primary",
10 | "prefer_related_applications": false,
11 | "icons": [
12 | {
13 | "src": "icons/Icon-192.png",
14 | "sizes": "192x192",
15 | "type": "image/png"
16 | },
17 | {
18 | "src": "icons/Icon-512.png",
19 | "sizes": "512x512",
20 | "type": "image/png"
21 | },
22 | {
23 | "src": "icons/Icon-maskable-192.png",
24 | "sizes": "192x192",
25 | "type": "image/png",
26 | "purpose": "maskable"
27 | },
28 | {
29 | "src": "icons/Icon-maskable-512.png",
30 | "sizes": "512x512",
31 | "type": "image/png",
32 | "purpose": "maskable"
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------